语句的扩展
Iterator
Iterator(遍历器)是一种协议,任何对象都可以部署遍历器协议,从而使得for…of循环可以遍历这个对象。
语法
Iterator(object, [keyOnly])
参数
概述
遍历器协议规定,任意对象只要部署了next方法,就可以作为遍历器,但是next方法必须返回一个包含value和done两个属性的对象。其中,value属性当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。
例子
基本用法
function makeIterator(array){ var nextIndex = 0; return { next: function(){ return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {done: true}; } } } var it = makeIterator(['a', 'b']); it.next().value // 'a' it.next().value // 'b' it.next().done // true
无限运行的遍历器
function idMaker(){ var index = 0; return { next: function(){ return {value: index++, done: false}; } } } var it = idMaker(); it.next().value // '0' it.next().value // '1' it.next().value // '2' // ...
for...of循环
JavaScript原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6提供for…of循环,允许遍历获得键值。可迭代对象(包括 Array, Map, Set, String, TypedArray,arguments 对象等等)。
语法
for (variable of object) { statement }
参数
- variable
每一次迭代,不同属性的属性值会被赋值给该变量。 - object
一个可迭代对象。
概述
for…of循环可以使用的范围包括数组、类似数组的对象(比如arguments对象、DOM NodeList对象)、Set和Map结构、后文的Generator对象,以及字符串。
例子
遍历 Array
let iterable = [20, 40, 60]; for (let value of iterable) { console.log(value); } // 20 // 40 // 60
如果你不修改语句块中的变量 , 也可以使用 const 代替 let .
let iterable = [20, 40, 60]; for (const value of iterable) { console.log(value); } // 20 // 40 // 60
遍历 String
let iterable = "foo"; for (let value of iterable) { console.log(value); } // "f" // "o" // "o"
遍历TypedArray
let iterable = new Uint8Array([0x00, 0xff]); for (let value of iterable) { console.log(value); } // 0 // 255
遍历Map
let iterable = new Map([["a", 2], ["b", 4], ["c", 6]]); for (let entry of iterable) { console.log(entry); } // [a, 2] // [b, 4] // [c, 6] for (let [key, value] of iterable) { console.log(value); } // 2 // 4 // 6
遍历 Set
let iterable = new Set([1, 1, 2, 2, 3, 3]); for (let value of iterable) { console.log(value); } // 1 // 2 // 3
遍历 DOM 集合
遍历Dom元素集合,比如一个NodeList对象: 下面的例子演示给每一个article标签的p子标签添加一个 “read” class.
let articleParagraphs = document.querySelectorAll("article > p"); for (let paragraph of articleParagraphs) { paragraph.classList.add("read"); }
遍历生成器
您还可以遍历一个 生成器::
// 注意: Firefox目前还不支持 "function*". // 删除该*号可以让下面的代码在Firefox 13中正常运行. function* fibonacci() { // 一个生成器函数 let [prev, curr] = [0, 1]; for (;;) { [prev, curr] = [curr, prev + curr]; yield curr; } } for (let n of fibonacci()) { // 当n大于1000时跳出循环 if (n > 1000) break; console.log(n); }
遍历另外的可遍历对象
您也可以遍历一个已经明确的可遍历(可迭代)协议。参见 iterable。
var iterable = { [Symbol.iterator]() { return { i: 0, next() { if (this.i < 3) { return { value: this.i++, done: false }; } return { value: undefined, done: true }; } }; } }; for (var value of iterable) { console.log(value); } // 0 // 1 // 2
for...of与for...in的区别
for…in循环会遍历一个object所有的可枚举属性。
for…of语法是为各种collection对象专门定制的,并不适用于所有的object.它会以这种方式迭代出任何拥有[Symbol.iterator] 属性的collection对象的每个元素。
下面的例子演示了for…of 循环和 for…in 循环的区别。for…in 遍历每一个属性名称,而 for…of遍历每一个属性值:
Object.prototype.objCustom = function () {}; Array.prototype.arrCustom = function () {}; let iterable = [3, 5, 7]; iterable.foo = "hello"; for (let i in iterable) { console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom" } for (let i of iterable) { console.log(i); // logs 3, 5, 7 }
Generator 函数
generator函数,需要在function关键字后面,加一个星号。然后,函数内部使用yield语句,定义遍历器的每个成员。
语法
function* gen() { yield a; yield b; yield c; } var g = gen(); // "Generator { }"
参数
概述
generator函数,作用就是返回一个内部状态的遍历器,主要特征是函数内部使用了yield语句。
当调用generator函数的时候,该函数并不执行,而是返回一个遍历器(可以理解成暂停执行)。以后,每次调用这个遍历器的next方法,就从函数体的头部或者上一次停下来的地方开始执行(可以理解成恢复执行),直到遇到下一个yield语句为止,并返回该yield语句的值。
例子
helloWorldGenerator
遍历器有两个成员“hello”和“world”。调用这个函数,就会得到遍历器。
function* helloWorldGenerator() { yield 'hello'; yield 'world'; }
yield有点类似于return语句,都能返回一个值。区别在于每次遇到yield,函数返回紧跟在yield后面的那个表达式的值,然后暂停执行,下一次从该位置继续向后执行,而return语句不具备位置记忆的功能。
调用这个函数,就会得到遍历器。
var hw = helloWorldGenerator();
执行遍历器的next方法,则会依次遍历每个成员。
hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: undefined, done: true } hw.next() // Error: Generator has already finished // at GeneratorFunctionPrototype.next (native) // at repl:1:3 // at REPLServer.defaultEval (repl.js:129:27) // ...
上面代码一共调用了四次next方法。
- 第一次调用:函数开始执行,直到遇到第一句yield语句为止。next方法返回一个对象,它的value属性就是当前yield语句的值hello,done属性的值false,表示遍历还没有结束。
- 第二次调用:函数从上次yield语句停下的地方,一直执行到下一个yield语句。next方法返回一个对象,它的value属性就是当前yield语句的值world,done属性的值false,表示遍历还没有结束。
- 第三次调用:函数从上次yield语句停下的地方,一直执行到函数结束。next方法返回一个对象,它的value属性就是函数最后的返回值,由于上例的函数没有return语句(即没有返回值),所以value属性的值为undefined,done属性的值true,表示遍历已经结束。
- 第四次调用:由于此时函数已经运行完毕,next方法直接抛出一个错误。
遍历器的本质,其实是使用yield语句暂停执行它后面的操作,当调用next方法时,再继续往下执行,直到遇到下一个yield语句,并返回该语句的值,如果直到运行结束。
如果next方法带一个参数,该参数就会被当作上一个yield语句的返回值。
function* f() { for(var i=0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }
上面代码先定义了一个可以无限运行的generator函数f,如果next方法没有参数,正常情况下返回一个递增的i;如果next方法有参数,则上一次yield语句的返回值将会等于该参数。如果该参数为true,则会重置i的值。
generator函数的这种暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
function* loadUI() { showLoadingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen(); }
上面代码表示,第一次调用loadUI函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示登录窗口,并且异步加载数据。再一次使用next方法,则会隐藏登录窗口。可以看到,这种写法的好处是所有登录窗口的逻辑,都被封装在一个函数,按部就班非常清晰。
斐波那契数列
function* fibonacci() { var previous = 0, current = 1; while (true) { var temp = previous; previous = current; current = temp + current; yield current; } } for (var i of fibonacci()) { console.log(i); } // 1, 2, 3, 5, 8, 13, ...,
下面是利用for…of语句,对斐波那契数列的另一种实现。
function* fibonacci() { let [prev, curr] = [0, 1]; for (;;) { [prev, curr] = [curr, prev + curr]; yield curr; } } for (n of fibonacci()) { if (n > 1000) break; console.log(n); } 从上面代码可见,使用for…of语句时不需要使用next方法。
这里需要注意的是,yield语句运行的时候是同步运行,而不是异步运行(否则就失去了取代回调函数的设计目的了)。实际操作中,一般让yield语句返回Promises对象。例子返回的就是一个Promises对象。
var Q = require('q'); function delay(milliseconds) { var deferred = Q.defer(); setTimeout(deferred.resolve, milliseconds); return deferred.promise; } function *f(){ yield delay(100); };
如果有一系列任务需要全部完成后,才能进行下一步操作,yield语句后面可以跟一个数组。下面就是一个例子。
function *f() { var urls = [ 'http://example.com/', 'http://twitter.com/', 'http://bbc.co.uk/news/' ]; var arrayOfPromises = urls.map(someOperation); var arrayOfResponses = yield arrayOfPromises; this.body = "Results"; for (var i = 0; i < urls.length; i++) { this.body += '\n' + urls[i] + ' response length is ' + arrayOfResponses[i].body.length; } };