语句的扩展

 

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;
  }
};