ES6读书笔记(二)

前言

前段时间整理了ES6的读书笔记:《ES6读书笔记(一)》,现在为第二篇,本篇内容包括:

  • 一、数组扩展
  • 二、对象扩展
  • 三、函数扩展
  • 四、Set和Map数据结构
  • 五、Reflect

本文笔记也主要是根据阮一峰老师的《ECMAScript 6 入门》和平时的理解进行整理的,希望对你有所帮助,喜欢的就点个赞吧!

一、数组扩展

1. 扩展运算符

①复制数组:

const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

②求最大值:

Math.max(...[14, 3, 77])
Math.max.apply(null, [14, 3, 77])

③合并数组:

let arr1 = ['a', 'b'];
let arr2 = ['c'];
let arr3 = ['d', 'e']; // ES5:
arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] 为浅拷贝,slice、Object.assign()也为浅拷贝 // ES6:
[...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ] 浅拷贝 // push:
Array.prototype.push.apply(arr1, arr2); // 因为push参数不能为数组
arr1.push(...arr2);

④数组克隆:

// ES5:
const a1 = [1, 2];
const a2 = a1.concat(); // ES6:
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

⑤与解构赋值结合:

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5] // 赋值时即在左边时只能放在参数最后一位
const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错 var arr1 = ['a', 'b'];
var a2 = [...arr1,2]; // 这样是没问题的,因为是扩展运算
console.log(a2) // ['a', 'b', 2]

⑥将字符串转为真正的数组:

[...'hello']   // [ "h", "e", "l", "l", "o" ]
Array.from('hello') // [ "h", "e", "l", "l", "o" ] //扩展运算符内部调用的是数据结构的Iterator接口,因此只有Iterator接口的对象才可以用扩展运算符转为真正的数组,Array.from也是如此,同时Array.from还支持转化类似数组的对象,即带有length属性的对象,可不含遍历器接口(Symbol.iterator):
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}; let arr = [...arrayLike]; // TypeError: Cannot spread non-iterable object.
// 这个类似数组的对象没有Iterator接口,所以不能转化,可以使用Array.from() Array.from({ length: 3 }); // [ undefined, undefined, undefined ]
Array.from({ length: 2 }, () => 'jack') // ['jack', 'jack']

⑦扩展运算符后面是一个空数组,则不产生任何效果:

[...[], 1]  // [1]

2. Array上的方法:

①Array.from(数组,回调函数(val,index),this绑定)

第二个参数,类似于map方法,第三参数,this指向:

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

应用:

(1)可用于转化/拷贝数组:

//原方式:
Array.prototype.slice.call(arr); let arr1 = ['a', 'b', 'c'];
let arr2 = Array.from(arr1); // ['a', 'b', 'c']

(2)处理空位:

var arrLike = {
length: 4,
2: "foo"
};
Array.from( arrLike );
// [ undefined, undefined, "foo", undefined ] // 和Array(3)产生空槽位不一样,这里是有undefined值的,会将空位转为undefined

②Array.of():用于将一组值,转换为数组:

与Array()不同,Array()的单参数会变为长度:

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8] Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1 //模拟:
function ArrayOf(){
return [].slice.call(arguments);
}

3. Array实例的方法:

①find((val, index, arr)=>{}, this) :用于找出【第一个】符合条件的【数组成员】,它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined,第二参数为this绑定。

[1, 4, -5, 10].find((n) => n < 0);
// -5 不是返回数组 [1, 5, 10, 15].find((value, index, arr) => value > 9); // 10 var a = [1,2,3,4,5];
(a.indexOf("2") != -1); // false indexOf是严格匹配===的,所以不会转化字符串 a.find(v => v == "2"); // 2 返回这个匹配的值,而不是返回布尔值
a.find(v => v == 7); // undefined let person = {name: 'John', age: 20};
[10, 12, 26, 15].find((v) => { v > this.age;}, person); // 26

②findIndex((val, index, arr)=>{}, this) :返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1,第二参数为this绑定。

