【ES6 笔记】迭代器和生成器

2018-11-06  本文已影响0人  ___Jing___

迭代器

概念:迭代器是一种特殊对象,包含一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;一个是done,布尔值,当没有更多可返回的数据时返回true。

function createIterator(items){
    var i = 0 ;
    return {
        next : function (){
            var done = (i>=items.length);
            var value = !done?items[i++]:undefined;
            return {
                done,
                value
            }
        }
    }
}

var iterator = createIterator([1,2]) ; 

console.log(iterator.next()); // {done: false, value: 1}
console.log(iterator.next()); // {done: false, value: 2}
//之后所有的调用都会返回相同的内容
console.log(iterator.next());  // {done: true, value: undefined}

上面只是原理,ES6中引入了生成器来简化创建迭代器的过程

生成器

概念:生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到关键字yield。星号可以紧挨着function关键字,也可以在中间添加一个空格:

// 生成器
function *createIterator(){
    yield 1;
    yield 2;
}
// 生成器的调用方式与普通函数相同,只不过返回的是一个迭代器
var iterator = createIterator() ; 

console.log(iterator.next());  //{value: 1, done: false}
console.log(iterator.next());  //{value: 2, done: false}
//之后所有的调用都会返回相同的内容
console.log(iterator.next());  //{value: undefined, done: true}

在这个例子中,星号表示这是一个生成器;yield关键字指定调用迭代器的next()的返回值和返回顺序。
没当执行完一句yield语句后函数会自动停止执行。

function *createIterator(items){
    for( let i=0; i<items.length; i++ ){
        yield items[i]
    }
}

let iterator = createIterator([1,2]) ; 

console.log(iterator.next()); // {done: false, value: 1}
console.log(iterator.next()); // {done: false, value: 2}
//之后所有的调用都会返回相同的内容
console.log(iterator.next());  // {done: true, value: undefined}

yield关键字只可在生成器内部使用,嵌套的非生成器函数不算内部

let createIterator = function *(items){
    for( let i=0; i<items.length; i++ ){
        yield items[i]
    }
}

let iterator = createIterator([1,2]) ; 

console.log(iterator.next()); // {done: false, value: 1}
console.log(iterator.next()); // {done: false, value: 2}
//之后所有的调用都会返回相同的内容
console.log(iterator.next());  // {done: true, value: undefined}

不能用箭头函数来创建生成器

let obj = {
    createIterator : function *(items){
        for( let i=0; i<items.length; i++ ){
            yield items[i]
        }
    }
}

let iterator = obj.createIterator([1,2]) ; 

简写法:

let obj = {
    *createIterator(items){
        for( let i=0; i<items.length; i++ ){
            yield items[i]
        }
    }
}

let iterator = obj.createIterator([1,2]) ; 

可迭代对象和for-of循环

可迭代对象具有Symbol.iterator属性,通过制定的函数可以返回一个作用于附属对象的迭代器。
由于生成器默认会为Symbol.iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象。
for-of循环每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中,循环将持续执行这一过程直到返回对象的done属性的值为true。

let values = [ 1, 2, 3 ]

for(let num of values){
    console.log(num)
}
// 分别输出1、2、3

如果将for-of语句用于不可迭代对象、null或undefined将会导致程序抛出错误

let values = [ 1, 2, 3 ];
let iterator = values[Symbol.iterator]();

console.log(iterator.next()); // { value : 1, done : false }
console.log(iterator.next()); // { value : 2, done : false }
console.log(iterator.next()); // { value : 3, done : false }
console.log(iterator.next()); // { value : undefined, done : true }

由于具有Symbol.iterator属性的对象都有默认的迭代器,因此可以用它来检测对象是否为可迭代对象:

function isIterable(object){
      return typeof object[Symbol.iterator] === 'function';
}

console.log( isIterable([1,2,3]) )  // true
console.log( isIterable("Hello") )  // true
console.log( isIterable(new WeakMap()) )  // false
let collection = {
      items : [],
      *[Symbol.iterator](){
            for(let item of this.items){
                  yield item
            }  
       }
}

collection.items.push(1);
collection.items.push(2);

for (let x of collection){
    console.log(x)
}
// 分别输出1、2

内建迭代器

let colors = ['red'];
let numbers = new Set([1234]);
let maps = new Map([['key','value']]);

for (let entry of colors.entries()){
    console.log(entry); // [0, "red"]
}
for (let entry of numbers.entries()){
    console.log(entry); //[1234, 1234]
}
for (let entry of maps.entries()){
    console.log(entry); //["key", "value"]
}

values()
调用values()迭代器时返回集合中所存在的值:

let colors = ['red'];
let numbers = new Set([1234]);
let maps = new Map([['key','value']]);

for (let entry of colors.values()){
    console.log(entry); //"red"
}
for (let entry of numbers.values()){
    console.log(entry); //1234
}
for (let entry of maps.values()){
    console.log(entry); //value
}

keys()
调用keys()迭代器时返回集合中存在的每一个键:

let colors = ['red'];
let numbers = new Set([1234]);
let maps = new Map([['key','value']]);

