不一样的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);
相关推荐
食指Shaye2 分钟前
Chrome 中清理缓存的方法
前端·chrome·缓存
JobsandCzj4 分钟前
PDF 分割工具
javascript·小程序·pdf
午后书香13 分钟前
一天三场面试,口干舌燥要晕倒(二)
前端·javascript·面试
Book_熬夜!28 分钟前
CSS—补充:CSS计数器、单位、@media媒体查询
前端·css·html·媒体
程序员大澈29 分钟前
1个基于 Three.js 的 Vue3 组件库
javascript·vue.js
程序员大澈35 分钟前
3个 Vue Scoped 的核心原理
javascript·vue.js
hyyyyy!38 分钟前
《原型链的故事:JavaScript 对象模型的秘密》
javascript·原型模式
程序员大澈1 小时前
3个好玩且免费的api接口
javascript·vue.js
程序员大澈1 小时前
4个 Vue 路由实现的过程
javascript·vue.js·uni-app
几度泥的菜花1 小时前
如何禁用移动端页面的多点触控和手势缩放
前端·javascript