资讯专栏INFORMATION COLUMN

ES6学习笔记之Generator函数

cjie / 1773人阅读

摘要:调用函数后和普通函数不同的是,该函数并不立即执行,也不返回函数执行结果,而是返回一个指向内部状态的对象,也可以看作是一个遍历器对象。第一个只是用来启动函数内部的遍历器,传参也没有多大意义。

之前断断续续接触到了一些ES6的知识,异步编程方面听得比较多的就是Promise,直到最近比较系统地学习了ES6的新特性才发现Generator这个神奇的存在,它可以实现一些前所未有的事情,让我顿时对它充满了兴趣。

为什么需要Generator?

JavaScript异步编程是为解决JavaScript执行环境是“单线程”这个问题的。在JavaScript中,异步编程的使用非常频繁,也经常会出现需要逐步完成多个异步操作的情况。之前用回调函数实现异步编程如果碰到了这种问题就需要嵌套使用回调函数,异步操作越多,嵌套得就越深,这样非常不利于代码的维护,代码阅读起来也很困难。Generator函数是ES6提出的一种异步编程解决方案,它可以避免回调的嵌套,但是它的用处可不仅仅如此哦,待我细细道来。

举个小例子
function* gen1() {
    yield 1;
    yield "hello";
    return true;
}
let g1 = gen1();
g1.next();  // Object {value: 1, done: false}
g1.next();  // Object {value: "hello", done: false}
g1.next();  // Object {value: true, done: true}
g1.next();  // Object {value: undefined, done: true}

上面的代码就定义了一个Generator函数,Generator函数的定义跟普通函数差不多,只是在function关键字后面加了一个星号。调用Generator函数后和普通函数不同的是,该函数并不立即执行,也不返回函数执行结果,而是返回一个指向内部状态的generator对象,也可以看作是一个遍历器对象。然后必须调用该对象的next方法,让函数继续走下去,是指针移向下一个状态。每当碰到yield语句,内部指针就停下来,直到下一次调用next()才开始执行。
上面代码调用了四次next方法,遍历才结束。next方法会返回一个有两个属性的对象,value属性的值为当前yield语句的值,done属性的值表示遍历是否结束,即最后一次调用next方法时,再也碰不到yield或者return语句了。
星号写在哪
function关键字和函数名之间的星号写在哪都可以,只要在两者之间即可,但是一般都采取我上面代码的那种写法。

Generator函数本质

上面说了那么多,想必大家已经知道Generator函数是怎么用的了,那么Generator本质上到底是个啥呢?Generator函数的理解有多种:

Generator函数可以被理解成一个状态机,里面封装了多种状态,有兴趣的同学可以去了解一下状态机,操作系统的书里都会讲到。

Generator函数还可以被理解成一个遍历器对象生成器,它返回的遍历器对象可以依次遍历Generator函数内部的每一个状态。这就是为什么之前说Generator函数不仅是为了解决回调函数嵌套问题。Generator函数是生成一个对象,但是调用的时候前面不能加new命令

yield语句

yield语句是Generator函数内部可以暂停执行程序的语句,yield语句后面的值可以是各种数据类型,字符串,整数,布尔值等等都可以。这里主要想说说Generator函数中yield语句和return语句的区别。

和return语句区别

从上面的例子可以看出,函数不仅是碰到yield语句才会停止执行,碰到return语句也会停止执行。这很容易理解,不管怎样Generator函数也是一个函数,碰到return语句必然会停止执行,返回值。那么,两者的区别是什么呢?先来看个例子:

function* gen2() {
    return true;
    yield 1;
    yield "hello";
}
let g2 = gen2();
g2.next();  // Object {value: true, done: true}
g2.next();  // Object {value: undefined, done: true}

从上面例子可以看出,当碰到return语句时,返回对象的done属性值就为true,遍历结束,不管后面是否还有yield或者return语句。这种区别本质上是因为yield语句具备位置记忆功能而return语句则没有该功能。

再说一点

Generator函数,不管内部有没有yield语句,调用函数时都不会执行任何语句,只有当调用next(),内部语句才会执行,只要调用next(),就会返回一个对象。yield语句只是函数暂停执行的一个标记。

function* gen3() {
    console.log("执行了么?");
}
let g3 = gen3();  // 没有任何输出
g3.next();
// 执行了么?
// Object {value: undefined, done: true}

注意:yield函数不能在普通函数中使用,否则会报错。

next方法

除了yield语句,next方法也是Generator函数实现中很重要的特性。既然next()是一个函数,那么这个函数可以带参数么,当然可以。上面的例子比较简单,都只是一些单纯的yield语句,其实Generator函数和普通函数一样里面是可以进行各种复杂的计算和操作的,也可以有各种循环语句,不仅next方法可以传参数,Generator函数也是可以传参数的,立马上例子:

