微信图片_20211224213416.jpg

今天我们来认真学习一下在ES6中出现的全新功能: 迭代器和生成器

为什么要加入这个新功能

我们之前使用for循环遍历时,如果多个嵌套需要追踪多个变量,代码复杂度也会增加。

迭代器的出现就是为了简化数据操作,消除这种复杂性并减少循环中的错误

什么是迭代器

  • 迭代器是一个对象

  • 对象有next()方法,调用该方法会返回一个对象,可接受参数用于代替生成器内部上一条yield语句的返回值,第一次调用传参无效

  • 返回的对象有value和done属性,value表示下一个将要返回的值;done用于表示是否已迭代完,默认值为false,迭代完其值为true

来造一个迭代器

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

什么是生成器

  • 生成器是返回迭代器的函数
  • 通过function关键字后面的*符号定义,两者可贴在一起也可空一格
  • 函数中会有yield关键字,通过它指定调用迭代器的next()方法时的返回值和返回顺序
  • 每执行完一条yield语句后函数就会自动停止执行,可以返回任何值或表达式
  • yield不能穿透函数边界
  • 不能用箭头函数创建生成器

它大概长啥样

// 长这样
function* createIterator(){
    yield 1
}
// 也可以长这样
let createIterator=function *(){
    yield 1
}
// 还可以这样
let o={
    createIterator: function *(){
        yield
    }
}
let iterator=createIterator()
console.log(iterator.next())

可迭代对象

  • 可迭代对象有Symbol.iterator属性,该属性通过指定函数可以返回一个作用于附属对象的迭代器
  • 在ES6中,所有的集合对象(数组、Set集合和Map集合)和字符串都是可迭代对象,都有默认的迭代器
  • 所有通过生成器创建的迭代器都是可迭代对象

访问默认迭代器

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

创建可迭代对象

默认情况下,开发者定义的对象都是不可迭代对象,但如果给Symbol.iterator属性添加一个生成器,则可以将其变成可迭代对象

let collection = {
    items: [],
    *[Symbol.iterator]() {
      for (let item of this.items) {
        yield item;
      }
    },
  };
  collection.items.push(1);
  collection.items.push(2);
  collection.items.push(3);
  for (let x of collection) {
    console.log(x);
  }

for-of

for-of通过调用可迭代对象的Symbol.iterator方法来获取迭代器,这一过程是由java引擎背后完成的

循环每执行一次都会调用得带起对象的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中,一直到done属性的值为true

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

内建迭代器

  • 数组、Map集合、Set集合都内建了一下三种迭代器

    • entries() 返回一个迭代器,其值为多个键值对,Map集合的默认迭代器
      • 调用next()返回一个数组,其中两个元素分别是键值

    可解构

    let data=new Map()
    data.set('name','yqx')
    for(let [key,value] of data.entries()){
        // do something
    }
    • values() 返回一个迭代器。其值为集合的值,数组和Set集合的默认迭代器
    • keys() 返回一个迭代器,其值为集合中的所有键名

字符串迭代器

我们知道可以通过类似数组下标的形式访问字符串中的某个字符,但由于方括号操作的是编码单元而非字符,因此无法正确访问双字节字符。

使用for-of可输出正确内容

NodeList迭代器

也拥有默认迭代器

展开运算符

展开运算符可以作用于任意可迭代对象,可将迭代对象转换为数组

let map = new Map([["name", "yqx"]]);
let set = new Set([1, 2, 3]);
console.log([...set]);
console.log([...map]);

在迭代器中抛出错误和处理

function* createIterator() {
  let first = yield 1;
  let second = yield first + 2;
}
let iterator = createIterator();
console.log(iterator.next());
console.log(iterator.throw(new Error('err')))
console.log(iterator.next(3));// 不会执行

处理

function* createIterator() {
    let first
    try{
        yield 1;
    }catch(ex){
        first=1
    }
   
    let second = yield first + 2;
  }
  let iterator = createIterator();
  console.log(iterator.next());
  console.log(iterator.throw(new Error('err')))
  console.log(iterator.next(3));

注意调用throw方法也会像掉哦那个next()方法一样返回一个结果对象

生成器返回值

在return语句可以指定一个返回值,该值将被赋值给返回对象的value属性

function* createIterator() {
    let first = yield 1;
    return 2
  }
  let iterator = createIterator();
  console.log(iterator.next());
  console.log(iterator.next());

委托生成器

可将两个迭代器合二为一,将生成数据的过程委托给其他迭代器

function* num() {
  yield 1;
}
function* color() {
  yield "red";
}
function* combined() {
  yield* num();
  yield* color();
}
let iterator = combined();
console.log(iterator.next());
console.log(iterator.next());

异步任务执行

let fs=require("fs")
function readFile(filename){
    return (callback)=>{
        fs.readFile(filename,callack)
    }
}
function run(taskDef) {
    let task= taskDef()
    let result=task.next()
    function step(){
        if(!result.done){
            if(typeof result.value==="function"){
                result.value(function(err,data){
                    if(err){
                        result=task.throw(err)
                        return;
                    }
                    result=task.next(data)
                    step()
                })
            }else {
                result=task.next(result.value)
                step()
            }
        }
    }
    step()
}