Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

迭代器模式 #65

Open
wangjing013 opened this issue Jan 9, 2022 · 0 comments
Open

迭代器模式 #65

wangjing013 opened this issue Jan 9, 2022 · 0 comments

Comments

@wangjing013
Copy link
Owner

wangjing013 commented Jan 9, 2022

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

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

为什么需要引入迭代器

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

例如数组 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() 方法进行遍历输出。

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

总结

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant