《ECMAScript 6 入门》读书笔记(二)

说实话,ES 6 新特性真的不少。感觉全部记下来都很难,更别谈消化了。继续啃《ECMAScript 6 入门》。希望能早日啃完,想去看 Vue.js 了…

此博文包括:函数的扩展、对象的扩展、Symbol、Set 和 Map 数据结构。

函数的扩展

函数参数的默认值

1.ES 6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function log(x, y = 'World'){
console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'Kyon') // Hello Kyon
log('Hello', '') // Hello

// 与解构赋值默认值结合使用
function foo({x, y = 5}){
console.log(x, y);
}

foo({x: 1}) // 1, 5
foo() // TypeeError: Cannot read property 'x' of undefined

// 函数参数的默认值是空对象,但是设置了对象解构赋值的默认值
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}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x和y都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

参数变量是默认声明的,所以不能用letconst再次声明;使用参数默认值时,函数不能有同名参数。

定义了默认值的参数通常应该为函数的尾参数。

2.函数的length属性:将返回没有指定默认值的参数个数。如果设置了默认值的参数不是尾参数,后面的参数也不会被计入。

3.作用域:一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,作用域消失。

1
2
3
4
5
6
7
8
9
10
// 函数foo内部声明的内部变量与参数不是同一个作用域
var x = 1;
function foo(x, y = function(){ x = 2; }){
var x = 3;
y();
console.log(x);
}

foo() // 3
x // 1
1
2
3
4
5
6
7
8
9
var x = 1;
function foo(x, y = function(){ x = 2; }){
x = 3;
y();
console.log(x);
}

foo() // 2
x // 1

4.函数参数默认值的应用:可以指定某一个参数不得省略,如果省略就抛出一个错误。

1
2
3
4
5
6
7
8
9
10
11
12
function throwIfMissing(){
throw new Error('Missing parameter');
}

function foo(mustBeprovided = throwIfMissing()){
return mustBeProvided;
}
// throwIfMissing函数名之后有一对圆括号
// 表明参数的默认值不是在定义时执行,而是在运行时执行
// 即如果参数已经赋值,默认值中的函数就不会运行

foo() // Error: Missing parameter

可以将参数默认值设为undefined,表明这个参数是可以省略的。

5.rest 参数:形式为”…变量名”,用于获取函数的多余参数。rest 参数中的变量代表一个数组。rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

1
const sortNumbers = (...numbers) => numbers.sort();

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

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

函数参数的默认值

6.扩展运算符:...,将一个数组转为用逗号分隔的参数序列。

1
2
3
4
5
6
7
8
9
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

var numbers = [4, 35];
add(...numbers) // 39

// 不再需要 apply 方法将数组转为函数参数
function f(x, y, z){}
f(...args);

7.扩展运算符的应用:

  • 合并数组:提供了新写法。
1
2
3
4
5
6
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

arr1.concat(arr2, arr3); // ES 5
[...arr1, ...arr2, ...arr3] // ES 6
  • 与解构赋值结合:用于生成数组(只能放在参数最后一位)。
1
2
3
4
5
6
7
8
9
10
11
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest // []:

const [first, ...rest] = ["foo"];
first // "foo"
rest // []
  • 函数的返回值:为函数提供一种返回多个值的方法。
1
2
3
4
// 从数据库取出一行数据,通过扩展运算符,直接传入构造函数Date

var dateFields = readDateFields(database);
var d = newDate(...dateFields);
  • 字符串:扩展运算符还可以将字符串转为真正的数组。
1
2
3
4
5
6
7
let str = 'x\uD83D\uDE80y';

str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'

[...str].reverse().join('')
// 'y\uD83D\uDE80x'
  • 实现了 Iterator 接口的对象:可以通过扩展运算符转为真正的数组。
1
2
var nodeList = document.querySelectorAll('div');
var array = [...nodeList];
  • Map 和 Set 结构,Generator 函数:扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符。Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Map结构
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

