wangjing013 / blog

📝 记录

Home Page:https://wangjing013.github.io/blog/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

迭代器模式

wangjing013 opened this issue · comments

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

如果对上面定义不理解,那么先接着看后面的内容。相信看完后,一定会恍然大悟。

为什么需要引入迭代器

从定义中能得出,其实迭代器模式要解决的根本问题是,不同聚合对象遍历的问题。如数组、链表、对象等,通常想要遍历某个对象时都需要针对类型去选取不同的方式.

例如数组 for + length,对象通过 Object.keys,链表相对复杂通过 next() 移动指针的方式。 这样其实带来理解上的成本。

迭代器离我们很近

在我们日常开发工作中,遍历是一种高频出现的需求。因为它的特殊性,所以在编程语言中都内置了迭代器。JS 也不例外,在 ES6 之前 JS 内部提供简陋的迭代器 Array.prototype.forEach 用于遍历数组对象,如下:

[1,2,3,4].forEach((item, index, arr)=> {
  console.log(`当前元素: ${item}, 对应索引: ${index}``);
})

forEach 从定义确定它不是万能,它只适合应用在 Array 对象上。 现在要遍历类数组对象(eg: arguments、HTMLCollection ),对象时 forEach 就显得无能无力了。

当遍历类数组对象时,通常做法是先通过借助 Array.prototype.slice 把相应的类数组对象转换成数组对象,再进行相应遍历操作。

  • 遍历类数组对象
const obj = {
  length: 2,
  '0': 1,
  '1': 2
}
const arr = Array.prototype.slice.call(obj);
arr.forEach(function(item, index, arr){
  console.log(`当前元素: ${item}, 对应索引: ${index}`);
});
  • 遍历对象
const obj = {
  name: 'wangjing',
  age: 28
}
const keys = Object.keys(obj);
for(let key of keys) {
  console.log(key, obj[key]);
}

从上面案例来看,在遍历不同集合类型时,首先需要知道它属于什么特定类型(关心内部结构),应用特定的方式。

遍历数组、对象、类数组都是一种常见需求,因为它被需要,所以很多库都提供通用的迭代器,例如 Jquery 中 map,lodash 中提供 forEachforEachRight

下面使用 lodash 中提供 forEach 来实现上面集合对象遍历,

import { forEach } from "lodash";
const arr = [1, 2, 3, 4];
forEach(arr, (value, index, arr) => {
  console.log(value, index, arr);
});

const obj = {
  name: "张三",
  age: 20
};
forEach(obj, (value, index, obj) => {
  console.log(value, index, obj);
});

const o = {
  length: 2,
  '0': 1,
  '1': 2
}
forEach(o, (value, index, obj)=> {
  console.log(value, index, obj)
})

细心人应该能发现,同样是迭代不同类型的对象,通过 lodash 提供 forEach 使得无需关心具体类型及内部的实现,这就是迭代器的价值所在。

随着 JS 的发展,同样提供一套统一迭代器规范。

ES6 对迭代器的实现

因为它的重要性,所以 ES6 定义一套统一接口规范(iterator),其次大部分内置对象中也都实现该规范,例如 Array、String、Map、Set、arguments、HTMLCollection。同样提供 for of 去遍历已实现迭代器的对象,这样达到统一的目的。

同样已前面的案例,现在通过 ES6 提供迭代器去完成相应的遍历。

数组

const arr = [1,2,3];
const iterator = arr[Symbol.iterator]()

iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: 3, done: false}
iterator.next(); // {value: undefined, done: true}

类数组对象

遍历 arguments 对象

function fn(){
  const iterator = arguments[Symbol.iterator]()
  iterator.next(); // {value: 1, done: false}
  iterator.next(); // {value: 2, done: false}
  iterator.next(); // {value: 3, done: false}
  iterator.next(); // {value: undefined, done: true}
}

fn(1,2,3,4)

对象类型

对象默认没有实现迭代器,如果想要一个对象成为可迭代的对象,一个对象必须实现 @@iterator 方法。这里就不概述了,想要了解更多可以查看 迭代协议

const obj = {
  name: "张三",
  age: 20
};

obj[Symbol.iterator] = function(){
  const keys = Object.keys(obj);
  let index = 0
  return  {
    next(){
      return index < keys.length ? {
        value: obj[keys[index++]]
        done: false
      } : {
        value: undefined,
        done: true
      }
    }
  }
}

generator

generator 天生为迭代器的 API:

function* fn(){
  yield 1;
  yield 2;
  yield 3;
}

var run = func();
run.next() // {value: 1, done: false}
run.next() // {value: 2, done: false}
run.next() // {value: 3, done: true}

我们无需关心 generator 内部是何种存储结构,只需要调用 .next(),并根据返回的 done 来判断是否遍历完即可。在 generator 的场景中,迭代器不仅用来遍历聚合,还用于执行代码。

链表

const linkedList = {
  value: 'n1',
  next: {
    value: 'n2',
    next: {
      value: 'n3',
      next: null
    }
  },
  [Symbol.iterator]() {
    let head = this;
    return {
      next(){
        if (head) {
          let value = head.value;
          head = head.next;
          return {
            value: value,
            done: false
          }
        }
        return {
          value: undefined,
          done: true
        }
      },
    }
  }
}

const iterator = linkedList[Symbol.iterator]()
console.log(iterator.next())  // { value: 'n1', done: false }
console.log(iterator.next())  // { value: 'n2', done: false }
console.log(iterator.next())  // { value: 'n3', done: false }
console.log(iterator.next())  //  { value: undefined, done: true }

从上面例子中可以看到,我们都是通过 Symbol.iterator 获取对象的迭代器( iterator ),然后调用它 next() 方法进行遍历输出。

现在再回顾看看迭代器定义及它解决的问题,是不是不谋而合。到此迭代器相关的内容就告一段落啦!!!

总结

迭代器是一种简单的设计模式,甚至很多时候我们都不认为它是一种设计模式。相信在此之很多人都没有意识到迭代器离我们很近,且一直都在用。希望通过上面案例,让对迭代器有一定认知且掌握它背后的实现机制。