阅读 43

迭代器模式

迭代器模式

Iterator(迭代器)

要成为可迭代对象,必须要实现@iterator方法,这个方法可以通过Symbol.iterator属性(可能在原型对象上)访问到。

因此可以通过有没有Symbol.iterator属性来判断是能不能进行遍历。

属性
Symbol.itarator一个无参数的函数,返回一个满足迭代器协议的可迭代对象(遍历器)

下面这些语法要使用可迭代对象

  • for...of..循环
  • 展开语法(数组、函数参数)
  • yield *
  • 结构赋值
// 以yield* 为例
let generator = function* () {
    yield 1;
    yield* obj; // obj是个可迭代对象,obj为代码块3定义的
    yield 5;
};
​
var iterator = generator();
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 'yxfan', done: false}
iterator.next(); // {value: 18, done: false}
iterator.next(); // {value: 5, done: false}
复制代码

下面这些数据结构内置可迭代对象,他们的原型对象都实现了@iterator方法

  • String

  • Array

  • Map

  • Set

  • 函数的arguments对象

  • NodeList (document.querySelectorAll)

  • HTMLCollection (document.getElementBy....)

  • TypedArray

    const arr = ['a', 'b', 'c']; // 1
    const iterator = arr[Symbol.iterator](); // 2
    iterator.next(); // { value: 'a', done: false }
    iterator.next(); // { value: 'b', done: false }
    iterator.next(); // { value: 'c', done: false }
    iterator.next(); // { value: undefined, done: true }
    复制代码

    由于Array原生就具备遍历器接口,所以在2处可以通过Symbol.iterator属性访问到生成遍历器的函数,并调用,得到遍历器iterator 因为对象没有内置遍历器接口,因此要用for...of在对象上,就得手动实现Symbol.iterator

    const obj = {
        name: 'yxfan', 
        age: 18
    };
    obj[Symbol.iterator] = function() {
        const keys = Object.keys(obj);
        let index = 0; // 老必包了
        return {
            next: function() {
              const object = {
                  value: obj[keys[index]],
                  done: index < keys.length ? false : true
              }
              index++;
              return object;
            }
        }
    }
    const iterator = obj[Symbol.iterator]();
    console.log(iterator.next()); // {value: "yxfan", done: false}
    console.log(iterator.next()); // {value: 18, done: false}
    console.log(iterator.next()); // {value: 18, done: true}
    for(let o of obj) {
        console.log(o); // 依次打印 yxfan  18
    }
    复制代码

    上面这个例子中,手动的给obj对象设置了Symbol.iterator属性,其值为一个生成器函数。这样我们就可以像for..of..数组一样,去for...of...对象啦!

    Generator(生成器)

    与Iterator接口的关系

    任意一个对象的[Symbol.iterator]方法,等于该对象的遍历器生成函数,调用生成函数,会返回一个遍历器对象。

    generator函数就是遍历器生成函数,因此可以把generator函数赋值给[Symbol.iterator]

    来改造上一个例子:

     const obj = {
       name: 'yxfan', 
       age: 18,
       * [Symbol.iterator]() {
            let index = 0;
            const keys = Object.keys(this);
            for(let k of keys) {
                yield [k, this[k]];
            }
       }
     };
    const iterator = obj[Symbol.iterator]();
    console.log(iterator.next()); // {value: ['name', 'yxfan'], done: false}
    console.log(iterator.next()); // {value: ['age', 18], done: false}
    console.log(iterator.next()); // {value: undefined, done: true}
    for(let [key, value] of obj) {
        console.log(key, value); // name yxfan age 18
    }
    复制代码

    利用generator我们不用在[Symbol.iterator]中部署next方法直接用yield给出每一步的返回值即可

    generator方法会返回一个遍历器,遍历器的next方法运行逻辑如下

    1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面表达式的值,作为返回对象的value
    2. 下一次调用next方法时,继续往下执行,直到遇到下一个yield表达式
    3. 如果没有遇到新的yield表达式值,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
    4. 如果该函数没有return语句,则返回的对象的value属性值为undefined

    generator函数不会立即执行

    function * g() {
        console.log('我不会立即执行喔');
    }
    const iterator = g();
    setTimeout(() => {
        console.log(iterator.next());   
    }, 2000);
    复制代码

    如果g()是个普通函数,一经调用则会马上输出log,但是这个是个generator函数,调用时会返回遍历器对象,我尝试着打印一下iterator

image-20210803174539826.png

只有调用next时函数g才会执行

总结

该篇主要是说迭代器模式,因此Iteratorgenerator只是稍微提了一下,跟详实的可看ES6

by the way,迭代器模式在实际开发中用的并不多,但还是得知道是怎么实现的(方便面试)

文章分类
前端
文章标签