find()和findIndex()都弥补了indexOf方法对NaN的不足:

[NaN].indexOf(NaN)   // -1
[NaN].findIndex(y => Object.is(NaN, y)) // 0

③copyWithin(target, start = 0, end = this.length)

target(必需):从该位置开始替换数据。如果为负值,表示倒数。

start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。

end(可选):到该位置 前 (这个位置前,所以不包含这个位置)停止读取数据,默认等于数组长度。如果为负值,表示倒数。

[1, 2, 3, 4, 5].copyWithin(0, 3)   // [4, 5, 3, 4, 5]  4、5替换1、2
[1, 2, 3, 4, 5].copyWithin(0, 2) // [3, 4, 5, 4, 5] 3、4、5替换1、2、3
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5] 4替换1

④entries(),keys()和values()——用于遍历数组:返回的是遍历器对象,可使用for of或者扩展运算符遍历出来:

for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1 for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b' for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b" var a = [1,2,3];
[...a.values()]; // [1,2,3]
[...a.keys()]; // [0,1,2]
[...a.entries()]; // [ [0,1], [1,2], [2,3] ]
[...a[Symbol.iterator]()]; // [1,2,3] //也可手动next调用:
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

⑤includes(包含项,起始位置): Array.prototype.includes方法返回一个布尔值(而find返回的是值),表示某个数组是否包含给定的值,与字符串的includes方法类似:

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true 弥补了indexOf(使用===)对NaN的不足

//该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始:

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, 4); // false
[1, 2, 3].includes(3, -1); // true

⑥fill(填充项,起始位置,结束位置):使用给定值填充数组

var a = Array(4).fill(undefined);
a; // [undefined,undefined,undefined,undefined] var a = [null, null, null, null].fill(42, 1, 3);
a; // [null,42,42,null] 不包含结束位置 //如果被赋值的是引用类型,是浅拷贝:
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

⑦flat():用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响:

[1, 2, [3, 4]].flat()   // [1, 2, 3, 4]

参数可设置拉平几层:

[1, 2, [3, [4, 5]]].flat(2)   // [1, 2, 3, 4, 5]

不管几层使用Infinity:

[1, [2, [3]]].flat(Infinity)   // [1, 2, 3]

会跳过空位:

[1, 2, , 4, 5].flat()   // [1, 2, 4, 5]

flatMap():只能展开一层,相当于map方法,第二参为this

[2, 3, 4].flatMap((x) => [x, x * 2])     // [2, 4, 3, 6, 4, 8]

//相当于:
[2, 3, 4].map((x) => [x, x * 2 ]).flat() [1, 2, 3, 4].flatMap(x => [[x * 2]]) // 只能展开一层 [[2], [4], [6], [8]]

4. 数组空位:空位不是undefined,是指没有值

var a = Array(3);
console.log(a) // [empty × 3]
console.log(a[0]) // undefined 为空,找不到值,所以返回undefined 0 in [undefined, undefined, undefined] // true 说明0号位是有值的
0 in [, , ,] // false

二、对象扩展

1. 对象属性

①简写:{x, y}

②属性名表达式:

let lastWord = 'last word';

const a = {
'first word': 'hello',
[lastWord]: 'world'
}; a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world" //表达式还可以用于定义方法名:
let obj = {
['h' + 'ello']() {
return 'hi';
}
}; obj.hello() // hi //属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]:
const keyA = {a: 1};
const keyB = {b: 2}; const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
}; myObject // Object {[object Object]: "valueB"} 'valueA'被覆盖了

③属性的可枚举性:设置为false,规避了for in操作,防止遍历到不可枚举属性。

Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false Object.getOwnPropertyDescriptor([], 'length').enumerable
// false

2.对象的解构赋值+扩展运算符:只能用在最后。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

为浅拷贝:

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

扩展运算符:用于取出参数对象的所有可遍历属性(应该就只是自身可枚举属性),拷贝到当前对象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

数组是特殊的对象,所以对象的扩展运算符也可以用于数组:

{...['a', 'b', 'c']}  // {0: "a", 1: "b", 2: "c"}

如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象:

{...'hello'}   // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

如果扩展运算符后面是一个空对象,则没有任何效果:

{...{}, a: 1}  // { a: 1 }

如果扩展运算符后面不是对象,则会自动将其转为对象:

{...1}  // {} 等同于 {...Object(1)}

对象的扩展运算符等同于使用Object.assign()方法:

let aClone = {...a};
// 等同于
let aClone = Object.assign({}, a);

上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法:

// 写法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
}; // 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);

扩展运算符可以用于合并两个对象:

let ab = {...a, ...b};
// 等同于
let ab = Object.assign({}, a, b);
)

3.super对象:指向当前对象的原型对象,super.foo等同于Object.getPrototypeOf(this).foo,只能用在对象的简写形式的方法之中

var o1 = {
foo() {
console.log("o1:foo");
}
};
var o2 = {
foo() {
super.foo(); // super相当于Object.getPrototypeOf(o2),指向o1,所以得到"o1:foo"的结果
console.log("o2:foo");
}
};
Object.setPrototypeOf(o2, o1);
o2.foo(); // o1:foo
// o2:foo

4. Object上新增的方法:

(1)Object.is():比较两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身:

+0 === -0       //true
NaN === NaN // false Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

(2)Object.assign():用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

// 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性:

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 }; Object.assign(target, source1, source2);
target // {a:1, b:2, c:3} // 如果只有一个参数,Object.assign会直接返回该参数:
const obj = {a: 1};
Object.assign(obj) === obj // true // 如果该参数不是对象,则会先转成对象,然后返回:
typeof Object.assign(2) // "object" // undefined和null无法转成对象,所以如果它们作为参数,就会报错:
Object.assign(undefined) // 报错
Object.assign(null) // 报错 // 如果非对象不是首参数,则会跳过(字符串会转为数字键对象),不会报错:
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true // 其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果:
const v1 = 'abc';
const v2 = true;
const v3 = 10; const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" } // 只拷贝源对象的自身可枚举属性,包括属性名为 Symbol 值的属性:
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' } // 且是浅拷贝:
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1); obj1.a.b = 2;
obj2.a.b // 2 // 可以用来处理数组,但是会把数组视为对象:
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]

上面代码中,Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。

应用:

(1)为对象添加属性和方法:

class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}}

上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。

(2)克隆或合并多个对象:

function clone(origin) {
return Object.assign({}, origin);
}

上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。

不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}

(3)为属性指定默认值:

const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
}; function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}

上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。

(3)Object.getOwnPropertyDescriptor():返回某个对象属性的描述对象。

const obj = {
foo: 123,
get bar() { return 'abc' }
}; Object.getOwnPropertyDescriptors(obj);
/*{ foo:
{ value: 123,
writable: true,
enumerable: true,
configurable: true
},
bar:
{ get: [Function: get bar],
set: undefined,
enumerable: true,
configurable: true
}
}
*/

继承:

const obj = Object.create(prot);
obj.foo = 123;
// 或者
const obj = Object.assign(
Object.create(prot),
{
foo: 123,
});

有了Object.getOwnPropertyDescriptors(),我们就有了另一种写法:

const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
}));

(4)Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替__proto__

①Object.setPrototypeOf(obj, prototype)

②Object.getPrototypeOf(obj)

如果参数不是对象,会被自动转为对象

(5)Object.keys()、Object.values()、Object.entries(),返回的都是数组,数组也有这些方法,但是返回的是遍历器对象。

①Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名:

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj);
// ["foo", "baz"]

②Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值:

const obj = {100: 'a', 2: 'b', 7: 'c'};
Object.values(obj);
// ["b", "c", "a"]
// 属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a

③Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组:

const obj = {foo: 'bar', baz: 42};
Object.entries(obj);
// [ ["foo", "bar"], ["baz", 42] ]

