分析ES6中的迭代器原理以及手写迭代器

150 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情 >>

迭代器

迭代器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

你可以理解为:迭代器是一个入口,为不同数据提供统一的入口。(数据要想传送过去必须通过iterator这个入口),任何数据结构只要添加Iterator接口,就可以完成遍历操作。

  1. ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费

  2. 原生具备iterator接口的数据(可用for of遍历)

    a)  Array
    b)  Arguments
    c)  Set
    d)  Map
    e)  String
    f)  TypedArray
    g)  NodeList
    

扩展对比一下for...of和for...in在对象,数组中的区别

        // 数组
        let arr = [1,3,4,5,6];
        // for of
        for(let item of arr){
            console.log(item);//数组的每一项
        }
        // for in
        for(let item in arr){
            console.log(item);//数组的下标
        }
​
        // 对象
        let obj = {
            name:"oko",
            age:18
        }
        // for in
        for (let item in obj) {
                console.log(item);//item是对象的每一个属性名
            }
        // for  of
        for(let item of obj){
            console.log(item);//obj is not iterable
        }

迭代器的工作原理:

a)      创建一个指针对象,指向当前数据结构的起始位置

b)      第一次调用对象的next方法,指针自动指向数据结构的第一个成员

c)      接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员

d)      每调用next方法返回一个包含value和done属性的对象

看到这里可能有小伙伴还是有点迷,下面我将用图片和代码的形式详细的解释一下:

图片:

1.创建一个指针对象(对象),指向当前数据结构(数组)的起始位置(不是指向第一个成员,是第一个成员之前) image.png 2.调用指针对象上的next方法,现在指针对象指向1 image.png 3.同上不断调用next()方法,指针一直向后移动,每调用一次,指针指向下一个成员 image.png 4.每调用一次next的方法,方法的返回值是一个对象,对象的属性有valuedone image.png 注意:不断调用next()方法的执行过程是先调用next方法,返回了对象 然后再调用next方法 image.png 代码解释

//定义一个数组
let arry1 = [1,2,3,4,5];
//指针对象-----理解为就是个对象
let iterator = arry1[Symbol.iterator]();
定义一个变量存储这个指针对象,arry1[Symbol.iterator]()这里需要用到Symbol的知识 Symbol的内置方法中有一个
Symbol.iterator方法,返回该对象的默认遍历器(迭代器)。数组是数组,数组也是一个对象.
那么数组arr1={[Symbol.iterator]:function(){}}
如果你看不懂[Symbol.iterator]这是个啥 你需要去复习一下[]语法 或者看我的symbol文章(比这篇文章写得应该更清楚 我写到这里已经不想写下去了 感觉理的不清楚);
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: 2, done: false}
console.log(iterator.next());//{value: 3, done: false}
console.log(iterator.next());//{value: 4, done: false}
console.log(iterator.next());//{value: 5, done: false}
console.log(iterator.next());//{value: undefined, done: true}
console.log(iterator.next());//{value: undefined, done: true}
value指的是数据成员的值 done的布尔值表示是否要继续遍历下去 如果等于true则结束遍历

带大家看下这个指针对象长啥样 console.log(iterator); image.png

手写迭代器

我们知道了迭代器以及迭代器的原理,接下来我们手写迭代器(对照着迭代器原理来写):

 const game = {
        name: '游戏',
        members: [
            '星际争霸',
            '英雄联盟',
            '骑马与砍杀',
            '鬼谷八荒'
        ],
        //1.设置迭代器的属性 因为对象身上没有迭代器这个属性 所以我们要添加上去
        [Symbol.iterator]:function(){
            // 6.设置index初始值为0 同时作为下标
            let index = 0;
            // 2.设置返回值 这个返回值就是指针对象 --->对象
            return{//指针对象还有啥特点? 指针对象上有next方法
            // 3.设置next方法
                next:function(){
                    // 5.定义一个value值 值对应members数组中下标为[index]的值
                    let value = game.members[index];
                    // 7. 定义done 初始值为false 为啥初始值为false 因为遍历还没开始
                    let done = false;
                    // 8.结束遍历的判断条件
                    if (index >= game.members.length ) {//如果index的值大于数组的长度 则代表指针对象已经走到头了 
                        done = true;//将done的值变为true 代表遍历结束了
                    }
                    //让index 自增
                    index++;
                    // 4.next方法也有一个返回值,返回一个对象,对象中有{value,done}属性
                    return{value,done}//完整的对象写法是:{value:value,done:done}
                }
            }
        }
    }
    for(let item of game){
        console.log(item);//输出结果如下图所示 为啥不是对象的属性 因为自己手写迭代器里面写的就是对象game.members这个数组
    }

image.png 如果你对手写迭代器还有些疑惑 接下来我将根据原理和手写迭代器继续解释一下

let iterator = arry1[Symbol.iterator[]();
1.arry1[Symbol.iterator]() ===> arry1是数组也是对象 arry1 = {[Symbol.iterator]:function(){}}
arry1身上有[Symbol.iterator]这个Symbol的内置方法
2.arry1 = {[Symbol.iterator]:function(){return{}}}
arry1身上有[Symbol.iterator]这个Symbol的内置方法的返回值是一个对象 为啥是对象看
console.log(iterator.next());//{value: 1, done: false}.左边的就是对象 ()左边的是函数 
3.arry1 = {[Symbol.iterator]:function(){return{next:function(return{value,done})}}}
arry1身上有[Symbol.iterator]这个Symbol的内置方法的返回值是一个对象,对象上有一个next方法,next方法返回值
也是一个对象 {value,done}

手写迭代器的一些注意点:

1.index的设置要设置在 [Symbol.iterator]这个方法中,为啥不能设置到next中?

因为每次调用next方法相当于在堆内存中每次开辟一个函数空间,index的值永远自增到1,所以得设置到next方法的外边.那么return{}里面为啥不行? 因为对象里面怎么let index = 0;

  1. if (index >= game.members.length ) 为啥等于(=)不就行了 还需要>=呢?
 console.log(iterator.next())  //{value: '星际争霸', done: false}
    console.log(iterator.next())  //{value: '英雄联盟', done: false}
    console.log(iterator.next())  //{value: '骑马与砍杀', done: false}
    console.log(iterator.next())  //{value: '鬼谷八荒', done: false}
    console.log(iterator.next())  //{value: undefined, done: true}
    console.log(iterator.next())  //{value: undefined, done: false}
    console.log(iterator.next()) //{value: undefined, done: fasle}
    当我在指针对象到达数组最后一个成员后 再调用一次就会发现 done的值又变为了false,
    而我们知道只有done变为true才会接受循环,这显然与我们预期的不符

image.png

image.png

3. console.log(game[Symbol.iterator]().next())的value值始终等于星际争霸

我们知道 let iterator = game[Symbol.iterator]();可以推出
iterator.next() = game[Symbol.iterator]().next()
那么为啥iterator.next()输出正常 而game[Symbol.iterator]().next()输出错误呢
原理和1.index差不多 game[Symbol.iterator]()每次调用 都会生成一个指针对象 调用4次 生成四个指针对象 
四个指针对象都只调用了一次next()方法 自然输出的结果就是数据的第一个成员了
而let iterator = game[Symbol.iterator]()只生成了一个指针对象 调用了四次next方法

image.png

4.我们看一下自己手写迭代器的 指针对象长啥样 let iterator = game[Symbol.iterator]()

image.png