// Generator 函数
var go = function*(){
yield 1;
yield 2;
yield 3;
};

[...go()] // [1, 2, 3]

8.函数内部严格模式:ES 5 时函数内部可以设定为严格模式;ES 6 规定只要函数参数使用了默认值、解构赋值或者扩展运算符,则函数内部不能显式设定为严格模式,否则报错。

原因:只有从函数体代码之中,才能知道参数代码是否应该以严格模式执行,但是参数代码却应该先于函数体代码执行。

两种规避方法:全局性严格模式,将函数包在一个无参数的立即执行函数里。

9.name 属性:返回该函数的函数名。

如果将一个匿名函数赋值给一个变量,ES5 的 name 属性,会返回空字符串,而 ES6 的 name 属性会返回实际的函数名。

如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的 name 属性都返回这个具名函数原本的名字。

1
2
3
4
const bar = function baz(){};

// ES 5、ES 6
bar.name // "baz"

Function构造函数返回的函数实例,name 属性的值为 anonymous

1
(new Function).name // "anonymous"

bind返回的函数,name属性值会加上bound前缀。

1
2
3
4
function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

箭头函数

10.如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 箭头函数与变量解构结合使用
const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person){
return person.first + ' ' + person.last;
}


// 简化回调函数
[1, 2, 3].map(x => x * x);

// 等同于
[1, 2, 3].map(function(x){
return x * x;
});

11.箭头函数使用注意点:

  • 函数体内的this对象就是定义时所在对象,而不是使用时所在对象。
  • 不可当作构造函数(即不可使用new命令),否则抛出错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

this指向的固定化,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。也正因如此,箭头函数不能用作构造函数,也不能用call()apply()bind()这些方法去改变this的指向。

除了thisargumentssupernew.target在箭头函数中也是不存在的,指向外层函数的对应变量。

12.嵌套的箭头函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ES 5
function insert(value) {
return {into: function(array) {
return {after: function(afterValue) {
array.splice(array.indexOf(afterValue)+1, 0, value);
return array;
}};
}};
}

insert(2).into([1, 3]).after(1); // [1, 2, 3]

// ES 6
let insert = (value) => ({into: (array) => ({after: (afterValue) => {
array.splice(array.indexOf(afterValue)+1, 0, value);
return array;
}})});

insert(2).into([1, 3]).after(1); // [1, 2, 3]

13.函数绑定运算符:::,左边为对象,右边为函数。该运算符自动将左边的对象作为上下文环境(即 this 对象),绑定到右边的函数上。用来取代callapplybind调用。

1
2
3
4
5
6
7
foo::bar;
// 等同于
bar.bind(foo);

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

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

1
2
3
4
5
6
7
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

var log = ::console.log;
// 等同于
var log = console.log.bind(console);

由于双冒号运算符返回的还是原对象,因此可以采用链式写法。

1
2
3
4
5
let { find, html } = jake;

document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha");

该语法为已被 Babel 转码器支持的一个 ES 7 提案。

14.尾调用:某个函数的最后一步是调用另一个函数。

To be continued…

对象的扩展

1.属性的简洁表示法:ES 6 允许直接写入变量和函数,作为对象的属性和方法。这时,属性名为变量名,属性值为变量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}

// 等同于
var baz = {foo: foo};


var o = {
method(){
return "Hello!";
}
};

// 等同于
var o = {
method: function(){
return "Hello!";
}
};

如果某个方法的值是一个 Generator 函数,前面需要加上星号。

1
2
3
4
5
var obj = {
* m(){
yield 'hello world';
}
};

2.属性名表达式:ES 6 允许字面量定义对象时,把表达式放在方括号内。表达式也可用于定义方法名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let propKey = 'foo';

let obj = {
[propKey]: true,
['a' + 'bc']: 123
};

// 表达式用于定义方法名
let obj = {
['h' + 'ello']() {
return 'hi';
}
};

obj.hello() // hi

属性名表达式与简洁表达式不能同时使用。

