深入理解ES6 008【学习笔记】

迭代器(Iterator)和生成器(Generator)

这个新特性对于高效的数据处理而言是不可或缺的,你也会发现在语言的其他特性中也都有迭代器的身影:新的for-of循环、展开运算符(...)、甚至连异步编程都可以使用迭代器。

迭代器是一种特殊的对象,它具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象(形如:{value:true|false,next(){}})。

function createIterator(items){
  var i = 0;
  return {
    next:function(){
      let done = (i>=items.length);
      let value = !done?items[i++];undefined;
      return {
        done,
        value
      }
    }
  }
}
var iterator = createIterator([1,2,3])
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}
// 之后所有调用都会返回相同内容
// {value:undefined,done:true}

生成器

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

// 生成器
function *createIteator(){
  yield 1;
  yield 2;
  yield 3;
}

// 生成器的调用方式与普通函数相同,只不过返回的是一个迭代器
let iterator = createIteator();

console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

生成器函数最有趣的部分大概是,每当执行完一条yield语句后函数就会自动停止执行。执行yield 1之后,函数便不再执行其他任何语句,直到再次调用迭代器的next()才会继续执行yield 2

使用yield关键字可以返回任何值或表达式,所以可以通过生成器函数批量地给迭代器添加元素,也可以在循环中使用yield关键字

yield后的面的值会放在迭代器返回值的value中

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

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}
// 之后所有的调用都会返回相同内容
console.log(iterator.next()) // {value:undefined,done:true}

生成器函数表达式

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

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}
// 之后所有的调用都会返回相同内容
console.log(iterator.next()) // {value:undefined,done:true}

生成器对象的方法

let o={
  createIterator:function *(items){
    for(let i=0;i<items.length;i++){
      yield items[i];
    }
  }
}
// 或简写
let o1={
  *createIterator(items){
    for(let i=0;i<items.length;i++){
      yield items[i];
    }
  }
}

可迭代对象和for-of循环

可迭代对象具有Symbol.iterator属性,是一种与迭代器密切相关对象。Symbol.iterator通过指定的函数可以返回一个作用于附属对象的迭代器。ES6所有集合对象(数组、Set集合及Map集合)和字符串都是可迭代对象,这些对象都有默认的迭代器。

由于生成器默认会为Symbol.iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象。

访问默认迭代器

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 isIterator(object){
  return typeof object[Symbol.iterator] === "function";
}
console.log(isIterator([1,2,3])); // true
console.log(isIterator("Hello")); // true
console.log(isIterator(new Map())); // true
console.log(isIterator(new Set())); // true
console.log(isIterator(new WeakMap())); // false
console.log(isIterator(new WeakSet())); // false

创建可迭代对象

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

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

for(let x of collection){
  console.log(x)
}

内建迭代器 集合对象迭代器

  • entries()
  • values()
  • keys()

字符串迭代器

var message = "A 𠮷 B";

for(let c of message){
  console.log(c)
}

NodeList迭代器

var divs = document.getElementByTagName("div");

for(let div of divs){
  console.log(div.id)
}

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

let set = new Set([1,2,3,3,3,4,5]),
array = [...set];
console.log(array); // [1,2,3,4,5]

let map = new Map([["name","Nicholas"],["age",25]]),
array = [...map];

console.log(array) // [["name","Nicholas"],["age",25]]

高级迭代器功能

可以用迭代器的next()方法的返回值,也可以在生成器内部使用yield关键字来生成值,如果给迭代器的next()方法传递参数,则这个参数的值就会替代生成器内部上一条yield语句的返回值。而如果要实现更多像异步编程这样的高级功能

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()方法时无论传入什么参数都会被丢弃。由于传给next()方法的参数会替代上一次yield的返回值,而在第一次调用next()方法前不会执行任何yield语句,因此第一次调用next()方法时传递参数是毫无意义的。

这里注意yield xxxlet a = yield xxx
yield xxx 执行后会把yield 后面的表达式的值添加到迭代对象的value中,而let a的值是上次next传参数的值。

function *createIterator(){
  let first = yield 1;
  let second;
  try{
    second = yield first+2; //
  }catch(ex) {
    second = 6;
  }
  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'))) // {value:69,done:false}
console.log(iterator.next()) //{value:undefined,done:true}

这里注意:调用throw()方法后也会像调用next()方法一样返回一个结果对象。由于成生成器内部捕获了这个错误,因而会继续执行下一条yield语句。

如此一来,next()throw()就像是迭代器的两条指令,调用next()方法命令迭代器继续执行,调用throw()方法也会命令迭代器继续执行,但同时抛出一个错误,在此之后的执行过程取决于生成器内部的代码(是否对error try-catch)

生成器的返回语句

由于生成器是函数,因此可以通过return语句提前退出函数执行,对于最后一次next()方法调用,可以主动为其指定一个返回值,在之前我们最后一次调用返回的都是undefined,正如在其他函数中那样,你可以通过return 语句指定一个返回值。而在生成器中,return 表示所有操作已经完成,属性done设置为true,同时如果提供一个返回值,则属性value被设置为这个值。通过return语句指定的返回值,只会在返回对象中出现一次,在后续调用返回的对象中,value忏悔会被重置为undefined。

function *createIterator(){
  yield 1;
  return 42;
}
let iterator = createIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:42,done:true}
console.log(iterator.next()); // {value:undefined,done:true}

