168

《深入理解ES6》阅读笔记 --- 迭代器(Iterator)和生成器(Generator) - 知乎专栏

 6 years ago
source link: https://zhuanlan.zhihu.com/p/29860223
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

《深入理解ES6》阅读笔记 --- 迭代器和生成器

是个厨子,热衷摩旅,同时编程;

这一小节的内容,比较鼓舞的是终于可以在JS语言层面,能看见Iterator和Generator了。说到迭代器,也许你会有疑问,可以预期的,你能看到Generator的实现也是依赖迭代器。我所接触到的编程语言中,最早让我理解这个特性的是Python。迭代器是一种特殊的对象,它的设计有专门的接口(描述)来完成我们常说的迭代(循环)过程。

每一个迭代器对象,都具备next方法(当然它也具备一些比如throw方法),当你执行next方法时,会返回一个(描述)对象,这个对象中,存在value,done属性,你想要的值就是value,而done则是用来描述整个迭代过程是否结束,可以想象,当迭代过程结束之后,value的值必然会是一个“空”(不同语言的描述不同),JS会给你一个undefined。

目前我们所知道的数组,Set,Map这三个集合是会存在迭代器,而且它们都有内建的迭代器:entries,values,keys。不知道,你在前几个小节中是否有印象Symbol中存在一个iterator属性,如果你想知道,不妨翻一翻之前分享的Symbol。在这里,我们可以通过Symbol.iterator来获取默认的迭代器(如果具备Symbol.iterator属性,那么意味着它必然具备默认的迭代器)。

let sets = [1,2,3]
let iterator = sets[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

如果你不想调用三下迭代器,你可以自己写一个小的循环,比如:

let res = iterator.next();
while(!res.done){
  res = iterator.next()
  console.log(res)
}

所谓的Generator也就是一种可以返回迭代器的函数,只不过它用*和yield来表示(描述过程)。

function * createIterator(){
   yield 1;
   yield 2;
}

let iterator = createIterator()

console.log(iterator.next())
console.log(iterator.next())

其实这很好理解,(语言)在背后帮我们对这个函数进行了包装,每一个yield都会返回一个迭代器对象,你想执行真正的逻辑,或者你想获取值,都需要通过这个迭代器对象来获取。

Generator可以辅助我们完成很多复杂的任务,而这些基础知识,又与iterator息息相关,举一个很简单的例子,相信有很多朋友,应该使用过co这个异步编程的库,它就是用Generator来实现,当然它的设计会比例子要复杂的多,我们先来看一个co简单的用法:

import co from 'co'
co(function* () {
  var result = yield Promise.resolve(true);
  return result;
}).then(function (value) {
  console.log(value);
}, function (err) {
  console.error(err.stack);
});

相应的,我们来实现一个简化的版本:

function co(task){
  let _task = task()
  let resl = _task.next();
  while(!resl.done){
    console.log(resl);
    resl = _task.next(resl.value);
  }
}

function sayName(){
  return {
    name: 'icepy'
  }
}

function assign *(f){
  console.log(f)
  let g = yield sayName()
  return Object.assign(g,{age:f});
}

co(function *(){
  let info = yield *assign(18)
  console.log(info)
})

虽然,这个例子中,还不能很好的看出来“异步”的场景,但是它很好的描述了Generator的使用方式。

从最开始的定义中,已经和大家说明了,Generator最终返回的依然是一个迭代器对象,有了这个迭代器对象,当你在处理某些场景时,你可以通过yield来控制,流程的走向。通过co函数,我们可以看出,先来执行next方法,然后通过一个while循环,来判断done是否为true,如果为true则代表整个迭代过程的结束,于是,这里就可以退出循环了。在Generator中的返回值,可以通过给next方法传递参数的方式来实现,也就是遇上第一个yield的返回值。

有逻辑,自然会存在错误,在Generator捕获错误的时机与执行throw方法的顺序有关系,一个小例子:

let hu = function *(){
  let g = yield 1;
  try {
    let j = yield 2;
  } catch(e){
    console.log(e)
  }
  return 34
}

let _it = hu();

console.log(_it.next())
console.log(_it.next())
console.log(_it.throw(new Error('hu error')))

当我能捕获到错误的时机是允许完第二次的yield,这个时候就可以try了。

Iterator和generator给了我们很多启发,在编程的维度上,我能想到的就是去处理异步代码时为我们提供便捷的方式。当然迭代器,这个方向上,可以做的事情有很多,如果你悉心去寻找,相信,很快能找到答案。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK