【ES6】浅谈Generator和yield

news/2024/7/4 1:09:54 标签: es6, generator, javascript

文章目录

  • 前言
  • 一、async await实现
  • 二、Generator实现
  • 三、Generator函数
    • 1、什么是Generator
    • 2.Generator的特点
    • 3.Generator的执行
  • 四、yield表达式
    • 1、什么是yield
    • 2、yield注意点
  • 五、Iterator对象
  • 五、next方法的参数
  • 总结


前言

问题发生在一个下午,当我正在实现Promise.all()方法的时候(有兴趣的可以去我另一篇博客看一看:深入透析Promise的all和race方法),发现for循环里的函数并不是同步执行的,那么我如果想让它同步执行该怎么办呢?

大家先来看一下这道题:

for(let i = 0;i < 5;i++) {
      if(i === 1) {
        setTimeout(() => {
          console.log(i);
        }, 0);
      }else {
        console.log(i);
      }
    }

执行结果:
在这里插入图片描述
这里便是宏任务与微任务的问题啦,不懂的同学可以去看一下事件循环知识哦!

我们发现1被推到了最后才去执行,那么我就想让他同步执行,也就是按顺序执行该怎么办呢?

  • 思路一:给函数包一层Promise,然后用async await使其同步执行。(这样还得给它而外包裹一层promise,不是多次一举吗?)
  • 思路二:使用Generator

话不多说,让我们一起来看一看吧

一、async await实现

async await和promise的内容本文就不具体讲了,直接上代码!

const promise= (i) => new Promise((resolve,reject) => {
      setTimeout(() => {
        // 此处resolve目的是为了让promise结束,否则await将一直等待
         resolve()
         console.log(i);
        }, 0);
    })
    ~(async (resolve,reject) => {
      for(let i = 0;i < 5;i++) {
        if(i === 1){
          await promise(i)
        }else {
          console.log(i)
        }
      }
    })()

让我们分析一下代码,由于await后面只能接promise才生效,我们为了让setTimeout不被排在最后执行,我们使用promise给console.log包了一层,让它成为同步执行的代码。

二、Generator实现

先看一下实现的过程,下面我们将介绍Generator和yield的一些使用

代码实现:

	function* test() {
      for(let i = 0;i < 5;i++) {
        yield i
      }
    }
    const num = test()
    
    for(let i of test()) {
      if(i === 1) {
        setTimeout(() => {
          console.log(num.next().value);
        }, 0);
      }else{
        console.log(num.next().value);
      }
    }

代码的意思呢就是:将我们需要的参数封装generator的内部对象(本文中的1-5),使用num接收generator返回的对象。

返回的对象有一点像一个链表,让我们必须执行完当前状态才能去执行下一个。

所以在for循环里,很严格的让数字按顺序执行。

三、Generator函数

1、什么是Generator

  • Generator 函数是 ES6 提供的一种异步编程解决方案
  • Generator 函数是一个状态机,封装了多个内部状态。
  • 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

2.Generator的特点

  • function关键字与函数名之间有一个星号(es6并没有严格规定星号位置,可写在function和函数名之间的任意处)
  • 函数体内部使用yield表达式,定义不同的内部状态
	function* test() {
      yield 1
      yield 2
      yield 3
      return 4
    }
    const num = test()

上述方法中使用num作为接收调用test()返回的对象,yield后面为封锁的状态,return后面为最后结束的状态。

3.Generator的执行

上面我们只是定义了一个generator,但是我们要怎样去使用它里面的状态值呢?

大家有没有发现,generator对象特别像一个链表,我们也可以像遍历链表的方式去遍历generator

	function* test() {
      yield 1
      yield 2
      return 3
    }
    const num = test()
    num.next() // {{ value: 1, done: false }}
    num.next() // {{ value: 2, done: false }}
    num.next() // {{ value: 3, done: true }}
    num.next() // {{ value: undefined, done: true }}

让我们来分析一下调用步骤:

  1. 第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值1,done属性的值false,表示遍历还没有结束。
  2. 第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。
  3. 第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。
  4. 第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

四、yield表达式

1、什么是yield

  • 由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

2、yield注意点

yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

例如:

	function* test() {
      yield 1 + 2
    }

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。

五、Iterator对象

Generator函数执行时生成的是一个iterator对象,且此时不再需要调用next方法。所以它可以使用Iterator接口。

