brunoyang / blog

134 stars 13 forks source link

JS 与迭代器 #21

Open brunoyang opened 8 years ago

brunoyang commented 8 years ago

JavaScript 的历史上,先有两种集合,ArrayObject,ES6 中新加了两种集合,MapSet。遍历 Array 可以用 for 循环或 forEach 等 ES5 中提供的遍历方法,Object 使用 for...inMapSet 使用 for...of。可以预见,如果不添加一个统一的遍历接口,数据结构改变就需要不同的遍历方法,势必会越来越混乱。所以,ES6 中添加了迭代器(Iterator)这一机制,为不同的数据结构提供统一的访问接口。

什么是迭代器

所谓迭代器,就是一种封装,封装了获取集合中某个值的操作,而屏蔽了『如何拿到某个值』的细节。举个例子,如果想获取对象和数组的第一个值,数组的取值方法是 arr[0], 对象的取值方法是 for (const v in obj) obj[v]。为了屏蔽差异,我们需要设计一种通用的方法。一种做法就是往这些集合上增加个方法,该方法可以拿到集合中的值,取值时只需要调用这个方法就可以了。

使用迭代器

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 }
iterator.next() // { value: undefined, done: true }

上面短短几行中,有几点是需要注意的:

一遍一遍地写 .next() 相当低效,所以 ES6 引入了 for...of 帮我们自动遍历。for...of 会自动调用迭代器接口,类似这样:

const arr = [1,2,3]
for (const v of arr) {
  console.log(v) // 1, 2, 3
}

自己实现个迭代器

四大集合,ArraySetMap 都有迭代器接口,独独少了 Object,据说是为了以后考虑。不过虽说官方没有默认提供,我们可以手动添加,自己实现个迭代器:

const foo = {
  a: 1,
  b: 2,
  c: 3,
}

Object.defineProperty(foo, Symbol.iterator, {
  configurable: false,
  enumerable: false,
  writable: false,
  get: () => {
    let index = -1
    const values = Object.entries(foo)
    const len = values.length
    return () => ({
      next: () => {
        index++
        return {
          value: values[index] ? values[index][1] : undefined,
          done: index >= len ? true : false,
        }
      }
    })
  }
})

for (const v of foo) {
  console.log(v) // 1, 2, 3
}

done, for...of 识别出了我们自定义的迭代器!

迭代器与 generator

{ value: xx, done: false } 这样的对象感觉很眼熟... 我们来对比一下数组和 generator function:

const arr = [1,2,3]
const iterator = arr[Symbol.iterator]()
iterator.next() // { value: 1, done: false }

// ----------------------

function * iterFunc () {
  let i = 1
  while (true) {
    yield i++
  }
}
const gen = iterFunc()
gen.next() // { value: 1, done: false }

简直一毛一样。

而且,generator 函数还可以用来部署迭代器

function* makeIterator(obj){
  var nextIndex = 0;
  var values = Object.entries(obj)

  while(nextIndex < values.length){
    yield values[nextIndex++][1];
  }
}

var gen = makeIterator({
  a: 1, 
  b: 2,
});

for (const v of gen) {
  console.log(v); // 1, 2
}

迭代器的其他用途

其实 ES6 中迭代器无处不在,随便列举几个

rest 参数:

function rest (...args) {
  for (const v of args) {
    console.log(args)
  }
}

解构赋值:

const [a, b] = 'a.b'.split('.')

const [c, d] = 'a.b.c'.split('.')
// c: 'a', d: ['b', 'c']

还可以对类数组比如字符串等操作:

[...'abc'] // 'a', 'b', 'c'
AyumiKai commented 8 years ago

已撸