作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
可迭代对象(iterable对象) 迭代器对象(Iterator对象)
可迭代协议:(for of循环)
允许对象定义他的可迭代行为,比如在for of结构中,哪些值可以遍历到。在js中的某些类型是内置好的可迭代对象,比如:字符串、数组、类型数组、Map对象、Get对象等。而Object类型不可迭代。这些内置可迭代对象可以进行迭代的原因是内部实现了@@iterator
方法,即在该对象或该对象的原型链上有Symbol.iterator属性实现了@@iterator
方法。
属性:[Symbol.iterator],它的值是:返回一个符合迭代器协议的对象(Iterator对象)的无参数函数。
比如字符串中:原型上有Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
当一个对象需要被迭代的时候(比如被置入一个 for...of
循环时),首先,会调用它的 @@iterator
方法,然后使用此方法返回的迭代器(Iterator对象)获得要迭代的值。
迭代器协议:(Iterator对象)
定义了产生一系列值的标准方式。一个对象必须实现next()方法,才能成为迭代器。
next()
方法必须返回一个对象,该对象应当有两个属性: done
和 value
done
(boolean)如果迭代器可以产生序列中的下一个值,则为 false
。
value
迭代器返回的任何 JavaScript 值
小结:
可迭代对象都有@@iterator
方法,该方法挂载在可迭代对象的[Symbol.iterator]属性上,即[Symbol.iterator]属性指向@@iterator
方法的函数体,所以拥有@@iterator
方法的对象称为iterable对象。
该函数体执行返回一个iterator对象,iterator对象上有next方法,next方法执行返回一个对象,包括value值和done值。
我们可以直接执行[Symbol.iterator],从而得到iterator对象,比如:
let arr = [1,2,3];
let arrIterator = arr[Symbol.iterator]();
console.log(arrIterator.next().value); //1
注意:隐式属性是可以访问的,就像数组的length属性
实例:
以字符串为例
String
是一个内置的可迭代对象:
let someString = "hi";
typeof someString[Symbol.iterator]; // "function"
String
的默认迭代器会依次返回该字符串的字符:
let 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 }
一些内置的语法结构——比如展开语法
——其内部实现也使用了同样的迭代协议:
[...someString] // ["h", "i"]
我们可以重写@@iterator
方法,那么调用该接口的操作都会受到影响
重写了@@iterator
方法,使用for of遍历:
var someString = new String("hi");
someString[Symbol.iterator] = function() {
return { // 只返回一次元素,字符串 "bye",的迭代器对象
index: 0,
str: "bye",
next: function() {
if (this.index < this.str.length) {
return { value: this.str[this.index++], done: false };
} else {
return { done: true };
}
}
};
};
for (let value of someString) {
console.log(value); b y e
}
我们使用展开运算符:
var someString = new String("hi");
someString[Symbol.iterator] = function() {
return { // 只返回一次元素,字符串 "bye",的迭代器对象
index: 0,
str: "bye",
next: function() {
if (this.index < this.str.length) {
return { value: this.str[this.index++], done: false };
} else {
return { done: true };
}
}
};
};
console.log([...someString]); ["b", "y", "e"]
注意重新定义的 @@iterator
方法是如何影响内置语法结构的行为的,我们进行普通取值时:
console.log(someString+""); hi
小结:
for...of 和 ...展开运算符,都是通过找@@interator方法,从而找到iterator对象,对iterator对象进行遍历。
探讨自定义 @@iterator
方法
目前所有的内置可迭代对象如下:String
、Array
、TypedArray
、Map
和 Set
,它们的原型对象都实现了 @@
iterator
方法。而且得到的Iterator对象,都有next方法和@@iterator接口。
普通方法实现:一定要注意,有@@iterator接口的对象,可以进行for of迭代和展开运算符等操作;有next方法的Iterator对象,可以进行next方法的调用,但是该Iterator对象有@@iterator接口,才可以进行for of和展开运算符的操作。
let myIterable = {};
myIterable[Symbol.iterator] = function(){ 有迭代接口的对象
return {
arr: [1,2,3],
index: 0,
next() {
if(this.index < this.arr.length){
return {value: this.arr[this.index++], done: false}
}else {
return {value: "done is true", done: true}
}
}
}
}
console.log([...myIterable]); [1, 2, 3]
let myIter = myIterable[Symbol.iterator](); Iterator对象
myIter.next(); // 1
myIter.next(); // 2
myIter.next(); // 3
console.log(myIter.next().value); done is true
上述代码,顺便探讨了next方法最后的返回值
Generator函数实现:
let myIterable = {};
myIterable[Symbol.iterator] = function* (){
yield 1;
yield 2;
yield 3;
}
console.log([...myIterable]); [1, 2, 3]
let myIter = myIterable[Symbol.iterator]();
console.log(myIter.next().value); 1
接受可迭代对象的内置API
new Map([iterable])
new WeakMap([iterable])
new Set([iterable])
new WeakSet([iterable])
Promise.all(iterable)
Promise.race(iterable)
Array.from(iterable)
for...of
...
都可以调用Iteraable对象,得到每个api自己想要的东西
...展开运算符,将Iterable对象展开
for...of,迭代遍历Iterable对象
new Set() 和 new Map()接收Iterable对象
Array.from()接收Iterable对象,得到数组
Promise.all() 和 Promise.race()
用于可迭代对象的语法:
for(let value of ["a", "b", "c"]){
console.log(value); "a" "b" "c"
}
[..."abc"]; ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];
}
gen().next(); { value: "a", done: false }
[a, b, c] = new Set(["a", "b", "c"]);
a // "a"
生成器对象到底是一个迭代器,还是一个可迭代对象?
生成器
对象既是迭代器,也是可迭代对象:
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
}
let genObj = generator();
console.log(genObj.next().value); 1
for(let value of genObj){
console.log(value); 2 3 4 5
}