展开运算符与for-of循环语句会直接忽略通过return语句指定的任何返回值,只要done一变为true,就立即停止读取其他值。返回值可以像给next()传参一样传递(委托生成器中)

委托生成器

如下:(yield语句添加一个*号)

function *createNumberIterator(){
  yield 1;
  yield 2;
}

function *createColorIterator(){
  yield "red";
  yield "green";
}

function *createCombinedIterator(){
  yield *createNmberIterator()
  yield *createColorIterator()
  yield true
}
var 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:"green",done:false}
console.log(iterator.next()); // {value:true,done:false}
console.log(iterator.next()); // {value:undefined,done:true}

有了生成器委托这个新功能,你可以进一步利用生成器的返回值来处理复杂任务:

function *createNumberIterator(){
  yield 1;
  yield 2;
  return 3;
}

function *createRepeatingIterator(count){
  for(let i=0;i<count;i++){
    yield "repeat";
  }
}

function *createCombinedIterator(){
  let result = yield *createNumberIterator()
  yield *createRepeatingIterator(result); // 返回值赋值给result 
}
var iterator = createCombinedIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:undefined,done:true}
// 数值3永远不会被返回,它只存在于生成器createCombinedIterator()的内部。如果想返回岀可以额外添加一条yield语句

yield *也可以直接应用于字符串,例如yield * "hello" 此时将使用字符串的默认迭代器

异步任务

复杂任务的异步化会带来很多管理代码的挑战,由于生成器支持在函数中暂停代码执行,因而可以深入挖掘异步处理的更多用法。简单的回调处理还好

let fs = require('fs');
fs.readFile('config.json',function(err,contents){
  if(err){
    throw err;
  }
  doSomethingWith(contents);
  console.log("Done")
})

如果需要嵌套回调或序列化一系列的异步操作,事情会变得非常复杂。此时,生成器和yield语句就派上了用场了。

简单任务执行器

由于执行yield语句会暂停当前函数的执行过程并等待一下次调用next()方法,因此你可以创建一个函数,在函数中调用生成器生成相应的迭代器,从而在不用回调函数的基础上实现异步next()方法的调用(类似async-await语法糖):

function run(taskDef){
  // 创建一个无使用限制的迭代器
  let task = taskDef();

  // 开始执行任务
  let result = task.next();

  // 循环调用next()
  function step(){
    // 如果任务未完成,则继续执行
    if(!result.done){
      result = task.next();
      step();
    }
  }

  // 开始迭代执行
  step()
}
run(function*(){
  console.log(1);
  yield;
  console.log(2);
  yield;
  console.log(3);

})

向任务执行器传递参数

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

异步任务执行器

function run(taskDef){
  // 创建一个无使用限制的迭代器
  let task = taskDef();

  // 开始执行任务
  let result = task.next();

  // 循环调用next()
  function step(){
    // 如果任务未完成,则继续执行
    if(!result.done){
      if(typeof result.value === 'funciton'){
        // 是函数传回调进去
        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()
}

let fs = require("fs");
function readFile(filename){
  return function(callback){
    fs.readFile(filename,callback)
  }
}

run(function*(){
  let contents = yield readFile("config.json");
  doSomethingWith(contents);
  console.log("Done")
})

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4334

(0)
Walker的头像Walker
上一篇 2025年3月8日 12:52
下一篇 2025年3月8日 12:51

相关推荐

  • 深入理解ES6 002【学习笔记】

    字符串和正则表达式 字符串和正则表达式 Javascript字符串一直基于16位字符编码(UTF-16)进行构建。每16位的序列是一个编码单元(code unit),代表一个字符。length、charAt()等字符串属性和方法都基于这个编码单元构造的。Unicode的目标是为世界上每一个字符提供全球唯一的标识符。如果我们把字符长度限制在16位,码位数量将不…

    个人 2025年3月8日
    53000
  • 深入理解ES6 011【学习笔记】

    Promise与异步编程 因为执行引擎是单线程的,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列中,每当一段代码准备执行时,都会被添加到任务队列中,每当引擎中的一段代码结束执行,事件循环会执行队列中的一下个任务。 Promise相当于异步操作结果占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise,就像这样…

    个人 2025年3月8日
    36200
  • 深入理解ES6 006【学习笔记】

    Symbol和Symbol属性 第6种原始数据类型:Symbol。私有名称原本是为了让开发者们创建非字符串属性名称而设计的,但是一般的技术无法检测这些属性的私有名称 创建Symbol let firstName = Symbol(); let person = {} person[firstName] = "Nicholas"; cons…

    个人 2025年3月8日
    48900
  • TS珠峰 002【学习笔记】

    泛型 /* * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git &a…

    个人 2025年3月27日
    39600
  • 向世界挥手,拥抱无限可能 🌍✨

    站得更高,看到更远 生活就像一座座高楼,我们不断向上攀登,不是为了炫耀高度,而是为了看到更广阔的风景。图中的两位女孩站在城市之巅,伸展双手,仿佛在迎接世界的无限可能。这不仅是一次俯瞰城市的旅程,更是对自由和梦想的礼赞。 勇敢探索,突破边界 每个人的生活都是一场冒险,我们生而自由,就该去探索未知的风景,去经历更多的故事。或许路途中会有挑战,但正是那些攀爬的瞬间…

    个人 2025年2月26日
    41100

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