# 可迭代协议和迭代器协议
ES2015补充规范:可迭代协议 (opens new window)和迭代器协议 (opens new window)
# 可迭代协议
允许JavaScript对象定义或定制它们的迭代行为,例如,在一个for...of结构中,哪些值可以被遍历到:
内置可迭代对象:String,Array,TypedArray,Map和Set,Object是不可迭代的,因此加入了Map来替代
要成为可迭代对象,一个对象必须实现@@iterator方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为@@iterator的属性,可通过常量Symbol.iterator访问该属性,返回值是一个无参数的函数,其返回值为一个符合迭代器协议的对象
String.prototype[Symbol.iterator]; // ƒ [Symbol.iterator]() { [native code] }
Array.prototype[Symbol.iterator]; // ƒ values() { [native code] }
Map.prototype[Symbol.iterator]; // ƒ entries() { [native code] }
Set.prototype[Symbol.iterator]; // ƒ values() { [native code] }
Object.prototype[Symbol.iterator]; // undefined
2
3
4
5
# 迭代器协议
定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值
只有实现了一个拥有以下语义(semantic)的next()方法,一个对象才能成为迭代器,next()是一个无参数函数,返回一个应当拥有以下两个属性的对象:
done(boolean)
- 如果迭代器可以产生序列中的下一个值,则为
false(这等价于没有指定done这个属性) - 如果迭代器已将序列迭代完毕,则为
true,这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
- 如果迭代器可以产生序列中的下一个值,则为
value
- 迭代器返回的任何JavaScript值。
done为true时可省略。
next()方法必须返回一个对象,该对象应当有两个属性:done和value,如果返回了一个非对象值(比如false或undefined),则会抛出一个TypeError异常("iterator.next() returned a non-object value")- 迭代器返回的任何JavaScript值。
迭代协议的使用:
const iterator = ('hi')[Symbol.iterator]();
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
// 重写可迭代协议的迭代行为
// 必须构造 String 对象以避免字符串字面量 auto-boxing
const someString = new String('hi');
someString[Symbol.iterator] = function () {
return { // 只返回一次元素, 字符串 "bye", 的迭代器对象
next: function() {
if (this.first) {
this.first = false;
return { value: 'bye', done: false };
}
return { done: true };
},
first: true,
};
};
[...someString]; // ['bye']
// 如果使用 next() 则会输出 {value: 'bye', done: false} {done: true}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
接受可迭代对象的内置API
- new Map([iterable]) (opens new window)
- new WeakMap([iterable]) (opens new window)
- new Set([iterable]) (opens new window)
- new WeakSet([iterable]) (opens new window)
- Promise.all(iterable) (opens new window)
- Promise.race(iterable) (opens new window)
- Array.from(iterable) (opens new window)
new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2); // "b"
const myObj = {};
new WeakMap([
[{}, 'a'],
[myObj, 'b'],
[{}, 'c'],
]).get(myObj); // "b"
new Set([1, 2, 3]).has(3); // true
new Set('123').has('2'); // true
new WeakSet(function* () {
yield {};
yield myObj;
yield {};
}()).has(myObj); // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Map,WeakMap,Set,WeakSet的区别
Map、Set为强引用,键、值为任意类型。不能进行垃圾回收,均可使用forEach、for...of遍历Map是用数组来存取键和值,所以在设置值和取值的时候的时间复杂度都是O(n)WeakMap、WeakSet为弱引用,可以进行垃圾回收。WeakMap键必须为对象,WeakSet值必须为对象WeakMap由于是弱引用,所以无法枚举所有的key,因此是无法进行遍历的。WeakSet同理不能遍历
迭代器实例
function makeIterator(array) {
let nextIndex = 0;
return {
next() {
return nextIndex < array.length ? {
value: array[nextIndex++],
done: false,
} : {
done: true,
};
},
};
}
const it = makeIterator(['哟', '呀']);
console.log(it.next().value); // '哟'
console.log(it.next().value); // '呀'
console.log(it.next().done); // true
// 使用生成器
function* makeSimpleGenerator(array) {
let nextIndex = 0;
while (nextIndex < array.length) {
yield array[nextIndex++];
}
}
const gen = makeSimpleGenerator(['哟', '呀']);
console.log(gen.next().value); // '哟'
console.log(gen.next().value); // '呀'
console.log(gen.next().done); // true
// 生成器有next方法, 所以是一个迭代器, 并且有 [Symbol.iterator] 返回一个函数 所以是一个可迭代对象
// 所以生成器对象既是迭代器, 也是可迭代对象
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
# 生成器与yield
function*这种声明方式(function关键字后跟一个星号)会定义一个生成器函数(generator function),它返回一个Generator对象。也可以使用构造函数GeneratorFunction或function*expression定义生成器函数。
生成器函数不能当构造器使用
function* name([param[, param[, ... param]]]) { statements }
// function*
function* generator(i) {
yield i;
yield i + 10;
}
const gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 20
console.log(gen.next().value); // undefined 因为只会返回两个值 第三次 done为true
// GeneratorFunction 并不是全局对象
Object.getPrototypeOf(function*(){}).constructor
new GeneratorFunction ([arg1[, arg2[, ...argN]],] functionBody)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
生成器函数在执行时能暂停,后面又能从暂停处继续执行。调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器(iterator)对象。当这个迭代器的next()方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield后紧跟迭代器要返回的值。或者如果用的是yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
next()方法返回一个对象,这个对象包含两个属性:value和done,value属性表示本次yield表达式的返回值,done属性为布尔类型,表示生成器后续是否还有yield语句,即生成器函数是否已经执行完毕并返回。
调用next()方法时,如果传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量
function* gen() {
yield 10;
x = yield 'foo';
yield x;
}
const genObj = gen();
console.log(genObj.next()); // 执行 yield 10, 返回 10
console.log(genObj.next()); // 执行 yield 'foo', 返回 'foo'
console.log(genObj.next(100)); // 将 100 赋给上一条 yield 'foo' 的左值, 即执行 x=100, 返回 100
console.log(genObj.next()); // 执行完毕, value 为 undefined, done 为 true
2
3
4
5
6
7
8
9
10
当在生成器函数中显式return时,会导致生成器立即变为完成状态,即调用next()方法返回的对象的done为true。如果return后面跟了一个值,那么这个值会作为当前调用next()方法返回的value值。
// yield* 转移
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i) {
yield i;
yield* anotherGenerator(i); // 移交执行权
yield i + 10;
}
const gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
深入理解js中的yield (opens new window)
yield并不能直接生产值,而是产生一个等待输出的函数- 除IE外,其他所有浏览器均可兼容(包括win10的Edge)
- 某个函数包含了
yield,意味着这个函数已经是一个Generator - 如果
yield在其他表达式中,需要用()单独括起来 yield表达式本身没有返回值,或者说总是返回undefined(由next返回)。因此在赋值时,变量的值始终来自于下一次的next()调用参数next()可无限调用,但既定循环完成之后总是返回undefined,除非有显示的return,则返回return后跟的值