EcmaScript 2015 (又称ES6)提供一个全新的迭代器
的概念,它容许咱们在语言层面上定义一个(有限或无限的)序列。es6
暂时先抛开它。咱们对于for
循环以及它的兄弟for-in
循环,都已经十分的熟悉。后者能够被用来帮助咱们理解迭代器。数组
jsfor (var key in table) { console.log(key + ' = ' + table[key]); }
对于for-in
循环,它有许多的问题。可是最大的问题,即是它不保证迭代的顺序。可是当咱们使用ES6迭代器时,这个问题就迎刃而解了。函数
for-of
是ES6中的新语法,用来配合迭代器。oop
jsfor (var key of table) { console.log(key + ' = ' + table[key]); }
使用for-of
,咱们获得的是一个能够保证顺序的迭代。为了让一个对象能够被迭代器所迭代,对象须要实现一个“迭代协议”,即拥有一个Symbol.iterator
属性。这个属性会被for-of
所使用,在咱们的例子中,它就是table[Symbol.iterator]
。性能
Symbol.iterator
也是在ES6中新增的内容,咱们会在另外一篇文章中详细讨论。在这里,咱们只需认为它是对象的一个特殊属性,而且永远不会和其余普通属性产生冲突。优化
table[Symbol.iterator]
的值,必须是一个符合“迭代协议”的函数,即它须要返回一个相似于{ next: function () {} }
的对象。this
jstable[Symbol.iterator] = function () { return { next: function () {} } }
而后,在for-of
循环每次调用next()
函数时,它须要返回一个相似于{value: …, done: [true/false]}
的对象。因此,一个迭代器的完整实现相似于以下的例子:rest
jstable[Symbol.iterator] = function () { var keys = Object.keys(this).sort(); var index = 0; return { next: function () { return { value: keys[index], done: index++ >= keys.length }; } } }
迭代器容许咱们在第一次调用next()
函数以后,再执行相应的逻辑。在上面的例子里,当咱们调用迭代器的瞬间,咱们就马上执行了排序和取值的工做。可是,若是next()
函数永远不被调用的话,咱们就浪费了性能。因此让咱们来优化它:code
jstable[Symbol.iterator] = function () { var _this = this; var keys = null; var index = 0; return { next: function () { if (keys === null) { keys = Object.keys(_this).sort(); } return { value: keys[index], done: index++ >= keys.length }; } } }
for-of
和for-in
的差异对象
理解for-of
和for-in
之间的差异,是十分重要的。如下是一个简单的,可是很是好的解释差异的例子:
jsvar list = [3, 5, 7]; list.foo = 'bar'; for (var key in list) { console.log(key); // 0, 1, 2, foo } for (var value of list) { console.log(value); // 3, 5, 7 }
正如所见的,for-of
循环仅打印出了数组中的值,忽略了其余属性。这是由于数组的迭代器只返回其中预期的元素。
String
,Array
,TypedArray
,Map
和Set
都是内置迭代器,由于它们的原型中都有一个Symbol.iterator
方法。
jsvar string = "hello"; for (var chr of string) { console.log(chr); // h, e, l, l, o }
解构操做一样也接受一个迭代器:
jsvar hello = 'world'; var [first, second, ...rest] = [...hello]; console.log(first, second, rest); // w o ["r","l","d"]
只要永远不返回done: true
,就实现了一个无限迭代器。固然,须要极力避免出现这种状况。
jsvar ids = { *[Symbol.iterator]: function () { var index = 0; return { next: function () { return { value: 'id-' + index++, done: false }; } }; } }; var counter = 0; for (var value of ids) { console.log(value); if (counter++ > 1000) { // let's make sure we get out! break; } }
若是你还不了解ES6 generator
函数,请参考MDN文档。简而言之,generator
函数是当前被谈论最多的ES6特性,它是一个能够暂时退出,而且稍后从新进入继续执行的函数。在屡次的进入中,它的上下文(绑定的变量)是会被保存的。generator
函数自身就是一个迭代器,来看下面的例子:
jsfunction* list(value) { for (var item of value) { yield item; } } for (var value of list([1, 2, 3])) { console.log(value); } var iterator = list([1, 2, 3]); console.log(typeof iterator.next); // function console.log(typeof iterator[Symbol.iterator]); // function console.log(iterator.next().value); // 1 for (var value of iterator) { console.log(value); // 2, 3 }
因此,咱们能够使用generator
函数重写咱们上面的迭代器:
jstable[Symbol.iterator] = function* () { var keys = Object.keys(this).sort(); for (var item of keys) { yield item; } }
迭代器给JavaScript
中的循环,generator
函数和值序列(value series)带来了一个新的维度。你能够使用它,定义一个类中,它的值的排序方式,也能够用经过其来建立一个惰性的或无限的序列,等等。
https://strongloop.com/strongblog/introduction-to-es6-iterators/