不一样的js-迭代器和生成器

前言

迭代器和生成器在js这门语言中扮演着非常重要的角色,涉及js许多语法糖的实现,比如for of async await等等,本篇文章将以一个全新的角度去讲解他们

迭代器

背景知识

  • 什么是迭代?

从一个数据集合中按照一定的顺序,不断的取出数据。

类比产品研发的迭代,产品的迭代是一次次的做出来的,不确定做多少个迭代(有可能做到中途项目废弃等等因素),只能知道依次迭代的动作。强调的是一个过程。

  • 迭代和遍历的区别

如上所说迭代强调的是过程,依次取出数据的动作,不保证能取完数据。

而遍历强调的是把整个数据都要依次取完,更强调一个结果

  • 迭代器

在迭代过程的封装,通常在不同语言中都有不同的表现形式,通常为对象

js中的迭代器

js规定,如果一个对象具有next方法,并且该方法返回一个对象,该对象的格式如下:

js 复制代码
 const obj = {
    next (){
        return{
            value:xxx,
            done:xxx
        }
    }
}

那么就称这个对象为迭代器

以数组循环为例,改造成为迭代器模式

js 复制代码
const array = [1, 2, 3, 4, 5];
//for循环
for (let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

function arrayIterator(arr) {
  let i = 0;
  return {
    next() {
      console.log(i);
      return {
        value: arr[i++],
        done: i > arr.length,
      };
    },
  };
}

//迭代器便利数组改造
let iterator = arrayIterator(array);
let data = iterator.next();
console.log(data);
while (!data.done) {
  console.log(data);
  data = iterator.next();
}

console.log("迭代完成");

虽然从上面代码来看迭代器操作数据会让代码变得复杂,但是也有一个好处,就是隔离的调用者去操作数据源数据的问题。

举一个迭代器能实现的需求,循环不能实现的需求

ini 复制代码
//依次得到斐波拉契数列前面n位的值
//eg 1 1 2 3 5 8...

function FbIterator() {
  let prev1 = 1,
    prev2 = 1,
    n = 1;
  return {
    next() {
      console.log(n);
      let value;
      if (n <= 2) {
        value = 1;
      } else {
        value = prev1 + prev2;
      }
      const result = {
        value,
        done: false,
      };
      prev2 = prev1;
      prev1 = result.value;
      n++;
      return result;
    },
  };
}
const fbIterator = FbIterator();

上面的案例,可以得到一个无限迭代的得波拉契数列

上面例子的FbIteratorarrayIterator都称为迭代器创建函数

可迭代器协议

es6规定,如果一个对象具有知名符号属性 Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是一个可迭代的(iterable)

eg

js 复制代码
const obj = {
  [Symbol.iterator]() {
    return {
      next() {
        return { value: xx, done: xx };
      },
    };
  },
};

那么

  • 如何知晓一个对象是否是可迭代的?

看是否有Symbol.iterator

我们常用的数组就是一个可迭代对象,

ini 复制代码
const arr = [1, 2, 3, 4];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next());
//{value:1,done:false}

很多的伪数组也是一个可迭代对象

  • 如何遍历一个可迭代对象?
js 复制代码
const arr = [1, 2, 3, 4];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next());
let result = iterator.next();
while (!result.done) {
  const item = result.value;
  console.log(item);
  result = iterator.next();
}
  • for of

用于循环便利可迭代对象,格式如下

js 复制代码
for (let item of iterable){
    //iterable 可迭代对象
    // item 每次迭代得到的数据
}
//这个语法糖实现原理其实就是上面遍历可迭代对象的过程

生成器(Generator)

生成器和迭代器有非常多的关联,其设计出来的核心目的是为了使迭代器书写方便。但是随着发展,生成器也有一些特殊的地方

什么是生成器

生成器是一个通过构造函数 Generator 创造的对象

但是这个对象是没发自己创建的。它是js引擎内部持有