例如:

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 扩展运算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解构赋值
let [x, y] = numbers();
x // 1
y // 2

// for...of 循环
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2

五、next方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

比如:

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

我们首先分析第一个foo(5):

  1. 第一个next到达第一个yield,所以value为yield后表达式,即5+1=6
  2. 第二个next到达第二个yield,此时y为NaN,所以y / 3为NaN,value为NaN
  3. 第三个next到达return,此时y和z为NaN,所以value为NaN

我们再来分析第二个foo(5):

  1. 第一个next到达第一个yield,所以value为yield后表达式,即5+1=6
  2. 第二个next携带12作为上一个yield,即第一个yield的参数,所以此时y = 2 * 12 = 24,此时的yield = y / 3 = 8
  3. 第三个next携带13作为上一个yield,即第二个yield的参数,所以此时z = 13,此时的yield = x + y + z = 42

总结

文章只是简单介绍了Generator和yield的基本用法和注意事项,如果有兴趣详细了解的同学可以去学习一下哦!


http://www.niftyadmin.cn/n/861671.html

相关文章

常见css居中问题

文章目录前言一、水平居中已知宽度未知宽度二、垂直居中已知高度未知高度三、垂直水平居中总结前言 居中是我们项目开发中最常用到样式&#xff0c;可以说&#xff0c;作为一名合格的前端&#xff0c;各种情况下的居中应该是得心应手的&#xff0c;接下来让我们看看有哪些方式…

【JS编译原理】V8执行JavaScript代码过程

文章目录前言一、编译器和解释器二、V8执行JavaScript代码过程1.生成AST&#xff08;抽象语法树&#xff09;2.生成字节码3.生成机器码总结前言 相信大家对Babel已经不陌生了&#xff0c;Babel充斥在我们代码中的每个角落。比如&#xff1a;jsx转化成js&#xff0c;es6转化成e…

【React Hooks优化】减少重复渲染

文章目录前言一、为什么要进行优化&#xff1f;React的默认渲染行为二、使用memo/useMemo缓存组件1.memo2.useMemo2.useCallback总结前言 事情发生在一个下午&#xff0c;我需要用React hooks写一个定时器&#xff0c;因为useEffect每次执行都会使组件重新渲染一次&#xff0c…

【前端怪谈】两个行内块元素之间的间距问题

文章目录前言一、问题描述二、解决方式1.使用浮动2.清除行内块元素之间的空格和换行符3.父元素设置font-size:03.父元素设置word-spacing总结前言 相信行内块元素,也就是display:inline-block的元素大家并不少见,不知道大家有没有发现一个问题,就是当两个行内块元素在同一行并…

【前端怪谈】js中为什么0.1+0.2 !== 0.3

文章目录前言一、问题原因二、解决办法三、大数相加1、BigInt2、大数相加总结前言 下面大家先来看一下这行诡异的代码&#xff0c;猜猜会输出什么 console.log(0.1 0.2);是不是和所想的不太一样&#xff1f;下面我们来探索一下为什么它会这样吧。 一、问题原因 在计算机中…

【前端优化】超详细!带你体验常用的前端优化手段

文章目录前言一、图片懒加载原因判断是否进入可视区方案一: clientHeight、scrollTop 和 offsetTop方案二&#xff1a;getBoundingClientRect二、防抖与节流三、路由和组件懒加载原因使用实例路由懒加载:组件懒加载四、图片预加载原因五、使用前端缓存强缓存协商缓存六、减少回…

【前端怪谈】为什么要用setTimeout模拟setInterval

文章目录前言一、setInterval存在的问题1.问题复现2.问题分析二、setInterval缺点及setTimeout1.setInterval缺点2.为什么setTimeout能取代setTimeout实现setInterval总结前言 大家都知道&#xff0c;setTimeout是延迟执行函数&#xff0c;而setInterval就像一个定时器&#x…

深入透析Promise实现细节(含手撕阶段)

文章目录前言一、Promise是什么&#xff1f;二、Promise核心逻辑实现1.基本原理2.新建类promise类&#xff0c;传入执行器executor3.传入resolve和reject方法4.then方法的简单实现5.完整代码及验证6.代码改进三.链式调用1.链式调用实现的基本思路2.then方法返回promise对象3.re…