(6)Object.fromEntries():是Object.entries()的逆操作,用于将一个键值对数组转为对象。

Object.fromEntries([
['foo', 'bar'],
['baz', 42]])
// { foo: "bar", baz: 42 }

特别适合将Map结构转为对象:

const entries = new Map([
['foo', 'bar'],
['baz', 42]]); Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

三、函数扩展

1. 函数参数

(1)默认参数:

①不能重复声明:

function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}

②默认参数的位置应该是写在参数的末尾,这样才是自适应省略传参,否则如果默认参数放前面,如果不给默认传参,想给其它参数传参,则还是得向默认参数传递一个undefined,否则所传参数会覆盖默认参数,造成混淆。

③指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真:

(function (a) {}).length         // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2 (function(...args) {}).length // 0

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了:

(function (a = 0, b, c) {}).length   // 0
(function (a, b = 1, c) {}).length // 1

④产生作用域:

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的:

var x = 1;

function f(x, y = x) {   // 不会找到外部的x,因为参数作用域中第一个参数已经声明有了x,所以会y=x的x会指向它,而不会指向外部的x
console.log(y);
} f(2); // 2
f(); // undefined //---------------------------------- let x = 1; function f(y = x) { // 会找到外部的x,因为参数中未声明x,所以会往上层作用域查找x
let x = 2;
console.log(y);
} f(3); // 3
f(); // 1

⑤默认值表达式是惰性求值的,这意味着它们只在需要的时候运行——即在参数的值省略或者为undefined的时候:

function bar(val) {
console.log("bar called!");
return y + val;
}
function foo(x = y + 3, z = bar(x)) { // 形参作用域是在函数声明包裹的作用域,而不是在函数体
console.log(x, z);
}
var y = 5;
foo(); // "bar called"
// 8 13
foo(10); // "bar called"
// 10 15
y = 6;
foo(undefined, 10); // 9 10 //-------------------------------------------- var w = 1, z = 2;
function foo(x = w + 1, y = x + 1, z = z + 1) {
console.log(x, y, z);
}
foo(); // ReferenceError 问题出在z+1中的z发现z是一个未初始化的参数变量,所以不会往上层作用域查找z

⑥undefined会触发默认值,null不会。

(2)参数的解构赋值:

function foo({x, y = 5}) {
console.log(x, y);
} foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined 如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况:
function foo({x, y = 5} = {}) {
console.log(x, y);
} foo() // undefined 5

区别以下:

// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
} // 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
} // 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0] // x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8] // x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined] // x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined] m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

(3)参数使用...运算符:

①扩展运算符...之后不能再有其他参数:

// 报错
function f(a, ...b, c) {
// ...
}

②函数的length属性,不包括rest参数:

(function(a) {}).length  // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1

2. 箭头函数

①函数体内的this对象,就是定义时所在的对象(找离它最近的一个执行环境作为执行上下文,如对象方法中的箭头函数的this是指向window的,所以对象方法不要使用箭头函数),而不是使用时所在的对象。由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向:

function foo() {
// setTimeout中的函数默认由全局环境执行,所以此时this指向window
setTimeout(() => {
console.log('id:', this.id);
}, 100);
} var id = 21; foo() // id: 21
foo.call({id: 42}); // id: 42 call改变了this指向

②不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

③不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替:

function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
} foo(2, 4, 6, 8)
// args: [2, 4, 6, 8] 得到的是外部函数的参数

④不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

不适用场合:

const cat = {
lives: 9,
jumps: () => {
this.lives--;
console.log(this.lives);
}
}
cat.jumps(); // NaN

this指向:普通函数的 this 是动态的,所以要在运行时找拥有当前上下文的对象。

而箭头函数的 this 是静态的,也就是说,只需要看箭头函数在什么函数作用域下声明的,那么这个 this 就会绑定到这个函数的上下文中。即“穿透”箭头函数。

例子里的箭头函数并没有在哪个函数里声明,所以 thisfallback 到全局,全局的lives未声明,为undefined,运算后得到NaN。

(3)函数的name属性:

var f = function() {};
// ES5
f.name // ""
// ES6
f.name // "f" const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"

(4)双冒号运算符:

foo::bar;
// 等同于
bar.bind(foo); foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

(5)尾调用:指某个函数的最后一步是调用另一个函数。

function f(x){
return g(x);
}

四、Set和Map数据结构

4.1 Set

1. Set类似于数组,但是成员的值都是唯一的,没有重复的值,它是构造函数,用于构造Set数据结构:

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
console.log(i);
}
// 2 3 5 4 不会添加重复值

2. Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化:

const set = new Set([1, 2, 3, 4, 4]);  // 可为数组去重
set // Set结构: {1, 2, 3, 4}
[...set] // [1, 2, 3, 4]

数组或类数组去重:

Array.from(new Set(array))

也可去除字符串里的重复字符:

[...new Set('ababbc')].join('')
// "abc"

3. Set内部认为NaN是相等的,所以只能添加一个;两个对象也总是不相等的。

4. Array.from方法可以将 Set 结构转为数组:

const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

5. Set实例属性:

// 构造函数,默认就是Set函数
Set.prototype.constructor // 返回Set实例的成员总数
Set.prototype.size

6. Set实例方法:

四个操作方法:

  • ①add(value):添加某个值,返回 Set 结构本身。
  • ②delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • ③has(value):返回一个布尔值,表示该值是否为Set的成员。
  • ④clear():清除所有成员,没有返回值。
let s = new Set();

s.add(1).add(2).add(2);   // 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false s.delete(2);
s.has(2) // false

四个遍历方法:

  • ①keys():返回键名的遍历器
  • ②values():返回键值的遍历器
  • ③entries():返回键值对的遍历器
  • ④forEach():使用回调函数遍历每个成员

由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致:

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法:

Set.prototype[Symbol.iterator] === Set.prototype.values    // true
let set = new Set(['red', 'green', 'blue']); for (let x of set) {
console.log(x);
}
// red
// green
// blue //方便实现并集、交集、差集:
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]); // 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4} // 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3} // 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

4.2 WeakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别:

①WeakSet 的成员只能是对象,而不能是其他类型的值:

const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set

②WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,且不可遍历。

作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]} 是数组的成员成为WeakSet 实例对象的成员,而不是a,所以成员必须是对象
const b = [3, 4];
const ws = new WeakSet(b); // 成员不是对象,所以会报错
// Uncaught TypeError: Invalid value used in weak set(…)

三个方法:

  • WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
  • WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。

没有size属性,无法遍历

4.3 Map

Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键:

const m = new Map();
const o = {p: 'Hello World'}; m.set(o, 'content') // 将对象o作为键
m // {{…} => "content"}
m.get(o) // "content" m.has(o) // true
m.delete(o) // true
m.has(o) // false

可接受数组作为参数,数组成员是一个个表示键值对的数组:

const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map // {"name" => "张三", "title" => "Author"}
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map:

const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1 const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

只有相同的简单类型的键才会被视为同一个键,否则如数组、对象等作为键时,不是同一个引用时就算是同名,也是不同的键:

const map = new Map();

map.set(['a'], 555);   // 数组对象键
map.get(['a']) // undefined 不是同一个地址 // ---------------------------------------------------- const map = new Map(); const k1 = ['a'];
const k2 = ['a']; map
.set(k1, 111)
.set(k2, 222); map.get(k1) // 111 引用地址不同,所以是不同的键
map.get(k2) // 222

属性方法:

  • ①size 属性
  • ②set(key, value)
  • ③get(key)
  • ④has(key)
  • ⑤delete(key)
  • ⑥clear()

三个遍历器生成函数和一个遍历方法:

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历 Map 的所有成员。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]); for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T" for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes" for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes" // 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes" // 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes" map[Symbol.iterator] === map.entries // true

可用扩展运算符转为数组:

