js之迭代器与生成器

135 阅读4分钟

迭代器

迭代器概念

迭代器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

可迭代协议

实现Iterable接口要求同时具备两种能力:支持迭代的自我识别能力和创建实现Iterator接口的对象的能力。必须暴露一个属性作为默认迭代器,且这个属性必须使用特殊的[Symbol.Iterator]作为键。这个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数返回一个新的迭代器。

很多内置数据类型都实现了Iterable接口:字符串、数组、映射、集合等

在时机写代码过程中,不需要显示调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。接收可迭代对象的原生语言特性包括:

for-of循环、数组结构、扩展操作符、yield* 操作符等

迭代器协议

迭代器API使用next()方法在可迭代对象中遍历数据。每次成功调用next()都会返回IteratorResult对象,其中包含迭代器的下一个值。

next方法返回的迭代器对象包含两个属性:done和value。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

class Counter{
    constructor(limit){
        this.limit = limit;
    }
    [Symbol.iterator](){
        let count = 1,limit = this.limit;
        return {
            next(){
                if(count > limit) return {value:undefined,done:true};
                return {value:count ++,done:false};
            }
        }
    }
}
let cnt = new Counter(3);
for(let i in cnt){
    console.log(i); // 1,2,3
}

提前终止迭代器

可选的return方法用于指定在迭代器提前关闭时执行的逻辑。

for-of循环通过return,break,continue,或throw提前退出。

class Counter{
    constructor(limit){
        this.limit = limit;
    }
    [Symbol.iterator](){
        let count = 1,limit = this.limit;
        return {
            next(){
                if(count > limit) return {value:undefined,done:true};
                return {value:count ++,done:false};
            },
            return(){
            	console.log("exiting");
            	return {done:true};
            }
        }
    }
}
let cnt = new Counter(3);
for(let i in cnt){
	if(i > 2) break;
    console.log(i); 
}
1
2
exiting

生成器

生成器是ES6提供的一种异步编程的解决方案。拥有一个函数快内暂停和恢复代码执行的能力。

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。

function* generator() {
  console.log(1);
  yield '11';
  console.log(2);
  yield '22';
  console.log(3);
  return '33';
  console.log(4);
}

var hw = generator(); // 1
console.log(hw.next()); //2 { value: '11', done: false }
console.log(hw.next()); //3 { value: '22', done: false }
console.log(hw.next()); // { value: '33', done: true }
console.log(hw.next()); //{ value: undefined, done: true }

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

next方法的参数

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* f() {
  for(var i = 0; true; i++) {
    var a = yield i;
      console.log(a);
    if(!a) { break; }
  }
}

var g = f();

g.next(true) // { value: 0, done: false }
g.next(true) // { value: 1, done: false }
g.next(false) // { value: undefined, done: true }

迭代器与生成器的关系

由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

var iter = {};
iter[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...iter] // [1, 2, 3]

常见面试题

Iterator 和 for-of 循环

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象

例子:

let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        }
        return { value: undefined, done: true };
      }
    };
  }
};
for (let item of obj){
    console.log(item); 
}
// 
hello
world