深入理解ES6的迭代器与生成器

循环语句的问题

var colors = ["red", "green", "blue"]; for(var i=0; i<colors.length; i++){ console.log(colors[i]); }

在ES6之前,这种标准的for循环,通过变量来跟踪数组的索引。如果多个循环嵌套就需要追踪多个变量,代码复杂度会大大增加,也容易产生错用循环变量的bug。

迭代器的出现旨在消除这种复杂性并减少循环中的错误。

什么是迭代器

我们先感受一下用ES5语法模拟创建一个迭代器:

function createIterator(items) { var i = 0; return { // 返回一个迭代器对象 next: function() { // 迭代器对象一定有个next()方法 var done = (i >= items.length); var value = !done ? items[i++] : undefined; return { // next()方法返回结果对象 value: value, done: done }; } }; } var iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false}" console.log(iterator.next()); // "{ value: 2, done: false}" console.log(iterator.next()); // "{ value: 3, done: false}" console.log(iterator.next()); // "{ value: undefiend, done: true}" // 之后所有的调用都会返回相同内容 console.log(iterator.next()); // "{ value: undefiend, done: true}"

以上,我们通过调用createIterator()函数,返回一个对象,这个对象存在一个next()方法,当next()方法被调用时,返回格式{ value: 1, done: false}的结果对象。

因此,我们可以这么定义:迭代器是一个拥有next()方法的特殊对象,每次调用next()都返回一个结果对象。

借助这个迭代器对象,我们来改造刚开始那个标准的for循环【暂时先忘记ES6的for-of循环新特性】:

var colors = ["red", "green", "blue"]; var iterator = createIterator(colors); while(!iterator.next().done){ console.log(iterator.next().value); }

what?,消除循环变量而已,需要搞这么麻烦,代码上不是得不偿失了吗?

并非如此,毕竟createIterator()只需写一次,就可以一直复用。不过ES6引入了生成器对象,可以让创建迭代器的过程变得更加简单。

什么是生成器

生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield。

function *createIterator(items) { for(let i=0; i<items.length; i++) { yield items[i]; } } let iterator = createIterator([1, 2, 3]); // 既然生成器返回的是迭代器,自然就可以调用迭代器的next()方法 console.log(iterator.next()); // "{ value: 1, done: false}" console.log(iterator.next()); // "{ value: 2, done: false}" console.log(iterator.next()); // "{ value: 3, done: false}" console.log(iterator.next()); // "{ value: undefiend, done: true}" // 之后所有的调用都会返回相同内容 console.log(iterator.next()); // "{ value: undefiend, done: true}"

上面,我们用ES6的生成,大大简化了迭代器的创建过程。我们给生成器函数createIterator()传入一个items数组,函数内部,for循环不断从数组中生成新的元素放入迭代器中,每遇到一个yield语句循环都会停止;每次调用迭代器的next()方法,循环便继续运行并停止在下一条yield语句处。

生成器的创建方式

生成器是个函数:

function *createIterator(items) { ... }

可以用函数表达式方式书写:

let createIterator = function *(item) { ... }

也可以添加到对象中,ES5风格对象字面量:

let o = { createIterator: function *(items) { ... } }; let iterator = o.createIterator([1, 2, 3]);

ES6风格的对象方法简写方式:

let o = { *createIterator(items) { ... } }; let iterator = o.createIterator([1, 2, 3]);

可迭代对象

在ES6中,所有的集合对象(数组、Set集合及Map集合)和字符串都是可迭代对象,可迭代对象都绑定了默认的迭代器。

来了来了,姗姗来迟的ES6循环新特性for-of:

var colors = ["red", "green", "blue"]; for(let color of colors){ console.log(color); }

for-of循环,可作用在可迭代对象上,正是利用了可迭代对象上的默认迭代器。大致过程是:for-of循环每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在变量中,循环将继续执行这一过程直到返回对象的done属性的值为true。

如果只需要迭代数组或集合中的值,用for-of循环代替for循环是个不错的选择。

访问默认迭代器

可迭代对象,都有一个Symbol.iterator方法,for-of循环时,通过调用colors数组的Symbol.iterator方法来获取默认迭代器的,这一过程是在JavaScript引擎背后完成的。

我们可以主动获取一下这个默认迭代器来感受一下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wydwdp.html