const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]); [...map.keys()]
// [1, 2, 3] [...map.values()]
// ['one', 'two', 'three'] [...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']] [...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

数组转为Map:

new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }

4.4 WeakMap

①WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名:

const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key

②WeakMap的键名所指向的对象,不计入垃圾回收机制。

不能遍历且无size属性

五、Reflect

类似于proxy,reflect也是为了操作对象,reflect对象上可以拿到语言内部的方法:

// 旧写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
} // 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
} // 旧写法
'assign' in Object // true // 新写法
Reflect.has(Object, 'assign') // true

1. Reflect.get(target, name, receiver)

Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined:

var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}; var myReceiverObject = {
foo: 4,
bar: 4,
}; Reflect.get(myObject, 'baz', myReceiverObject) // 8

2. Reflect.set(target, name, value, receiver)

Reflect.set方法设置target对象的name属性等于value:

var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
} myObject.foo // 1 Reflect.set(myObject, 'foo', 2);
myObject.foo // 2 Reflect.set(myObject, 'bar', 3)
myObject.foo // 3

3. Reflect.has(obj, name)

Reflect.has方法对应name in obj里面的in运算符:

var myObject = {
foo: 1,
}; // 旧写法
'foo' in myObject // true // 新写法
Reflect.has(myObject, 'foo') // true
// 如果第一个参数不是对象,Reflect.has和in运算符都会报错。

4. Reflect.deleteProperty(obj, name)

Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性:

const myObj = { foo: 'bar' };

// 旧写法
delete myObj.foo; // 新写法
Reflect.deleteProperty(myObj, 'foo'); //该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回true;删除失败,被删除的属性依然存在,返回false。

5. Reflect.construct(target, args)

Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法:

function Greeting(name) {
this.name = name;
} // new 的写法
const instance = new Greeting('张三'); // Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);

6. Reflect.getPrototypeOf(obj)

Reflect.getPrototypeOf方法用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj):

const myObj = new FancyThing();

// 旧写法
Object.getPrototypeOf(myObj) === FancyThing.prototype; // 新写法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;

Reflect.getPrototypeOf和Object.getPrototypeOf的一个区别是,如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错。

Object.getPrototypeOf(1)  // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // 报错

7. Reflect.setPrototypeOf(obj, newProto)

Reflect.setPrototypeOf方法用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表示是否设置成功:

const myObj = {};

// 旧写法
Object.setPrototypeOf(myObj, Array.prototype); // 新写法
Reflect.setPrototypeOf(myObj, Array.prototype); myObj.length // 0

8. Reflect.apply(func, thisArg, args)

Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数:

const ages = [11, 33, 12, 54, 18, 96];

// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest); // 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);

9. Reflect.defineProperty(target, propertyKey, attributes)

Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,所以最好现在就开始使用Reflect.defineProperty代替它:

function MyDate() {
/*…*/
} // 旧写法
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
}); // 新写法
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});

10. Reflect.getOwnPropertyDescriptor(target, propertyKey)

Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者:

var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
}); // 旧写法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden'); // 新写法
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');

11. Reflect.isExtensible(target)

Reflect.isExtensible方法对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展:

const myObject = {};

// 旧写法
Object.isExtensible(myObject) // true // 新写法
Reflect.isExtensible(myObject) // true

12. Reflect.preventExtensions(target)

Reflect.preventExtensions对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功:

var myObject = {};

// 旧写法
Object.preventExtensions(myObject) // Object {} // 新写法
Reflect.preventExtensions(myObject) // true

13. Reflect.ownKeys(target)

Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和:

var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
}; // 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar'] Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)] // 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]

以上静态方法,如果传入的不是对象,基本都会报错

最后

好了,本篇就到这里,主要都是摘抄常用的知识点和备注自己的理解,希望对你有所帮助,后面会持续更新,也感谢你能看到这里!

GitHub传送门

博客园传送门

ES6读书笔记(二)

上一篇:python自动化运维常用模块安装


下一篇:Hive| 压缩| 存储| 调优