function* gen4(a) {
    let b = yield (a + 1);
    return b * 2;
}
let g4 = gen4(1);
g4.next();  //  Object {value: 2, done: false}
g4.next();  //  Object {value: NaN, done: true}
let g5 = gen4(1);
g5.next();  //  Object {value: 2, done: false}
g5.next(3);  //  Object {value: 6, done: true}

上面例子中,Generator函数需要接收一个参数a,表面上变量b是用yield语句赋值了,但是遗憾的是这个赋值好像并没有成功,当第二次调用next方法(没有传参数)时,返回的对象value值居然为NaN,而不是我们想的 2 *(1+1)= 4。但是如果第二次调用next方法时,传入一个参数3,返回对象的value值就为6。这可以说明两点:

yield语句没有返回值,或者总是返回undefined;

next方法如果带上一个参数,这个参数就是作为上一个yield语句的返回值。

注意:因为next方法表示上一个yield语句的返回值,所以必须有上一个yield语句的存在,那么第一次调用next方法时就不能传参数。第一个next只是用来启动Generator函数内部的遍历器,传参也没有多大意义。

再说Generator函数与普通函数区别 可以用prototype么?

虽然Generator函数和普通函数区别很大,但是Generator函数的实例也可以继承Generator函数的prototype对象上的方法。

function* gen5() {}
gen5.prototype.say = function() {
    console.log("有generator?");
}
let g6 = gen5();
g6.say();  // 有generator?

从上面代码可以看出,Generator函数返回的g6,继承了gen5.prototype。

this咋用?

大家都知道普通函数都会有一个this对象,那么Generator的this对象怎么用呢?还是例子更直观:

function* gen6() {
    this.a = 1;
}
let g7 = gen6();
g7.a;  //  undefined

上面代码中,Generator函数在this对象上添加了一个属性a,g7实例并不能取到这个属性。那么怎么让Generator函数返回一个可以正常使用this对象的实例呢?阮一峰老师提供了一种方法,首先,生成一个空对象,使用call方法绑定Generator函数内部的this。这样,构造函数调用以后,这个空对象就是Generator函数的实例对象了。参考代码在这:http://es6.ruanyifeng.com/#docs/generator

Generator函数与Iterator

Generator函数返回的是一个遍历器对象,那么它在遍历这方面肯定有用武之地,下一次讨论Iterator时候再总结吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88039.html

相关文章

  • JS笔记

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。异步编程入门的全称是前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。 TypeScript 入门教程 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 网络基础知识之 HTTP 协议 详细介绍 HTT...

    rottengeek 评论0 收藏0
  • JavaScript异步编程解决方案笔记

    摘要:异步编程解决方案笔记最近读了朴灵老师的深入浅出中异步编程一章,并参考了一些有趣的文章。另外回调函数中的也失去了意义,这会使我们的程序必须依赖于副作用。 JavaScript 异步编程解决方案笔记 最近读了朴灵老师的《深入浅出NodeJS》中《异步编程》一章,并参考了一些有趣的文章。在此做个笔记,记录并巩固学到的知识。 JavaScript异步编程的两个核心难点 异步I/O、事件驱动使得...

    dmlllll 评论0 收藏0
  • JavaScript学习笔记第二天_函数

    摘要:廖雪峰的教程学习笔记变量作用域不能声明块级的变量,的函数内变量声明会被提升至函数体开头则用来解决这个块级变量声明,于引入。普通函数一般将赋值为。高阶函数输出结果是。箭头函数新引入的相当于如下的匿名函数其中为参数。 廖雪峰的JavaScript教程学习笔记 1. 变量作用域 var 不能声明块级的变量,js的函数内变量声明会被提升至函数体开头let 则用来解决这个块级变量声明,于ES6...

    ShevaKuilin 评论0 收藏0
  • ES6学习笔记3-Set和Map、Promise、Iterator、Generator、async

    摘要:去除数组的重复成员这表明,在内部,两个是相等。返回一个布尔值,表示该值是否为的成员。使用回调函数遍历每个成员没有返回值。对象特点对象有三种状态进行中已完成,又称和已失败。方法是的别名,用于指定发生错误时的回调函数。 Set和Map数据结构 Set 新的数据结构Set类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。接受一个数组(或...

    sevi_stuo 评论0 收藏0
  • ES6笔记 变量解构赋值

    摘要:变量的解构赋值中允许按照一定模式,从数组和对象中提取,对变量进行赋值。数组的解构赋值上面的代码标示可以从数组中提取值,按照位置的对应关系对变量进行赋值。默认值解构赋值允许指定默认值。 变量的解构赋值 ES6中允许按照一定模式,从数组和对象中提取,对变量进行赋值。 数组的解构赋值 var [a,b,c] = [1,2,3]; a // 1; b // 2; c // 3; 上面的代码标示...

    baukh789 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<