1
2
3
4
5
6
7
8
// 报错
var foo = 'bar';
var bar = 'abc';
var baz = { [foo] };

// 正确
var foo = 'bar';
var baz = { [foo]: 'abc'};

属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object](即不要这么用)。

3.方法的 name 属性:返回函数名。

如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的getset属性上面,返回值是方法名前加上getset

有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous

如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。

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

5.Object.assign():用于对象的合并,将源对象的所有可枚举属性复制到目标对象。第一个参数是目标对象,其他参数是源对象。

1
2
3
4
5
6
7
var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

如果多个对象有同名属性,后面的属性会覆盖前面的。

如果只有一个参数将直接返回(参数不是对象会先转成对象)。无法转成对象的源对象将被跳过。undefinednull无法转成对象,所以不能作为目标对象(否则报错)。其他相关注意事项见文档。

实行浅拷贝,即源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。对于嵌套对象,一旦遇到同名属性会被替换。

1
2
3
4
var target = { a: { b: 'c', d: 'e'} };
var source = { a: { b: 'hello' } };

Object.assign(target, source); // {a: { b: 'hello' }}

Object.assign()有很多用处,其中包括为对象添加属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}


// 添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2){ ... },
anotherMethod(){ ... }
});

// 等同于
SomeClass.prototype.someMethod = function(arg1, arg2) { ... };
SomeClass.prototype.anotherMethod = function(){ ... };

属性的可枚举性与遍历

6.Object.getOwnPropertyDescriptor:对象的每个属性都有一个描述对象,用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获得该属性的描述对象。

1
2
3
4
5
6
7
8
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo');
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }

ES 7 引入Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const obj = {
foo: 123,
get bar() { return 'abc'; }
};

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

7.属性的可枚举性:描述对象的enumerable属性,成为“可枚举性”。ES 5 有三个操作会忽略enumerablefalse的属性:

  • for...in循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys():返回对象自身的所有可枚举的属性的键名。
  • JSON.stringify():只串行化对象自身的可枚举的属性。

ES 6 新增Object.assign(),会忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。

当只关心对象自身的属性时,尽量不要用for...in循环,而用Object.keys()代替。

8.Object.keys():返回一个数组,包括对象自身的(不含继承的)所有可枚举属性的键名。ES 2017 引入配套的Object.valuesObject.entries作为遍历一个对象的补充手段。

Object.values():返回一个数组,包括对象自身的(不含继承的)所有可枚举属性的键值(不含 Symbol 属性)。参数为字符串时返回各个字符组成的一个数组,参数为数值或布尔值时返回空数组(与包装对象有关)。

Object.entries():返回一个数组,包括对象自身的(不含继承的)所有可枚举属性的键值对数组(不含 Symbol 属性)。

9.属性的遍历:ES 6 共有5种遍历对象的方法。

  1. for...in:循环遍历对象自身的继承的可枚举属性(不含 Symbol 属性)。
  2. Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。
  3. Object.getOwnPropertyNames(obj):返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但包括不可枚举属性)。
  4. Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 属性
  5. Reflect.ownKeys(obj):返回一个数组,包含对象自身的所有属性(无论是否可枚举或者属性名是 Symbol 还是字符串)。

以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则(之前一些遍历的方法也遵循此规则)。

  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。

__proto__属性及相关方法

10.__proto__属性:用于读取或设置当前对象的prototype对象。内部属性,不是正式对外 API。建议使用Object.setPrototypeOf()(写操作)、Object.getPrototype()(读操作)、Object.create()(生成操作)代替。

如果一个对象本身部署了__proto__属性,则该属性的值就是对象的原型。

11.Object.setPrototypeOf():作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身。

1
2
3
4
5
// 格式
Object.setPrototypeOf(object, prototype)

// 用法
var o = Object.setPrototypeOf({}, null);

如果第一个参数不是对象,会自动转为对象(由于返回的还是第一个参数,故无效果);是undefinednull则报错(无法转为对象)。

12.Object.getPrototypeOf():用于读取一个对象的原型对象。

1
Object.getPrototypeOf(obj);

如果参数不是对象,会被自动转为对象;是undefinednull则报错。

13.对象的扩展运算符:ES 2017 将...引入对象。主要用途有:

  • 解构赋值:
1
2
3
4
let {x, y, ...z} = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

注意解构赋值的拷贝是浅拷贝,且不会拷贝继承自原型对象的属性。

1
2
3
4
5
6
7
8
9
// 扩展某个函数的参数,引入其他操作
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// 使用x和y参数进行操作
// 其余参数传给原始函数
return baseFunction(restConfig);
}
  • 扩展运算符:取出参数对象的所有可遍历属性,拷贝到当前对象之中。等同于Object.assign方法。
1
2
3
4
5
6
7
8
9
10
11
12
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

// 等同于
let n = Object.assign({}, z);


// 用于合并两个对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

如果用户自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。因此可用于修改现有对象部分的部分属性。

1
2
3
4
let newVersion = {
...previousVersion,
name: 'New Name' // Override the name property
};

如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

1
let aWithDefaults = { x: 1, y: 2, ...a };

扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。

14.Null 传导运算符:?.,用于判断对象及其内部属性是否存在。仅为提案,详见文档。

Symbol

1.概述:ES 6 引入的新的原始数据类型,表示独一无二的值。至此,JS 共有七种数据类型:

  • Undefined
  • Null
  • 布尔值(Boolean)
  • 字符串(String)
  • 数值(Number)
  • 对象(Object)
  • Symbol

Symbol 值通过Symbol函数生成。即对象的属性名有两种类型:原来就有的字符串,和新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就是独一无二的,不会与其他属性名产生冲突。

1
2
3
4
let s = Symbol();

typeof s
// "symbol"

生成的 Symbol 是一个原始类型的值,不是对象。因此Symbol函数前不能使用new命令(否则报错),Symbol 值也不能添加属性。

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。但相同参数的Symbol函数的返回值是不相等的。

1
2
3
4
5
6
7
8
var s1 = Symbol('foo');
var s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后生成一个 Symbol 值。

1
2
3
4
5
6
7
const obj = {
toString(){
return 'kyon';
}
};
const sym = Symbol(obj);
sym // Symbol(abc)

Symbol 值不能与其他类型的值进行运算(会报错),但可以显式转为字符串和布尔值。

2.作为属性名的Symbol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
var a = {
[mySymbol]: 'Hello!'
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello'! });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

Symbol 值作为对象属性名时,不能用点运算符。

1
2
3
4
5
6
var mySymbol = Symbol();
var a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

Symbol 值作为属性名时,该属性是公开属性而非私有属性。

3.魔术字符串:在代码中多次出现、与代码形成强耦合的某一个具体的字符串或数值。应尽量消除而改用含义清晰的变量代替。

Symbol可用于消除魔术字符串。

4.属性名的遍历:Symbol 作为属性名,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。这个特性可被用于为对象定义一些非私有的、但又希望只用于内部的方法。

Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

Reflect.ownKeys(obj)方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

1
2
3
4
5
6
7
8
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};

Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]

5.Symbol.for()Symbol.keyFor():接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

1
2
3
4
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');

s1 === s2; // true

Symbol.for()Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

1
2
3
4
5
Symbol.for("bar") === Symbol.for("bar")
// true

Symbol("bar") === Symbol("bar")
// false

Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key

1
2
3
4
5
var s1 = Symbol.for("foo");
Symbol.keyFor(s1); // "foo"

var s2 = Symbol("foo")
Symbol.keyFor(s2); // undefined

6.Singleton 模式:指调用一个类,任何时候返回的都是同一个实例。

Node 中,模块文件可以看作一个类。可以使用 Symbol,通过把实例放到顶层对象global来实现 Singleton 模式。

内置的 Symbol 值(暂且略过)

7.Symbol.hasInstance属性:指向一个内部方法。

8.Symbol.isConcatSpreadable属性:

9.Symbol.species

10.Symbol.match

11.Symbol.replace

12.Symbol.search

13.Symbol.split

14.Symbol.iterator

15.Symbol.toPrimitive

16.Symbol.toStringTag

17.Symbol.unscopables

Set 和 Map 数据结构

Set

1.基本用法:Set 结构不会添加重复的值。

1
2
3
4
5
6
7
8
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

Set 函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。因此有一种去除数组重复成员的方法:

1
[...new Set(array)]

Set 内部判断两个值是否不同,使用的算法叫做“Same-value equality”,类似精确相等运算符(===),主要的区别是NaN等于自身(===认为NaN不等于自身)。另外,两个对象总是不相等的。

2.Set 实例的属性和方法:

实例属性:

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

操作方法:

  • add(value):添加某个值,返回 Set 结构本身。
  • delete(value):删除某个值,返回一个表示删除是否成功的布尔值。
  • has(value):返回一个布尔值。
  • clear():清除所有成员,没有返回值。

遍历方法(Set 结构中,键名和键值是同一个值):

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

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

WeakSet

3.含义:与 Set 类似,但成员只能是对象,且都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用。如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

ES 6 规定,WeakSet 不可遍历。

4.语法:有adddeletehas方法;没有size属性。

5.用处:是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

Map

6.含义和基本用法:为了解决 Object 的只能用字符串当作键名的问题,ES 6 提供了 Map 数据结构。各种类型的值(包括对象)都可以当作键。

set(key, value)get(key)has(key)delete(key)clear()方法。

作为构造函数,Map 可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组

如果对同一个键多次赋值,后面的值将覆盖前面的值;如果读取一个未知的键,则返回undefined

7.与其他数据结构的互相转换:

  • Map 转为数组:使用扩展运算符(...)。
  • 数组转为 Map:使用 Map 构造函数。
  • Map 转为对象:如果所有 Map 的键都是字符串,可以转为对象。
1
2
3
4
5
6
7
8
9
10
11
function strMapToObj(strMap){
let obj = Object.create(null);
for(let [k, v] of strMap){
obj[k] = v;
}
return obj;
}

const myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap);
// { yes: true, no: false }
  • 对象转为 Map:
1
2
3
4
5
6
7
function objToStrMap(obj){
let strMap = new Map();
for(let k of Object.keys(obj)){
strMap.set(k, obj[k]);
}
return strMap;
}
  • Map 转为 JSON:
1
2
3
4
5
// 情况一:Map 的键名都是字符串
// 可以转换为对象 JSON
function strMapToJson(strMap){
return JSON.stringify(strMapToObj(strMap));
}
1
2
3
4
5
// 情况二:Map 的键名有非字符串
// 可以转换为数组 JSON
function mapToArrayJSON(map){
return JSON.stringify([...map]);
}
  • JSON 转为 Map:逆操作。

WeakMap

8.含义:与 Map 类似,用于生成键值对。但只接受对象作为键名(null除外),且键名所指向的对象不计入垃圾回收机制。

专用场合:它的键所对应的对象可能会在将来消失。WeakMap结构有助于防止内存泄漏。

9.语法:没有遍历操作,无法清空。只有四个方法可用:get()set()has()delete()

10.用处:DOM 节点作为键名。

1
2
3
4
5
6
7
8
9
10
11
12
// 一个例子
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function(){
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);

// 一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险

注册监听事件的listener对象,就很适合用 WeakMap 实现。

1
2
3
4
5
6
7
8
9
const listener = new WeakMap();

listener.set(element1, handler1);
listener.set(element2, handler2);

element1.addEventListener('click', listener.get(element1), false);
element2.addEventListener('click', listener.get(element2), false);

// 一旦DOM对象消失,跟它绑定的监听函数也会自动消失

WeakMap 的另一个用处是部署私有属性。

Proxy

暂时跳过。

Reflect

暂时跳过。