迭代协议(Iteration protocols)并不是 ECMAScript 2015新增项中的 内置功能 或 语法,而是协议。这些协议可以由遵守约定的任何对象来实现。
迭代协议有两种协议:可迭代协议(iterable protocol) 和 迭代器协议(iterator protocol)
可迭代协议(iterable protocol)
可迭代协议允许JavaScript对象定义或自定义其迭代行为,例如:在一个 for..of
结构中定义什么值可以被循环(得到)。一些内置类型都是内置的可迭代类型并且有默认的迭代行为, 比如: Array
和 Map
,有些类型则不是, 比如Object
。
为了变成可迭代对象, 对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链上的某个对象)必须有一个名字是 Symbol.iterator 的属性,这个属性可通过Symbol.iterator
访问到:
属性 | 值 |
---|---|
[Symbol.iterator] | 返回一个无参数的函数,这个函数的返回值是一个符合迭代器协议的对象。 |
当一个对象需要被迭代的时候(例如:在for..of
循环的开始),都将不带任何参数地调用其@@ iterator
方法,循环中返回的迭代器用来获取要迭代的值。
迭代器协议(iterator protocol)
迭代器协议定义了一种标准的方法来生成一个值序列(有限值或无限值),并且当所有的值都已经被迭代后,就会有一个默认的返回值。
当对象使用以下语义实现 next()
方法时,它就是一个迭代器:
next()
方法没有参数,它的返回值是一个对象,这个对象至少包含 [done] 和 [value] 两个属性。
- [done] 属性(
boolean
类型):当迭代器超过了迭代序列,返回true
,此时value
的值可以被省略;当迭代器能够继续产生序列中的下一个值,则值为false。 - [value] 属性(
boolean
类型):迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
注意:next()
方法必须要返回一个对象,该对象有两个必要的属性:done
和 value
。如果返回一个非对象值,就会抛出TypeError (“iterator.next() returned a non-object value”) 的错误。
特定对象是否实现了 迭代器协议 我们或许不清楚,然而,我们自己可以轻松地创建同时满足 迭代器协议 和 可迭代协议 的对象(比如下面的例子)。这样做可以使迭代器可以被 各种用来迭代的语法 所使用。因此,很少希望在没有实现 可迭代的 情况下实现 迭代器协议。
//同时满足 迭代器协议 和 可迭代协议 的对象
var myIterator = {
//迭代器协议
next: function() {
// ...
},
//可迭代协议
[Symbol.iterator]: function() { return this }
};
使用 迭代协议(iteration protocols)的例子:
String
是一个内置的可迭代对象:
var someString = "hi";
console.log(someString[Symbol.iterator]); // [Function: [Symbol.iterator]]
console.log(typeof someString[Symbol.iterator]); // function
console.log(someString[Symbol.iterator]()); // Object [String Iterator] {}
console.log(someString[Symbol.iterator]() + ''); // [object String Iterator]
String
的默认迭代器会一个接一个返回该字符串的字符:
var iterator = someString[Symbol.iterator]();
iterator + ""; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
一些内置的语法结构,比如 spread operator (展开语法:[…val]),内部也使用了迭代协议:
console.log([...someString]) // ["h", "i"]
我们可以通过自己的 @@iterator 方法重新定义迭代行为:
var someString = new String("hi"); // need to construct a String object explicitly to avoid auto-boxing
someString[Symbol.iterator] = function() {
return { // this is the iterator object, returning a single element, the string "bye"
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
console.log([...someString]); // ["bye"]
console.log(someString + ''); // "hi"
var iterator = someString[Symbol.iterator]();
console.log(iterator.next()); // { value: 'bye', done: false }
console.log(iterator.next()); // { done: true }
console.log(iterator.next()); //{ done: true }
如果内置语法结构实现了迭代协议(Iteration protocols) ,但我们又重新用 迭代协议(Iteration protocols) 定义 了@@iterator 方法,此时内置语法结构中的迭代方式就会被改变。
可迭代对象例子
-
内置可迭代对象:
String, Array, TypedArray, Map
和Set
是所有内置可迭代对象, 因为它们的原型对象都实现了@@iterator
方法。 -
自定义可迭代对象:
我们自己可以实现一个可迭代对象,就像这样:
var myIterator = {};
myIterator[Symbol.iterator] = function *(){
yield 'happy';
yield 'chen';
yield 666;
}
console.log([...myIterator]); // [ 'happy', 'chen', 666 ]
内置API接受可迭代性
有许多接受迭代的API,例如: Map([iterable])
, WeakMap([iterable])
,Set([iterable])
,WeakSet([iterable])
,Promise.all(iterable)
,Promise.race(iterable)
和 Array.from()
。
有些语法也可迭代对象
有些语句和表达式也可以迭代,例如 for-of
循环、扩展语法、yield*
语句 和 析构赋值:
for (const item of ['菠萝头','可达鸭','吉尼龟']) {
console.log(item);
}
// 菠萝头
// 可达鸭
// 吉尼龟
console.log([...'happychen']); // [ 'h', 'a', 'p', 'p', 'y', 'c', 'h', 'e', 'n' ]
function* gen(){
yield* ['菠萝头','可达鸭','吉尼龟'];
}
console.log(gen().next()); //{ value: '菠萝头', done: false }
格式化不好的迭代器
如果一个迭代器的 @@iterator
方法没有返回迭代器对象,那么它是格式化不好的迭代器。使用它可能会导致运行时异常或古怪的行为:
var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function