Iterator概念
当需要对一个对象进行迭代时(比如开始用于一个for..of循环中),它的@@iterator方法都会在不传参情况下被调用,返回的迭代器用于获取要迭代的值(官方)
- Iterator 的作用有三个
1.是为各种数据结构,提供一个统一的、简便的访问接口
2.是使得数据结构的成员能够按某种次序排列
3.是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要是为了for...of使用。
当我们打印一个数组,打印一个对象,会发现我们数组以及对象的原型中有一个属性比较特殊。
-
对象的原型:
image.png -
数组的原型:
image.png
我们会发现,数组原型中有一个属性Symbol(Symbol.iterator),而对象中没有这个属性,那么这个属性主要作用是什么呢,我们前面说到,Iterator主要是为了for...of...进行使用的,所以我们就使用for...of...分别对数组和对象进行使用,发现数组可以遍历出来,但是对象却报错,报错信息为Uncaught TypeError: obj is not iterable,这句话的意思是对象没有迭代器。
迭代器流程
Iterator 的遍历过程是这样的。
1.创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
3.第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
4.不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
迭代器内部模拟
function () {
let index = 0,
len = this.length,
container = this;
return {
next(){
return {
value:container[index],
done:index++ == len ? true : false
}
}
}
}
那么我们把下面的方法加入数组中试试看,会发生什么事情
let arr = [1,2,3,'a','b','c']
arr[Symbol.iterator] = function () {
let index = 0,
len = this.length,
container = this;
return {
next(){
return {
value:container[index],
done:index++ == len ? true : false
}
}
}
}
for(let item of arr){
console.log(item) // 输出 1 2 3 a b c
}
根据上面的代码,我们就模拟了一个数组的迭代器出来,如何看出是模拟出来的,我们假设把next中return出来的对象中的value换成1,不采用container[index],试试看。

发现打印了6次1,而数组里面命名是[1,2,3,'a','b','c'],这里我们就用自己改写方法改变了迭代器的迭代内容。
迭代器函数
- 以上的内容,都只是说明迭代器的功能,以及模拟出来的迭代器,而在ES6规范中,有给到一个方法实现一个迭代器函数,下面,同样用ES6迭代函数的方法进行封装对象,让对象作为可迭代对象,其中代码如下:
let obj = {
0:'2',
1:'33',
2:'44',
length:3
}
obj[Symbol.iterator] = function *() {
if(!this.length){
yield undefined
}else{
for(let i = 0; i < this.length; i++){
yield this[i]
}
}
}
for(let item of obj){
console.log(item)
}
同样实现了上面模拟的相同功能,这时候会发现一个迭代函数是带号实现的,当我们编写一个函数,在function和()中间带一个,我们认为他是一个迭代器,迭代器里有一个yield,每次调用next,会返回yield后面的数据,然后停止执行。(每一次调用它的next方法,会停留在下一个yield的位置。)给一个例子:
function* test(){
yield 1;
yield 2;
yield 3;
}
const test1 = test();
console.log( test1.next() ); //{value: 1, done: false}
console.log( test1.next() ); //{value: 2, done: false}
console.log( test1.next() ); //{value: 2, done: false}
console.log( test1.next() ); //{value: undefined, done: true}
当我们一次一次调用next时候,会不断返回一个对象,其中value代表返回的值,而done则代表当前是否迭代完成。我们会发现,我们在调用next时候,要先执行test,返回一个数据,存在了test1内。我们可以打印test1查看

我们可以看到有一个属性[[GeneratorState]]: "suspended" ( 这个表示为当前的迭代状态,它代表着数据中的done,当我们还没迭代完成时候,状态显示为suspended,当我们迭代完成时候,状态则显示closed )
传参方式
我们的迭代函数同样可以进行传参,当我们使用test1.next()时候,可以传入参数,在我们一开始test()时候,也可以进行传参。我们可以看看传参的结果。
function* test(params){
console.log(params)
yield 1;
console.log(params)
yield 2;
console.log(params)
yield 3;
}
const test1 = test(22);
console.log( test1.next() ); // 22 {value: 1, done: false}
console.log( test1.next() ); // 22 {value: 2, done: false}
console.log( test1.next() ); // 22 {value: 2, done: false}
我们发现,当我们在一开始给他传入一个参数时候,参数会在迭代函数的参数里面拿到我们传进行的实参,并且每一次执行,都不会更改我们传入的参数,不会因为停止执行函数,参数而变成undefined。那我们在next中传入参数呢?
function* test(params){
console.log(params)
const par1 = yield 1;
console.log(par1)
const par2 = yield 2;
console.log(par2)
const par3 = yield 3;
console.log(par3)
}
const test1 = test(22);
console.log( test1.next(1) ); // 22 {value: 1, done: false}
console.log( test1.next(2) ); // 2 {value: 2, done: false}
console.log( test1.next(3) ); // 3 {value: 2, done: false}
我们发现,我们第一次传进入的next(1),传入的1不见了,通过多次传入不同,可以发现,next传进入的参,会返回到下一次yield中的接收值。如next(1)时,首先打印params,因为=是赋值符号,则先看右面的,先运行yield 1,当运行完yield,则函数停止了。当下一次运行next,才会执行par1,然后把参数给到par1,所以par1变成了2。

