细数那些关于迭代器的知识点

457 阅读5分钟

迭代

迭代有着一套属于自己的迭代协议,这规定了迭代与实现的逻辑,它之所以能够工作是因为依靠着迭代器(具体的迭代实现逻辑)、迭代对象(实现了[Symbol.iterator]方法的可被迭代对象)和迭代语句(例如for...in和for..of)

两种迭代语句的效果

  • for...in针对数组的演示案例一:
let arr=['a','b','c','d']
for(var i in arr){
	console.log(i, arr[i])
}

//执行效果为
0 a
1 b
2 c
3 d
  • for...of针对数组的演示案例二:
let arr=['a','b','c','d']
for(var i of arr){
    console.log(i)
}

//执行效果为:
a
b
c
d
  • 由上面两个针对数组案例中可以看出两种语句的差别,我们打印出数组来查看一下数组的特别之处(console.log(arr)),我们可以发现数组都有着Symbol.iterator属性
  • for...in针对对象的演示案例一:
var obj={
    a:1,
    b:2
}

for(var i in obj){
    console.log(i,obj[i])
}

//执行的顺序为
a 1
b 2
  • for...of针对对象的演示案例二:
var obj={
  a:1,
  b:2
}

for(var i of obj){ //obj is not iterable
    console.log(i,obj[i])
}

//执行的顺序为
Uncaught TypeError: obj is not iterable
  • 由上面两个针对对象案例中可以看出两种语句的差别,我们打印出对象(console.log(obj))来查看一下对象是否有实现[Symbol.iterator],结果是对象上默认是不支持迭代的,没有[Symbol.iterator],这也就解释了为什么通过for...of默认迭代不了对象的原因。

给对象搞上自定义的迭代协议

我们知道for...of默认是不能对对象进行迭代的,因为对象上是没有[Symbol.iterator]属性的,如果我们想让对象也能被for...of语句迭代,那我们可以给对象搞上自定义的迭代协议

  • 第一步:创建一个对象并给其添加上属性[Symbol.iterator]
let obj = {
    a: 1,
    b: 2,
    c: 3
};

obj[Symbol.iterator] = function(){

}

//调用for of 时会去调用obj[Symbol.iterator] 
for(let val of obj){
    console.log(val);
}
  • 第一步的执行结果为:Uncaught TypeError: Result of the Symbol.iterator method is not an object这是因为我们没有在obj[Symbol.iterator] 中去返回一个对象(return {})

  • 第二步:给obj[Symbol.iterator] 中去返回一个对象(return {})
let obj = {
    a: 1,
    b: 2,
    c: 3
};

obj[Symbol.iterator] = function(){
	return {}
}

//调用for of 时会去调用obj[Symbol.iterator] 
for(let val of obj){
    console.log(val);
}
  • 第二步的执行结果为: Uncaught TypeError: undefined is not a function这是因为我们没有在return{}里定义next()方法

  • 第三步:在return{}里定义next()方法
let obj = {
    a: 1,
    b: 2,
    c: 3
};

obj[Symbol.iterator] = function(){
	return {
    	next(){}
    }
}

//调用for of 时会去调用obj[Symbol.iterator] 
for(let val of obj){
    console.log(val);
}
  • 第三步的执行结果为:Uncaught TypeError: Iterator result undefined is not an object这是因为在next()[}里还需要返回一个返回值(对象),该返回值上面要有两个属性,一个是done,一个是value

第四步:使用迭代规则let values = Object.values(obj);的最终的改造结果

let obj = {
    a: 1,
    b: 2,
    c: 3
};