for (let entry of colors.keys()){
    console.log(entry); // 0 
}
for (let entry of numbers.keys()){
    console.log(entry); // 1234
}
for (let entry of maps.keys()){
    console.log(entry); // ‘key’
}

不同集合类型的默认迭代器
每个集合类型都有一个默认迭代器,在for-of循环中,如果没有显式指定则使用默认的迭代器。
数组和Set集合的默认迭代器是values()方法
Map集合的默认迭代器是entries()方法

let colors = ['red'];
let numbers = new Set([1234]);
let maps = new Map([['key','value']]);

for (let entry of colors){
    console.log(entry); // red
}
for (let entry of numbers){
    console.log(entry); // 1234
}
for (let entry of maps){
    console.log(entry); // ["key", "value"]
}

结构与for-of循环
如果要在for-of循环中使用结构语法,则可以利用Map集合默认构造函数的行为来简化编码过程:

let data = new Map();
data.set('name','欧阳不乖')
data.set('age',18)

for(let [key, value] of data){
    console.log(`${key} = ${value}`)
}
// name = 欧阳不乖
// age = 18
let message = 'Hi 欧阳不乖';
for(let w of message){
  console.log(w)
}
//H
// i
// (空)
//欧
//阳
//不
//乖
let divs = document.getElementByTags('div');
for (let div of divs){
    console.log(div.id)
}

展开运算符与非数组可迭代对象

在数组字面量中可以多次使用展开运算符,将可迭代对象中的多个元素依次插入新数组中,替换原先展开运算符所在的位置:

let smallNumbers = [ 1, 2, 3 ];
let bigNumbers = [ 100, 101, 102 ];
let allNumbers = [0, ...smallNumbers, ...bigNumbers];

console.log(allNumbers); // [0, 1, 2, 3, 100, 101, 102]

高级迭代器功能

function *createIterator(){
    let first = yield 1;
    let second = yield first +2;
    yield second +3;
}
let iterator = createIterator();

console.log( iterator.next() );  // {value: 1, done: false}
console.log( iterator.next(4) );  // {value: 6, done: false}
console.log( iterator.next(5) );  // {value: 8, done: false}
console.log( iterator.next() );  // {value: undefined, done: true}

这里有一个特例,第一次调用next()方法时无论传入什么样的参数都会被丢弃。
让我们从下面的代码分解步骤,来理解一下代码内部的具体细节:


执行步骤
function *createIterator(){
    let first = yield 1;
    let second = yield first +2;
    yield second +3;
}
let iterator = createIterator();

console.log( iterator.next() );  // {value: 1, done: false}
console.log( iterator.next(4) );  // {value: 6, done: false}
console.log( iterator.throw(new Error('BOOM')) );  //浏览器抛出错误:Uncaught Error: BOOM
console.log( iterator.next() );  //这里什么都不会再发生了

如下图所示,错误抛出则立即阻止了后续代码的执行,红色背景区域的代码永远都不会被执行了:


错误抛出位置示意图
function *createIterator(){
    yield 1;
    return 'finished';  //之后的代码将不再执行
    yield 2;
    yield 3;
}
let iterator = createIterator();

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: "finished", done: true} 
console.log(iterator.next()); // {value: undefined, done: true}

通过return语句指定的返回值,只会在返回对象中出现一次,在后续调用返回的对象中,value属性会被重置为undefined。
展开运算符与for-of循环语句会直接忽略通过return语句指定的任何返回值,只要done一变为true就立即停止读取其他的值。

function *createNumberIterator(){
    yield 1;
    yield 2
}
function * createColorIterator(){
    yield 'red';
    yield 'blue';
}
function *createCombinedIterator(){
    yield *createNumberIterator();
    yield *createColorIterator();
    yield true;
}
let iterator = createCombinedIterator();

console.log( iterator.next() );  // {value: 1, done: false}
console.log( iterator.next() );  // {value: 2, done: false} 
console.log( iterator.next() );  // {value: "red", done: false}
console.log( iterator.next() );  // {value: "blue", done: false}
console.log( iterator.next() );  // {value: undefined, done: true}

yield *也可以直接应用于字符串,例如 yield *‘hello’

异步任务执行

function run(taskDef){
    // 创建一个无使用限制的迭代器
    let task = taskDef();
    // 开始执行任务
    let result = task.next();
    // 循环调用next()的函数
    function step(){
        // 如果任务尚未完成,则继续执行
        if(!result.done){
              result = task.next();
        }
    }
    // 开始迭代执行
    step();
}

run(function *(){
    console.log(1);
    yield;
    console.log(2)
})
// 1
// 2
function run(taskDef){
    // 创建一个无使用限制的迭代器
    let task = taskDef();
    // 开始执行任务
    let result = task.next();
    // 循环调用next()的函数
    function step(){
        // 如果任务尚未完成,则继续执行
        if(!result.done){
            result = task.next(result.value);
            step();
        }
    }
    // 开始执行迭代
    step();
}

run(function *(){
    let value = yield 1;
    console.log(value);  // 1

    value = yield  value+3;
    console.log(value)  // 4
})
上一篇下一篇

猜你喜欢

热点阅读