var colors = ["red", "green", "blue"]; for(var i=0; i<colors.length; i++){ console.log(colors[i]); }
在ES6
以前,这种标准的for循环,经过变量来跟踪数组的索引。若是多个循环嵌套就须要追踪多个变量,代码复杂度会大大增长,也容易产生错用循环变量的bug。javascript
迭代器的出现旨在消除这种复杂性并减小循环中的错误。java
咱们先感觉一下用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循环新特性】:this
var colors = ["red", "green", "blue"]; var iterator = createIterator(colors); while(!iterator.next().done){ console.log(iterator.next().value); }
what?,消除循环变量而已,须要搞这么麻烦,代码上不是得不偿失了吗?
并不是如此,毕竟createIterator()只需写一次,就能够一直复用。不过ES6
引入了生成器对象,可让建立迭代器的过程变得更加简单。spa
生成器是一种返回迭代器的函数,经过function
关键字后的星号(*)来表示,函数中会用到新的关键字yield
。code
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
语句处。orm
生成器是个函数:对象
function *createIterator(items) { ... }
能够用函数表达式方式书写:blog
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
引擎背后完成的。
咱们能够主动获取一下这个默认迭代器来感觉一下:
let values = [1, 2, 3]; let iterator = values[Symbol.iterator](); 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: undefined, done: true}"
在这段代码中,经过Symbol.iterator获取了数组values的默认迭代器,并用它遍历数组中的元素。在JavaScript引擎中执行for-of循环语句也是相似的处理过程。
用Symbol.iterator属性来检测对象是否为可迭代对象:
function isIterator(object) { return typeof object[Symbol.iterator] === "function"; } console.log(isIterable([1, 2, 3])); // true console.log(isIterable(new Set())); // true console.log(isIterable(new Map())); // true console.log(isIterable("Hello")); // true
当咱们在建立对象时,给Symbol.iterator属性添加一个生成器,则能够将其变成可迭代对象:
let collection = { items: [], *[Symbol.iterator]() { // 将生成器赋值给对象的Symbol.iterator属性来建立默认的迭代器 for(let item of this.items) { yield item; } } }; collection.items.push(1); collection.items.push(2); collection.items.push(3); for(let x of collection) { console.log(x); }
ES6
中的集合对象,数组、Set
集合和Map
集合,都内建了三种迭代器:
entries() 返回一个迭代器,其值为多个键值对。
若是是数组,第一个元素是索引位置;若是是Set
集合,第一个元素与第二个元素同样,都是值。
values() 返回一个迭代器,其值为集合的值。
keys() 返回一个迭代器,其值为集合中的全部键名。
若是是数组,返回的是索引;若是是Set
集合,返回的是值(Set
的值被同时用做键和值)。
每一个集合类型都有一个默认的迭代器,在for-of循环中,若是没有显式指定则使用默认的迭代器。按常规使用习惯,咱们很容易猜到,数组和Set
集合的默认迭代器是values(),Map
集合的默认迭代器是entries()。
请看如下示例:
let colors = [ "red", "green", "blue"]; let tracking = new Set([1234, 5678, 9012]); let data = new Map(); data.set("title", "Understanding ECMAScript 6"); data.set("format", "print"); // 与调用colors.values()方法相同 for(let value of colors) { console.log(value); } // 与调用tracking.values()方法相同 for(let num of tracking) { console.log(num); } // 与调用data.entries()方法相同 for(let entry of data) { console.log(entry); }
这段代码会输入如下内容:
"red" "green" "blue" 1234 5678 9012 ["title", "Understanding ECMAScript 6"] ["format", "print"]
for-of循环配合解构特性,操纵数据会更方便:
for(let [key, value] of data) { console.log(key + "=" + value); }
let set = new Set([1, 2, 3, 4, 5]), array = [...set]; console.log(array); // [1,2,3,4,5]
展开运算符能够操做全部的可迭代对象,并根据默认迭代器来选取要引用的值,从迭代器读取全部值。而后按返回顺序将它们依次插入到数组中。所以若是想将可迭代对象转换为数组,用展开运算符是最简单的方法。
前面咱们看到,在迭代器内部使用yield关键字能够生成值,在外面能够用迭代器的next()方法得到返回值。
其实next()方法还能够接收参数,这个参数的值就会代替生成器内部上一条yield语句的返回值。
function *createIterator() { let first = yield 1; let second = yield first + 2; // 4 + 2 yield second + 3; // 5 + 3 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.next(5)); // "{ value: 8, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
下图的阴影展现了每次yield前正在执行的代码,能够辅助理解程序内部的具体细节:
在生成器内部,浅红色高亮的是next()方法的第一次调用,浅绿色标识了next(4)的调用过程,紫色标示了next(5)的调用过程,分别返回每一次yield生成的值。这里有一个过程很复杂,在执行左侧代码前,右侧的每个表达式会先执行再中止。
这里有个特例,第一次调用next()方法时不管传入什么参数都会被丢弃。因为传递给next()方法的参数会代替上一次yield的返回值,而在第一次调用next()方法前不会执行任何yield语句,所以在第一次调用next()方法时传递参数是毫无心义的。
除了给迭代器传递数据外,还能够给它传递错误条件。经过throw()方法,当迭代器恢复执行时可令其抛出一个错误。
function *createIterator() { let first = yield 1; let second = yield first + 2; // yield 4 + 2, 而后抛出错误 yield second + 3; // 永远不会被执行 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // 从生成器中抛出错误
这个示例中,前两个表达式正常求值,而调用throw()后,在继续执行let second求值前,错误就会被抛出并阻止代码继续执行。
知道了这一点,就能够在生成器内部经过try-catch代码块来捕获这些错误:
function *createIterator() { let first = yield 1; let second; try { second = yield first + 2; // yield 4 + 2, 而后抛出错误 } catch(e) { second = 6; } yield second + 3; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
这里有个有趣的现象:调用throw()方法后也会像调用next()方法同样返回一个结果对象。因为在生成器内部捕获了这个错误,于是会继续执行下一条yield语句,最终返回数值9。
如此一来,next()和throw()就像是迭代器的两条指令,调用next()方法命令迭代器继续执行(可能提供一个值),调用throw()方法也会命令迭代器继续执行,但同时抛出一个错误,在此以后的执行过程取决于生成器内部的代码。
因为生成器也是函数,所以能够经过return语句提早退出函数执行。
function *createIterator() { yield 1; return; yield 2; yield 3; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
这段代码中的生成器包含多条yield语句和一条return语句,其中return语句紧随第一条yield语句,其后的yield语句将不会被执行。
在return语句中也能够指定一个返回值:
function *createIterator() { yield 1; return 10; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 10, done: true }" console.log(iterator.next()); // "{ value: undefined, done: true }"
经过return语句指定的返回值,只会在返回对象中出现一次,在后续调用返回的对象中,value属性会被重置为undefined。
在某些状况下,须要将两个迭代器合二为一,这时能够建立一个生成器,再给yield语句添加一个星号,就能够将生成数据的过程委托给其余生成器。
function *createNumberIterator() { yield 1; yield 2; } function *createColorIterator() { yield "red"; yield "green"; } function *createCombinedIterator() { yield *createNumberIterator(); yield *createColorIterator(); yield true; } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "red", done: false }" console.log(iterator.next()); // "{ value: "green", done: false }" console.log(iterator.next()); // "{ value: true, done: false }" console.log(iterator.next()); // "{ value: undfined, done: true }"
有了委托生成器这个信功能,你能够进一步利用生成器的返回值来处理复杂任务:
function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for(let i=0; i<count; i++) { yield "repeat"; } } function *createCombinedIterator() { let result = yield *createNumberIterator(); yield *createRepeatingIterator(result); } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: undfined, done: true }"
注意,不管经过何种方式调用迭代器的next()方法,数值3永远不会被返回,它只存在于生成器createCombinedIterator()的内部。但若是想输出这个值,则能够额外添加一条yield语句:
function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for(let i=0; i<count; i++) { yield "repeat"; } } function *createCombinedIterator() { let result = yield *createNumberIterator(); yield result; // 这里加一句yield yield *createRepeatingIterator(result); } var iterator = createCombinedIterator(); 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: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: undfined, done: true }"