diff --git a/docs/iterator.md b/docs/iterator.md index c9536a3..15741d6 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -12,13 +12,13 @@ Iterator的遍历过程是这样的。 (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。 -(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。 +(2)第一次调用指针对象的`next`方法,可以将指针指向数据结构的第一个成员。 -(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。 +(3)第二次调用指针对象的`next`方法,指针就指向数据结构的第二个成员。 -(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。 +(4)不断调用指针对象的`next`方法,直到它指向数据结构的结束位置。 -每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。 +每一次调用`next`方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含`value`和`done`两个属性的对象。其中,`value`属性是当前成员的值,`done`属性是一个布尔值,表示遍历是否结束。 下面是一个模拟next方法返回值的例子。 @@ -41,13 +41,28 @@ function makeIterator(array){ } ``` -上面代码定义了一个makeIterator函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。对数组`['a', 'b']`执行这个函数,就会返回该数组的遍历器对象(即指针对象)it。 +上面代码定义了一个`makeIterator`函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。对数组`['a', 'b']`执行这个函数,就会返回该数组的遍历器对象(即指针对象)`it`。 -指针对象的next方法,用来移动指针。开始时,指针指向数组的开始位置。然后,每次调用next方法,指针就会指向数组的下一个成员。第一次调用,指向a;第二次调用,指向b。 +指针对象的`next`方法,用来移动指针。开始时,指针指向数组的开始位置。然后,每次调用`next`方法,指针就会指向数组的下一个成员。第一次调用,指向`a`;第二次调用,指向`b`。 -next方法返回一个对象,表示当前数据成员的信息。这个对象具有value和done两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用next方法。 +`next`方法返回一个对象,表示当前数据成员的信息。这个对象具有`value`和`done`两个属性,`value`属性返回当前位置的成员,`done`属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用`next`方法。 -总之,调用指针对象的next方法,就可以遍历事先给定的数据结构。 +总之,调用指针对象的`next`方法,就可以遍历事先给定的数据结构。 + +对于遍历器对象来说,`done: false`和`value: undefined`属性都是可以省略的,因此上面的`makeIterator`函数可以简写成下面的形式。 + +```javascript +function makeIterator(array){ + var nextIndex = 0; + return { + next: function(){ + return nextIndex < array.length ? + {value: array[nextIndex++]} : + {done: true}; + } + } +} +``` 由于Iterator只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。下面是一个无限运行的遍历器对象的例子。 @@ -70,9 +85,9 @@ function idMaker(){ } ``` -上面的例子中,遍历器生成函数idMaker,返回一个遍历器对象(即指针对象)。但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来。 +上面的例子中,遍历器生成函数`idMaker`,返回一个遍历器对象(即指针对象)。但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来。 -在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性(详见下文),另外一些数据结构没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。 +在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被`for...of`循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了`Symbol.iterator`属性(详见下文),另外一些数据结构没有。凡是部署了`Symbol.iterator`属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。 如果使用TypeScript的写法,遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下。 diff --git a/docs/set-map.md b/docs/set-map.md index bf159d8..e573d07 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -785,7 +785,7 @@ map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key ``` -上面代码中,如果将1和`Symbol`作为WeakMap的键名,都会报错。 +上面代码中,如果将`1`和`Symbol`作为WeakMap的键名,都会报错。 `WeakMap`的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,`WeakMap`自动移除对应的键值对。典型应用是,一个对应DOM元素的`WeakMap`结构,当某个DOM元素被清除,其所对应的`WeakMap`记录就会自动被移除。基本上,`WeakMap`的专用场合就是,它的键所对应的对象,可能会在将来消失。`WeakMap`结构有助于防止内存泄漏。 @@ -832,7 +832,7 @@ myElement.addEventListener('click', function() { }, false); ``` -上面代码中,myElement是一个DOM节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在WeakMap里,对应的键名就是myElement。一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险。 +上面代码中,`myElement`是一个DOM节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在WeakMap里,对应的键名就是`myElement`。一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险。 WeakMap的另一个用处是部署私有属性。 @@ -863,4 +863,4 @@ c.dec() // DONE ``` -上面代码中,Countdown类的两个内部属性_counter和_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 +上面代码中,Countdown类的两个内部属性`_counter`和`_action`,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 diff --git a/docs/symbol.md b/docs/symbol.md index 61a0fd1..46355c8 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -2,7 +2,7 @@ ## 概述 -ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 +ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 @@ -72,6 +72,21 @@ String(sym) // 'Symbol(My symbol)' sym.toString() // 'Symbol(My symbol)' ``` +另外,Symbol值也可以转为布尔值,但是不能转为数值。 + +```javascript +var sym = Symbol(); +Boolean(sym) // true +!sym // false + +if (sym) { + // ... +} + +Number(sym) // TypeError +sym + 2 // TypeError +``` + ## 作为属性名的Symbol 由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。 @@ -145,6 +160,26 @@ log(log.levels.DEBUG, 'debug message'); log(log.levels.INFO, 'info message'); ``` +下面是另外一个例子。 + +```javascript +const COLOR_RED = Symbol(); +const COLOR_GREEN = Symbol(); + +function getComplement(color) { + switch (color) { + case COLOR_RED: + return COLOR_GREEN; + case COLOR_GREEN: + return COLOR_RED; + default: + throw new Error('Undefined color'); + } +} +``` + +常量使用Symbol值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证上面的`switch`语句会按设计的方式工作。 + 还有一点需要注意,Symbol值作为属性名时,该属性还是公开属性,不是私有属性。 ## 属性名的遍历