obj[Symbol.iterator] = function(){
  // 迭代协议--希望根据什么规则来循环
  // values=[1,2,3]
  let values = Object.values(obj);


  // console.log(values) //[1,2,3]
  // 用来遍历values的值  values[0]、 values[1]、 values[2]
  let index = 0;



  return {
      next(){
          if(index >= values.length){
              return {
                  //是否循环是否遍历迭代完成
                  //循环结束时不会在走到for-of里面
                  //循环执行完成就算这里写了value也是没有用的不会再影响循环结果了
                  done: true,
                  value:'无用'
              }
          } else {
              return {
                  //一直没有执行完成
                  done: false,
                  //value是我们在循环过程中的值
                  value:  values[index++]
              }
          }
      }
  }
};
  • 第四步的调用结果:
//调用for of 时会去调用obj[Symbol.iterator] 
//然后每一次会去执行next()方法,
//next()方法中的判断条件决定了循环的结果
//并把value值返回到这里的val里


for(let val of obj){
    console.log(val);
}


//执行顺序为
1
2
3

  • 第五步:我们可以换一种方式来验证第四步的执行步骤
let values = obj[Symbol.iterator]();

console.log(values.next());
// {done:false,value:1}

console.log(values.next());
// {done:false,value:2}


console.log(values.next());
// {done:false,value:3}


//一旦执行到done:true,
//就算这里写了value也是没有用的不会再影响循环结果了
console.log(values.next());
// {done:true, value: "无用"}

第六步:我们可以发现第四步使用的迭代规则是let values = Object.values(obj);我们同样可以换一种迭代规则来迭代,接下来我们使用let keys = Object.keys(obj);

let obj = {
    a: 1,
    b: 2,
    c: 3
};
obj[Symbol.iterator] = function(){
    let keys = Object.keys(obj);
    //[a,b,c]
    let index = 0;
    return {
        next(){
            if(index >= keys.length){
                return {
                    done: true
                }
            } else {
                return {
                    done: false,
                    value:  {
                        key: keys[index],
                        value: obj[keys[index++]]
                    }
                }
            }
        }
    }
};
for(let val of obj){
    console.log(val);
}

不经常用的Generator

  • 没有async/await之前依靠的Generator+promise
  • generator是基于迭代器实现的迭代函数
  • 案例一:
function*fn(){
    //yield定义的是每次循环返还的值
    //yield定义的其实是next()方法
    //这里的1next方法中返回的value值
    yield 1;
    yield 2;
    yield 3;
}
let f = fn();
// console.log(f); //不会立即执行 返回一个函数
//console.log(f.next()); //想要执行得调用next()方法 //第一次返回{value:1,done:false}
for(let val of f){
    console.log(val);
}
  • 案例二:
function*fn(){
    yield new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log("a");
            resolve('第1个Promise需要传递的数据');
        },500);
    });
    yield new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log("b");
            resolve('第2个Promise需要传递的数据');
        },500);
    });
    yield new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log("c");
            resolve('第3个Promise需要传递的数据');
        },500);
    });
}
fn();  //一个都没有显示 //不会立即执行 返回一个函数
  • 我们通过for-of来直接调用fn返回的函数,但是我们发现三个promise也是立即执行不会有时间间隔,我们想要的效果是三个promise会判断上一个异步执行完成之后再去执行下一个异步
//就算使用for of 三个promise也是立即执行不会有时间间隔
let f=fn();
for(let i of f){
    console.log(i)
}

  • 将上例改造为会判断上一个异步执行完成之后再去执行下一个异步
co(fn);
function co(fn){
    //f是fn()的执行结果
    let f = fn();  
    next();
    
    function next(data){
        //调用f.next方法
        let result = f.next();
        // console.log(result)
        //结果为{value:Promise,done:false}


        //result.done为false的时候
        if(!result.done){
            //第一次的时候{value:Promise,done:false}中的Promise是第一个Promise对象
            //这个Promise对象中有then方法

            // 上一个异步走完了,再执行下一个异步
            // 用了then当中info有resolve()传递的信息
            result.value.then((info)=>{
                console.log(info,data);
                next(info);

                //再去调用next()方法再次触发f.next()
                //next()
            });
        }
    }
}