• javaScript系列 [47]Iterator迭代器


    本文简单说明 迭代器接口 Iterator 接口的基本使用,涉及 Array 、Set 、Map 和 String 以及伪数组等数据结构,以及 for...of循环的用法等。

    Iterator - 基本使用

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

    Iterator 的优点在于能够为不同的数据结构提供了统一的接口;能够以特定的排序来遍历数据结构;提供创造了for...of循环。JavaScript中默认实现迭代器接口( Iterator )的数据结构有类数组结构(NodeList、arguments、String等) 和 Set 、Map、Array等 ,实现 Iterator 接口的数据结构均支持使用 for...of 循环来执行遍历操作。

    下面通过代码简单展示Set 、Map、Array三种数据结构中实现的原生迭代器接口( Iterator )和for...of遍历

    /* 1.数组 Array */
    /* 2.集合 Set */
    /* 3.映射 Map */
    /* 4.其它结构 */
    
    let arr = [100, 200, 300];
    console.log("arr", arr);
    
    let set = new Set([10, 20, 30, 20, "测试"]);
    console.log("set", set);
    
    let map = new Map();
    map.set("a", "A");
    map.set("b", "B");
    map.set("c", "C");
    console.log("map", map);
    
    /* 测试数据 */
    for (let ele of arr) {
        console.log(ele);
    }
    console.log('______________');
    for (let ele of set) {
        console.log(ele);
    }
    console.log('______________');
    for (let [key,val] of map) {
        console.log(key,val);
    }
    console.log('______________');
    
    /* 打印输出 */
    /* 
    arr [ 100, 200, 300 ]
    set Set { 10, 20, 30, '测试' }
    map Map { 'a' => 'A', 'b' => 'B', 'c' => 'C' }
    100
    200
    300
    ______________
    10
    20
    30
    测试
    ______________
    a A
    b B
    c C
    ______________ */
    

    通过查看console.log(Array.property,Set.property,Map.property);打印结果,你会发现在数组、集合和映射它们的内部,都在其构造函数的原型对象上无一例外都实现了Symbol(Symbol.iterator): ƒ entries()函数,调用该函数我们能够得到一个iterator 型对象,当我们使用for...of循环结构来遍历它们的时候,在内部会利用该对象来完成遍历操作。

    let arr = [100, 200, 300];
    
    /* arr.__proto__ ===  Array.prototype[Symbol.iterator] */
    let iterator = arr[Symbol.iterator]();
    console.log(iterator); /* Object [Array Iterator] {} */
    
    console.log("_________bgn_________")
    let o = iterator.next();
    while (!o.done) {
        o = iterator.next()
        console.log(o);
    }
    console.log("_________end_________")
    
    /* 打印输出 */
    /* 
    Object [Array Iterator] {}
    _________bgn_________
    { value: 200, done: false }
    { value: 300, done: false }
    { value: undefined, done: true }
    _________end_________
    */
    

    通过Array.prototype[Symbol.iterator]()可以得到一个iterator 型对象,调用该对象的next方法后能得到个拥有两个键值对的对象,其中value表示的是当前的值,而 done 可以理解为是循环是否结束。在上面的代码中,我通过一个 while 循环来模拟了for..of循环过程。此外,也可以简单对比下这些结构中的entries()、keys() 和 values()等函数的用法。

    let arr = [100, 200, 300];
    let iterator = arr.entries();
    
    console.log("_________bgn_________")
    let o = iterator.next();
    while (!o.done) {
        o = iterator.next()
        console.log(o);
    }
    console.log("_________end_________")
    console.log(arr.keys())
    console.log(arr.values())
    
    /* 测试输出 */
    /* 
      _________bgn_________
      { value: [ 1, 200 ], done: false }
      { value: [ 2, 300 ], done: false }
      { value: undefined, done: true }
      _________end_________
      Object [Array Iterator] {}
      Object [Array Iterator] {} */
    

    在 ES6中的数组、Set和 Map 中都部署了entries() 、keys()、values() 三个方法,它们调用后都返回 iterator 迭代器对象,其中entries()返回的迭代器对象用于遍历[key,value]组成的数组,而keys()返回的迭代器对象用于遍历所有的键名,values()返回的迭代器对象用于遍历所有的键值。除了上面列出的Array、Set和 Map结构支持for..of外,下面在给出类数组结构(伪数组)的几种情况。

    /* 1. arguments */
    function test() {
        console.log('arguments');
        for (const iterator of arguments) {
            console.log('iterator = ', iterator);
        }
    }
    
    test("a", "b", "c", 10, 203);
    
    /* 打印输出: */
    /* 
    arguments
    iterator =  a
    iterator =  b
    iterator =  c
    iterator =  10
    iterator =  203 */
    
    
    /* 2.NodeList */
    let oDiv = document.createElement("div");
    oDiv.innerHTML = "<span>A</span><span>B</span><span>c</span><span>D</span>";
    console.log(oDiv.children);
    for (const iterator of oDiv.children) {
        console.log('element = ', iterator);
    }
    /* 打印输出 */
    /* 
    HTMLCollection(4) [span, span, span, span]
    VM76:5 element =  <span>​A​</span>
    VM76:5 element =  <span>​B​</span>
    VM76:5 element =  <span>​c​</span>
    VM76:5 element =  <span>​D​</span> */
    
    
    /* 3.字符串(String) */
    let str = "Hello";
    
    for (const iterator of str) {
        console.log("s = ", iterator);
    }
    
    for (const iterator of str[Symbol.iterator]()) {
        console.log("s = ", iterator);
    }
    
    /* 打印输出 */
    /* 
    s =  H
    s =  e
    s =  l
    s =  l
    s =  o */
    

    在上面列出的几种伪数组结构中,他们内部都实现了iterator接口,自己写的伪数组或者是对象实现了iterator接口支持for...of循环吗? 答案是否定的。

    /* 1、自己写的伪数组结构 */
    let likeArray = { 0: "a", 1: "b", 2: 'c', length: 3 };
    for (const iterator of likeArray) {
        console.log('iterator = ', iterator);
    }
    /* 报错:TypeError: likeArray is not iterable */
    
    /* 2.对象结构 */
    let o = { name: "Yong", age: 18 };
    for (const iterator of o) {
        console.log('iterator = ', iterator);
    }
    /* 报错:TypeError: o is not iterable */
    

    如果自己写的伪数组也要能够支持for...of 循环,那么可以有下面几种尝试的办法。

    let likeArray = { 0: "a", 1: "b", 2: 'c', length: 3 };
    
    /* 第一种方式:通过对象解构方式来先转换为数组 */
    /* 结果:失败 (分析原因:扩展运算符[...]内部默认会自动调用 iterator 接口) */
    for (const iterator of[...likeArray]) {
        console.log('iterator = ', likeArray);
    }
    
    /* 第二种方式:利用 Array.from尝试转换为数组 */
    for (const iterator of Array.from(likeArray)) {
      console.log('iterator = ', iterator);
    }
    
    /* 结果输出:
    iterator =  a
    iterator =  b
    iterator =  c */
    
    /* 第三种方式:在当前伪数组的原型上面部署"原生"的 iterator 迭代器接口 */
    /* ①  */
    // likeArray.__proto__[Symbol.iterator] = Array.prototype[Symbol.iterator];
    /* ② */
    // Object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    /* ③ */
    // likeArray[Symbol.iterator] = Array.prototype[Symbol.iterator];
    /* ④ */
    likeArray[Symbol.iterator] = [][Symbol.iterator].bind(likeArray);
    
    for (const iterator of likeArray) {
        console.log('iterator = ', iterator);
    }
    
    /* 结果输出:
    iterator =  a
    iterator =  b
    iterator =  c */
    
    

    注意的是上面直接利用数组的[Symbol.iterator]来为伪数组部署迭代器接口的方式并不适用于普通的对象,如果用在普通对象的身上那么则毫无效果。

    /* 2.对象结构 */
    let o = { name: "Yong", age: 18 };
    
    /* 2.1 对象无法直接通过 for...of进行遍历 */
    for (const iterator of o) {
        console.log('iterator = ', likeArray);
    }
    /* 报错:TypeError: o is not iterable */
    
    /* 2.2 尝试利用数组的Symbol.iterator接口部署 */
    o.__proto__[Symbol.iterator] = [][Symbol.iterator];
    for (const iterator of o) {
        console.log('iterator = ', likeArray);
    }
    /* 结果:不会进入循环,没有任何输出 */
    
    /* 2.3 尝试遍历对象的 keys 间接遍历对象 */
    for (const key of Object.keys(o)) {
        console.log(`key:${key} value:${o[key]}`);
    }
    /* 结果 */
    /* 
    key:name value:Yong
    key:age  value:18 */
    

    这里简单思考和总结下,对象中没有实现Iterator迭代器的原因
    ○ 对象已经拥有了 for...in循环 (该循环专为对象迭代设计)。
    ○ 对象在遍历的时候,属性( 键值对 )遍历的先后顺序是不确定的,而Iterator迭代器是线性的。
    ○ ES6提供了 Map ,可以在某种程度上实现替代操作。

    Iterator - 内部结构

    在数组等数据结构中,当我们调用 entries() 或者是Symbol.iterator()的时候将得到一个iterator迭代器对象,在该对象中next方法每调用一次就会返回一个包含本次迭代 value 值以及标记是否完成迭代的 done 属性。

    let arr = ["a", "b"];
    let iterator = arr[Symbol.iterator]();
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    /* 打印输出 */
    /* 
    { value: 'a', done: false }
    { value: 'b', done: false }
    { value: undefined, done: true } */
    
    

    这里我们可以尝试来封装一个函数makeIterator,模拟 next函数的工作过程。

    let makeIterator = (arr) => {
        let idx = 0;
        return {
            next: () => idx < arr.length ? 
            { value: arr[idx], done: false } : { value: undefined, done: true }
        }
    }
    
    let iterator = makeIterator([100, 200, "Yong"]);
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    
    /* 打印输出 */
    /* 
    { value: 100, done: false }
    { value: 200, done: false }
    { value: 'Yong', done: false }
    { value: undefined, done: true }*/
    

    假如我们想要让普通的对象也能够直接支持(除了Object.keys()形式)for...of循环,那么可以考虑主动的在对象或者对象的原型对象上面部署iterator迭代器接口,下面简单给出对应的示例代码。

    /* 方案-01 */
    // let o = {
    //     name: "Yong",
    //     address: "GuangZhou",
    //     [Symbol.iterator]() {
    //         let idx = 0;
    //         let map = [];
    //         Object.keys(this).forEach(key => map.push([key, this[key]]))
    //         return {
    //             next: () => idx < map.length ? { value: map[idx++], done: false } 
    //              : { value: undefined, done: true }
    //         };
    //     }
    // };
    
    /* 方案-02 */
    Object.prototype[Symbol.iterator] = function() {
        let idx = 0;
        let map = [];
        Object.keys(this).forEach(key => map.push([key, this[key]]))
        return {
            next: () => idx < map.length ? { value: map[idx++], done: false } 
              : { value: undefined, done: true }
        };
    }
    
    let o = {
        name: "Yong",
        address: "GuangZhou"
    };
    
    /* 测试代码 */
    let iterator = o[Symbol.iterator]();
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log("+++++++++++++++++");
    
    for (const iterator of o) {
        console.log('iterator:', iterator);
    }
    
    console.log("+++++++++++++++++");
    
    for (const [key, value] of o) {
        console.log('key:', key, "val:", value);
    }
    
    /* 打印输出 */
    /* 
    { value: [ 'name', 'Yong' ], done: false }
    { value: [ 'address', 'GuangZhou' ], done: false }
    { value: undefined, done: true }
    
    +++++++++++++++++
    iterator: [ 'name', 'Yong' ]
    iterator: [ 'address', 'GuangZhou' ]
    
    +++++++++++++++++
    key: name    val: Yong
    key: address val: GuangZhou */
    

    如果想要更简单点,其实还可以借助 Generator 生成器函数来实现。

    /* 生成器函数来实现 */
    let obj = {
        *[Symbol.iterator]() {
            yield "H";
            yield "e";
            yield "l";
            yield "l";
            yield "o"
        }
    }
    
    for (const e of obj) {
        console.log('e:', e);
    }
    /* 打印输出 */
    /* 
    e: H
    e: e
    e: l
    e: l
    e: o */
    
  • 相关阅读:
    三层架构及生活实例
    初识ADO.NET
    remoting与socket、web service的比较及实例
    ERP与MES
    25人赛跑问题-得出前三名
    WPF导学目录
    心目中的职业初规划
    应用服务器
    2015广深骑行记
    ASP.NET的几个试题(《C#与.NET程序员面试宝典》)
  • 原文地址:https://www.cnblogs.com/wendingding/p/15761427.html
Copyright © 2020-2023  润新知