js--迭代器总结

前言

  我们已经熟练使用set.map.array几种集合类型了,掌握了map(),for..of..,filter()等迭代集合的方法,你是否思考过,js引擎是怎么迭代的,怎么判断迭代是否结束,本文来总结一下 js 新增的迭代器相关在知识点。

正文

  1 、迭代器的产生、定义和模拟

  (1) for 循环的弊端

  普通的for循环的弊端因为数组有已知的长度,且数组每一项都可以通过索引获取,所以整个数组可以通过递增索引来遍历。由于如下原因,通过这种循环来执行例程并不理想。

  a、 迭代之前需要事先知道如何使用数据结构。数组中的每一项都只能先通过引用取得数组对象,然后再通过 [] 操作符取得特定索引位置上的项。这种情况并不适用于所有数据结构。

  b、 遍历顺序并不是数据结构固有的。通过递增索引来访问数据是特定于数组类型的方式,并不适用于其他具有隐式顺序的数据结构。

  (2) 迭代器的产生

迭代器是被设计专用于迭代的对象,带有特定的接口,迭代器持有一个指向集合位置的内部指针,每当调用了next()方法,迭代器就会返回一个结果 IteratorResult 对象,该结果对象有两个属性,对应下一个值的 value 以及一个布尔类型的 done,在最后一个值再调用next()则返回 done 属性值为 true(标识没有更多值供使用),并且value属性会是迭代器自身的返回值. 该返回值不是原始数据集的一部分,却成为相关数据的最后一部分,或在迭代器未提供返回值的时候使用undefined ,迭代器的自身返回值类似于函数的返回值,是向调用者返回信息的最后手段。

  (3) 迭代器的模拟

  根据上面的定义,手动实现迭代器函数

    // createIterator 函数返回一个带有next()方法的对象,作为迭代器,每次调用next()方法,返回具体的IteratorResult 对象值
    function createIterator(items) {
      var i = 0;
      return {
        next() {
          var done = (i >= items.length);
          var value = !done ? items[i++] : undefined;
          return {
            done, value
          }
        }
      }
    }
    var iterator = createIterator([11, 22, 33])
    console.log(iterator.next());//{done: false, value: 11}
    console.log(iterator.next());//{done: false, value: 22}
    console.log(iterator.next());//{done: false, value: 33}
    console.log(iterator.next());//{done: true, value: undefined}

  2、 迭代器模式和可迭代对象

  迭代器模式(特别是在 ECMAScript 这个语境下)描述了一个方案,即可以把有些结构称为“可迭代对象”(iterable),因为它们实现了正式的 Iterable 接口,而且可以通过迭代器 Iterator 消费。

  实现 Iterable 接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现 Iterator 接口的对象的能力。在 ECMAScript 中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的 Symbol.iterator 作为键。这个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。

  (1)访问默认迭代器 symbol.iterator

        let values = [1, 2, 3, 4]
        let iterator = values[Symbol.iterator]()
        console.log(iterator.next());//{value:1,deno:false}
        console.log(iterator.next());//{value:2,deno:false}
        console.log(iterator.next());//{value:3,deno:false}
        console.log(iterator.next());//{value:4,deno:false}
        console.log(iterator.next());//{value:undefiend,deno:true}

  (2) 检测一个对象是否可以用来迭代

        let num = 1;
        let obj = {};
        // 这两种类型没有实现迭代器工厂函数
        console.log(num[Symbol.iterator]); // undefined
        console.log(obj[Symbol.iterator]); // undefined

        let str = 'abc';
        let arr = ['a', 'b', 'c'];
        let map = new Map().set('a', 1).set('b', 2).set('c', 3);
        let set = new Set().add('a').add('b').add('c');
        let els = document.querySelectorAll('div');
        // 这些类型都实现了迭代器工厂函数
        console.log(str[Symbol.iterator]); // f values() { [native code] }
        console.log(arr[Symbol.iterator]); // f values() { [native code] }
        console.log(map[Symbol.iterator]); // f values() { [native code] }
        console.log(set[Symbol.iterator]); // f values() { [native code] }
        console.log(els[Symbol.iterator]); // f values() { [native code] }
        // 调用这个工厂函数会生成一个迭代器
        console.log(str[Symbol.iterator]()); // StringIterator {}
        console.log(arr[Symbol.iterator]()); // ArrayIterator {}
        console.log(map[Symbol.iterator]()); // MapIterator {}
        console.log(set[Symbol.iterator]()); // SetIterator {}
        console.log(els[Symbol.iterator]()); // ArrayIterator {}

  总结:检测一个对象是否可以用来迭代的方式:typeof object[Symbol.iterator] === "function",同时可以得出很多内置类型都实现了 Iterable 接口:字符串、 数组、映射、集合、arguments 对象、NodeList 等 DOM 集合类型等。

 注意:

         a、 每个迭代器都表示对可迭代对象的一次性有序遍历。不同迭代器的实例相互之间没有联系,只会独立地遍历可迭代对象。

         b、迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程。如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化。如下:

        let arr = ['foo', 'baz'];
        let iter = arr[Symbol.iterator]();
        let iter2 = arr[Symbol.iterator]();
        console.log(iter.next()); // { done: false, value: 'foo' }
        console.log(iter2.next()); // { done: false, value: 'foo' }
        // 在数组中间插入值
        arr.splice(1, 0, 'bar');
        console.log(iter.next()); // { done: false, value: 'bar' }
        console.log(iter.next()); // { done: false, value: 'baz' }
        console.log(iter.next()); // { done: true, value: undefined }

  3 、开发中使用的迭代器

  实际写代码过程中,不需要显式调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。接收可迭代对象的原生语言特性包括:
            a、 for-of 循环
            b、 数组解构
            c、 扩展操作符
            d、 Array.from()
            e、 创建集合
            f、 创建映射
            g、 Promise.all() 接收由期约组成的可迭代对象
            h、 Promise.race() 接收由期约组成的可迭代对象
            i、 yield* 操作符,在生成器中使用
  这些原生语言结构会在后台调用提供的可迭代对象的这个工厂函数,从而创建一个迭代器。

  (1) 集合的内置迭代器

   es6 具有三种集合对象类型:数组、map、set ,三种类型都拥有如下迭代器
          a、entries()返回一个包含键值对的迭代器
          b、values() 返回一个包含集合中的值的迭代器
          c、keys() 返回一个包含集合中的键的迭代器,对于set来说键和值是相同的,对于map来说,迭代器返回每个不重复的键
    /*
    entries()迭代器会在每次next()被调用时返回一个双项数组,此数组代表了集合中每个元素的键与值,
    对于数组来说,第一项是数值索引,
    对于set,第一项也是值,因为它的值也会被视为键,
    对于map来说,第一项就是键
    */
    let arr = [1, 2, 3]
    console.log(arr.entries());//Array Iterator []
    for (const item of arr.entries()) {
      console.log(item);
    }// [0:1],[1:2],[2:3]

    let set = new Set([1, 2, 3])
    console.log(set.entries());//SetIterator  {[1=>1],[2=>2],[3=>3]}
    for (const item of set.entries()) {
      console.log(item);
    }//  [1:1],[2:2],[3:3]
    
    let map = new Map()
    map.set("first", "firseetValue")
    map.set("second", "seondValue")
    map.set("third", "thirdValue")
    console.log(map.entries());//MapIterator  {["first"=>"firseetValue"],["second"=>"seondValue"],["third"=>"thirdValue"]}
    for (const item of map.entries()) {
      console.log(item);
    }// ["first":"firseetValue"],["second":"seondValue"],["third":"thirdValue"]

  (2)字符串的迭代器

    // 访问字符串中的字符可以通过下标的形式 
    let message = "a  b"
    console.log(message[0])// 'a'
    let info = "a b"
    for (const c of info) {
      console.log(c);
    }//a, ,b   

  4、自定义函数实现迭代器

        // 与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用。
        class Counter {
            constructor(limit) {
                this.limit = limit;
            }
            [Symbol.iterator]() {
                let count = 1,// 把计数器变量放到闭包里,然后通过闭包返回迭代器,让一个可迭代对象能够创建多个迭代器,且每创建一个迭代器就对应一个新计数器
                    limit = this.limit;
                return {
                    next() {
                        if (count <= limit) {
                            return { done: false, value: count++ };
                        } else {
                            return { done: true, value: undefined };
                        }
                    },
                    // 可选的 return() 方法用于指定在迭代器提前关闭时执行的逻辑
                    return() {
                        console.log('Exiting early');
                        return { done: true };
                    }
                };
            }
        }
        let counter = new Counter(3);
        for (let i of counter) { console.log(i); }
        // 1
        // 2
        // 3
        for (let i of counter) { console.log(i); }
        // 1
        // 2
        // 3

        let counter2 = new Counter(5);
        try {
            for (let i of counter2) {
                if (i > 2) {
                    throw 'err';
                }
                console.log(i);
            }
        } catch (e) { }
        // 1
        // 2
        // Exiting early

  5、如何使用for...of遍历对象

  for…of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。

  如果需要遍历的对象是类数组对象,用Array.from转成数组即可。

var obj = {
    0:'one',
    1:'two',
    length: 2
};
obj = Array.from(obj);
for(var k of obj){
    console.log(k)// one two
}

  如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。

//方法一:
var obj = {
    a:1,
    b:2,
    c:3
};

obj[Symbol.iterator] = function(){
    var keys = Object.keys(this);
    var count = 0;
    return {
        next(){
            if(count<keys.length){
                return {value: obj[keys[count++]],done:false};
            }else{
                return {value:undefined,done:true};
            }
        }
    }
};

for(var k of obj){
    console.log(k);
}


// 方法二
var obj = {
    a:1,
    b:2,
    c:3
};
obj[Symbol.iterator] = function*(){
    var keys = Object.keys(obj);
    for(var k of keys){
        yield [k,obj[k]]
    }
};

for(var [k,v] of obj){
    console.log(k,v);
}

 

写在最后

  以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。

 

 

posted @ 2021-11-28 11:54  zaisy'Blog  阅读(777)  评论(0编辑  收藏  举报