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'
// ...
JavaScript原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6提供for…of循环,允许遍历获得键值。可迭代对象(包括 Array, Map, Set, String, TypedArray,arguments 对象等等)。
for (variable of object) {
statement
}
for…of循环可以使用的范围包括数组、类似数组的对象(比如arguments对象、DOM NodeList对象)、Set和Map结构、后文的Generator对象,以及字符串。
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
let iterable = "foo";
for (let value of iterable) {
console.log(value);
}
// "f"
// "o"
// "o"
let iterable = new Uint8Array([0x00, 0xff]);
for (let value of iterable) {
console.log(value);
}
// 0
// 255
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
let iterable = new Set([1, 1, 2, 2, 3, 3]);
for (let value of iterable) {
console.log(value);
}
// 1
// 2
// 3
遍历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…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函数,需要在function关键字后面,加一个星号。然后,函数内部使用yield语句,定义遍历器的每个成员。
function* gen() {
yield a;
yield b;
yield c;
}
var g = gen(); // "Generator { }"
generator函数,作用就是返回一个内部状态的遍历器,主要特征是函数内部使用了yield语句。
当调用generator函数的时候,该函数并不执行,而是返回一个遍历器(可以理解成暂停执行)。以后,每次调用这个遍历器的next方法,就从函数体的头部或者上一次停下来的地方开始执行(可以理解成恢复执行),直到遇到下一个yield语句为止,并返回该yield语句的值。
遍历器有两个成员“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方法时,再继续往下执行,直到遇到下一个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;
}
};