生成器的特点

  • 生成器是一个迭代器(一定有next方法)

  • 又是一个可迭代对象(一定有知名符号Symbol.iterator

  • 一定可以使用for of 循环便利

如何创建生成器

生成器的创建,必需使用生成器函数(Generator Function)

书写生成器函数

javascript 复制代码
function *method() {} //该函数一定返回一个生成器

只需要在函数名称前面加上 *

上面函数调用会生成什么?

一定会得到一个生成器

生成器函数内部执行逻辑

javascript 复制代码
function *method() { //该函数一定返回一个生成器
    console.log("test") //直接调用,不会执行 因为这里并没有迭代
} 

生成器函数内部是为了给生成器的每次迭代提供数据的

每一次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置

yield关键字,只能在生成器函数内部使用,表示产生一个迭代数据。

arduino 复制代码
function *method() { //该函数一定返回一个生成器
    console.log("第一次运行") //会执行
    yield 1;
    console.log("第二次运行") //会执行
     yield 2;
   console.log("第三次运行")  //会执行
} 
const generator = method();
consolo.log(generator.next())
//"第一次运行" 
//{value:1,done:false}
consolo.log(generator.next())
//"第二次运行" 
//{value:2,done:true}
consolo.log(generator.next())
//"第三次运行" 
//{value:undefined,done:true}

上述例子,可以看到,生成器主要的核心就是方便产生迭代器

将上面迭代器遍历数据的代码使用生产器重构

js 复制代码
const array = [1, 2, 3, 4, 5];
//for循环
for (let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

function arrayIterator(arr) {
  let i = 0;
  return {
    next() {
      return {
        value: arr[i++],
        done: i > arr.length,
      };
    },
  };
}

//迭代器便利数组改造
let iterator = arrayIterator(array);
let data = iterator.next();
console.log(data);
while (!data.done) {
  console.log(data);
  data = iterator.next();
}

//使用生成器改造
function* arrayGenerator(arr) {
  for (let item of arr) {
    yield item;
  }
}

const generator = arrayGenerator(array);
console.log("迭代完成");

生成器需要注意的细节

  • 生成器函数可以有返回值,返回值出现在第一次done为ture时的value属性中
javascript 复制代码
function *method() { //该函数一定返回一个生成器
    console.log("第一次运行") //会执行
    yield 1;
    console.log("第二次运行") //会执行
     yield 2;
   console.log("第三次运行")  //会执行
   return 10 //   运行为done之后返回10,并且放在value中
} 
const generator = method();
  • 调用生成器的next方法时,可以传递参数,传递的参数可以交给 yeild的表达式
ini 复制代码
function* method() {
  //该函数一定返回一个生成器
  let num = yield 1;
  console.log(num); //这个num就是next参数传进来的参数
  num = yield 2 + num;
}
const generator = method();
  • 生成器的其他Api

return:调用该方法,可以提前结束生成器函数,从而提前结束整个迭代过程

javascript 复制代码
function* method() {
  yield 1;
  yield 2;
  yield 3;
}
const generator = method();
generator.next()
generator.return()//直接结束迭代

throw方法:调用该方法,可以在生成器中产生一个错误

生产器的运用

背景

在es6刚出来的时候,是没有async await关键字的使用promise用起来很麻烦,所以这一段真空期时间,有些人都想出来一个办法,使用生成器解决这个问题。

ini 复制代码
function* task() {
  const d = yield 1;
  const a = yield "abc";
  const resp = yield fetch("....");
  const result = yield resp.json();
}
function run(generatorFunc) {
  const generator = generatorFunc();
  let result = generator.next();
  handleResult();
  function handleResult() {
    if (result.done) {
      return;
    }
    if (typeof result.value.then === "function") {
      result.value.then(
        (data) => {
          result = generator.next(data);
          handleResult();
        },
        (error) => {
          result = generator.throw(error);
          handleResult();
        }
      );
    } else {
      result = generator.next(result.value);
      handleResult();
    }
  }
}
run(task);
相关推荐
2401_882726485 小时前
BY组态-低代码web可视化组件
前端·物联网·低代码·前端框架·web
万维——组态5 小时前
BY组态-低代码web可视化组件
前端·物联网·低代码·编辑器·流程图·组态
小黄编程快乐屋5 小时前
npm操作大全:从入门到精通
前端·npm·node.js
OpenTiny社区5 小时前
TinyEngine v2.1版本发布:全新的区块方案和画布通信方案,打造更强力的可拓展低代码引擎
前端·低代码·开源·opentiny
轻口味5 小时前
【HarmonyOS NAPI 深度探索7】N-API 数据处理:与 JavaScript 数据的交互
javascript·c++·交互·harmonyos·napi·harmonyos-next
用户380235599005 小时前
[快速入门:利用LangChain与百度千帆平台进行对话模型集成]
前端
黑客老陈6 小时前
基于 Electron 应用的安全测试基础 — 提取和分析 .asar 文件
运维·服务器·前端·javascript·网络·electron·xss
几道之旅6 小时前
RPA编程实践:Electron实践开始
javascript·electron·rpa
yqcoder6 小时前
electron 获取本机 ip 地址
前端·javascript·electron
唐某霖7 小时前
el-dialog弹窗的@open方法中,第一次引用ref发现undefined问题,第二次后面又正常了
前端·javascript·vue.js