Generator函数语法解析
转载请注明出处: Generator函数语法解析
Generator函数是ES6提供的一种异步编程解决方案,语法与传统函数完全不同。以下会介绍一下Generator函数。
写下这篇文章的目的其实很简单,是想梳理一下自己对于Generator的理解,同时呢,为学习async函数做一下知识储备。
Generator函数
- 基本概念
- yield表达式
- next方法
- next方法的参数
- yield*表达式
- 与Iterator接口的关系
- for...of循环
- 作为对象属性的Generator函数
- Generator函数中的this
- 应用
基本概念
对于Generator函数(也可以叫做生成器函数)的理解,可以从四个方面:
形式上:Generator函数是一个普通的函数,不过相对于普通函数多出了两个特征。一是在function关键字和函数明之间多了'*'号;二是函数内部使用了yield表达式,用于定义Generator函数中的每个状态。
语法上:Generator函数封装了多个内部状态(通过yield表达式定义内部状态)。执行Generator函数时会返回一个遍历器对象(Iterator对象)。也就是说,Generator是遍历器对象生成函数,函数内部封装了多个状态。通过返回的Iterator对象,可以依次遍历(调用next方法)Generator函数的每个内部状态。
调用上:普通函数在调用之后会立即执行,而Generator函数调用之后不会立即执行,而是会返回遍历器对象(Iterator对象)。通过Iterator对象的next方法来遍历内部yield表达式定义的每一个状态。
写法上:*号放在哪里好像都可以也。看个人习惯吧,我喜欢第一种写法
function *gen () {} √
function* gen () {}
function * gen () {}
function*gen () {}
yield表达式
yield,英文意思即产生、退让的意思,因此yield表达式也有两种作用:定义内部状态和暂停执行。
举一个栗子吧: )
function *gen () {
yield 1
yield 2
return 3
}
const g = gen() // Iterator对象
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: true}
从上面代码中可以看出,gen函数使用yield表达式定义了两个内部状态。同时呢,也可以看出来,return语句只能有一个,而yield表达式却可以有多个。
执行gen函数之后,会返回一个遍历器对象,而不是立即执行gen函数。如果需要获取yield表达式定义的每个状态,需要调用next方法。
每调用一次next方法都会返回一个包含value和done属性的对象,此时会停留在某个yield表达式结尾处。value属性值即是yield表达式的值;done属性是布尔值,表示是否遍历完毕。
另外呢,yield表达式没有返回值,或者说返回值是undefined。待会会说明一下如何给yield表达式传递返回值。
需要注意的是,yield表达式的值,只有调用next方法时才能获取到。因此等于为JavaScript提供了手动的'惰性求值'(Lazy Evaluation)的功能。
一般情况下,Generator函数会结合yield表达式使用,通过yield表达式定义多个内部状态。但是,如果不使用yield表达式的Generator函数就成为了一个单纯的暂缓执行函数,个人感觉没什么意义...
function *gen () {
console.log('凯斯')
}
window.setTimeout(() => {
gen().next()
}, 2000)
// 不使用yield表达式来暂停函数的执行,还不如使用普通函数呢..
// 所以Generator函数配合yield表达式使用效果更佳
另外,yield表达式如果用在另一个表达式中,需要为其加上圆括号。作为函数参数和语句是可以不使用圆括号。
function *gen () {
console.log('hello' + yield) ×
console.log('hello' + (yield)) √
console.log('hello' + yield '凯斯') ×
console.log('hello' + (yield '凯斯')) √
foo(yield 1) √
const param = yield 2 √
}
next方法
yield表达式具有暂停执行的功能,而恢复执行的是next方法。每一次调用next方法,就会从函数头部或者上一次停下来的地方开始执行,直到遇到下一个yield表达式(return 语句)为止。同时,调用next方法时,会返回包含value和done属性的对象,value属性值可以为yield表达式、return语句后面的值或者undefined值,done属性表示遍历是否结束。
遍历器对象的next方法(从Generator函数继承而来)的运行逻辑如下
- 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield表达式后面的那个表达式的值,作为返回的对象的value属性值。
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
- 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到遇到return语句为止,并将return语句后面表达式的值,作为返回的对象的value属性值。
- 如果该函数没有return语句,则返回的对象的value属性值为undefined。
从上面的运行逻辑可以看出,返回的对象的value属性值有三种结果:
- yield表达式后面的值
- return语句后面的值
- undefined
也就是说,如果有yield表达式,则value属性值就是yield表达式后面的指;如果没有yield表达式,value属性值就等于return语句后面的值;如果yield表达式和return语句都不存在的话,则value属性值就等于undefined。举个例子: )
function *gen () {
yield 1
yield 2
return 3
}
const g = gen()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: true}
g.next() // {value: undefined, done: true}
根据next运行逻辑再针对这个例子,就很容易理解了。调用gen函数,返回遍历器对象。
第一次调用next方法时,在遇到第一个yield表达式时停止执行,value属性值为1,即yield表达式后面的值,done为false表示遍历没有结束;
第二次调用next方法时,从暂停的yield表达式后开始执行,直到遇到下一个yield表达式后暂停执行,value属性值为2,done为false;
第三次调用next方法时,从上一次暂停的yield表达式后开始执行,由于后面没有yield表达式了,所以遇到return语句时函数执行结束,value属性值为return语句后面的值,done属性值为true表示已经遍历完毕了。
第四次调用next方法时,value属性值就是undefined了,此时done属性为true表示遍历完毕。以后再调用next方法都会是这两个值。
next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined。
function *gen () {
var x = yield 'hello world'
var y = x / 2
return [x, y]
}
const g = gen()
g.next() // {value: 'hello world', done: false}
g.next() // {value: [undefined, NaN], done: true}
从上面代码可以看出,第一次调用next方法时,value属性值是'hello world',第二次调用时,由于变量y的值依赖于变量x,而yield表达式没有返回值,所以返回了undefined给变量x,此时undefined / 2为NaN。
要解决上面的问题,可以给next方法传递参数。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
function *gen () {
var x = yield 'hello world'
var y = x / 2
return [x, y]
}
const g = gen()
g.next() // {value: 'hello world', done: false}
g.next(10) // {value: [10, 5], done: true}
当给第二个next方法传递参数10时,yield表达式的返回值为10,即var x = 10,所以此时变量y为5。
注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上说,第一个next方法用来启动遍历器对象,所以不用带上参数。所以呢,每次使用next方法会比yield表达式要多一次。
如果想要第一次调用next方法时就可以传递参数,可以使用闭包的方式。
// 实际上就是在闭包内部执行了一次next方法
function wrapper (gen) {
return function (...args) {
const genObj = gen(...args)
genObj.next()
return genObj
}
}
const generator = wrapper(function *generator () {
console.log(`hello ${yield}`)
return 'done'
})
const a = generator().next('keith')
console.log(a) // hello keith, done
实际上,yield表达式和next方法构成了双向信息传递。yield表达式可以向外传递value值,而next方法参数可以向内传递值。
yield*表达式
如果在Generator函数中调用另一个Generator函数,默认情况下是无效的。
function *foo () {
yield 1
}
function *gen () {
foo()
yield 2
}
const g = gen()
g.next() // {value: 2, done: false}
g.next() // {value: undefined, done: true}
从上面代码中可以看出,并没有在yield 1处停止执行。此时就需要使用yield* 表达式。从语法角度上说,如果yield表达式后面跟着遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。实际上,yield*表达式是for...of循环的简写,完全可以使用for...of循环来代替yield*表达式
function *foo () {
yield 1
}
function *gen () {
yield* foo()
yield 2
}
const g = gen()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: undefined, done: true}
// 相当于
function *gen () {
yield 1
yield 2
}
// 相当于
function *gen () {
for (let item of foo()) {
yield item
}
yield 2
}
如果直接使用了yield foo(),返回的对象的value属性值为一个遍历器对象。而不是Generator函数的内部状态。
function *foo () {
yield 1
}
function *gen () {
yield foo()
yield 2
}
const g = gen()
g.next() // {value: Generator, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: undefined, done: true}
另外,任何数据类型(Array, String)只要有Iterator接口,就能够被yield*遍历
const arr = ['a', 'b']
const str = 'keith'
function *gen () {
yield arr
yield* arr
yield str
yield* str
}
const g = gen()
g.next() // {value: ['a', 'b'], done: false}
g.next() // {value: 'a', done: false}
g.next() // {value: 'b', done: false}
g.next() // {value: 'keith', done: false}
g.next() // {value: 'k', done: false}
...
如果在Generator函数中存在return语句,则需要使用let value = yield* iterator方式获取返回值。
function *foo () {
yield 1
return 2
}
function *gen () {
var x = yield* foo()
return x
}
const g = gen()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: true}
使用yield*表达式可以很方便的取出嵌套数组的成员。
// 普通方法
const arr = [1, [[2, 3], 4]]
const str = arr.toString().replace(/,/g, '')
for (let item of str) {
console.log(+item) // 1, 2, 3, 4
}
// 使用yield*表达式
function *gen (arr) {
if (Array.isArray(arr)) {
for (let i = 0; i < arr.length; i++) {
yield * gen(arr[i])
}
} else {
yield arr
}
}
const g = gen([1, [[2, 3], 4]])
for (let item of g) {
console.log(item) // 1, 2, 3, 4
}
与Iterator接口的关系
任何一个对象的Symbol.iterator属性,指向默认的遍历器对象生成函数。而Generator函数也是遍历器对象生成函数,所以可以将Generator函数赋值给Symbol.iterator属性,这样就使对象具有了Iterator接口。默认情况下,对象是没有Iterator接口的。
具有Iterator接口的对象,就可以被扩展运算符(...),解构赋值,Array.from和for...of循环遍历了。
const person = {
name: 'keith',
height: 180
}
function *gen () {
const arr = Object.keys(this)
for (let item of arr) {
yield [item, this[item]]
}
}
person[Symbol.iterator] = gen
for (let [key, value] of person) {
console.log(key, value) // name keith , height 180
}
Generator函数函数执行之后,会返回遍历器对象。该对象本身也就有Symbol.iterator属性,执行后返回自身
function *gen () {}
const g = gen()
g[Symbol.iterator]() === g // true
for...of循环
for...of循环可以自动遍历Generator函数生成的Iterator对象,不用调用next方法。
function *gen () {
yield 1
yield 2
yield 3
return 4
}
for (let item of gen()) {
console.log(item) // 1 2 3
}
上面代码使用for...of循环,依次显示 3 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。
作为对象属性的Generator函数
如果一个对象有Generator函数,那么可以使用简写方式
let obj = {
* gen () {}
}
// 也可以完整的写法
let obj = {
gen: function *gen () {}
}
当然了,如果是在构造函数中,简写形式也是一样的。
class F {
* gen () {}
}
Generator函数中的this
Generator函数中的this对象跟构造函数中的this对象有异曲同工之处。先来看看构造函数中的new关键字的工作原理。
function F () {
this.a = 1
}
const f = new F()
- 调用构造函数F,返回实例对象f
- 将构造函数内部中的this指向这个实例对象
- 将构造函数中的原型对象赋值给实例对象的原型
- 执行构造函数中的代码
调用Generator函数会返回遍历器对象,而不是实例对象,因此无法获取到this指向的实例对象上的私有属性和方法。但是这个遍历器对象可以继承Generator函数的prototype原型对象上的属性和方法(公有属性和方法)。
function *Gen () {
yield this.a = 1
}
Gen.prototype.say = function () {
console.log('keith')
}
const g = new Gen()
g.a // undefined
g.say() // 'keith'
如果希望修复this指向性问题,可以使用call方法将函数执行时所在的作用域绑定到Generator.prototype原型对象上。这样做,会使私有属性和方法变成公有的了,因为都在原型对象上了。
function *Gen () {
this.a = 1
yield this.b = 2
yield this.c = 3
}
const g = Gen.call(Gen.prototype)
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: false}
g.next() // {value: undefined, done: true}
g.a // 1,继承自Gen.prototype
g.b // 2,同上
g.c // 3,同上
应用
Generator函数的应用主要在异步编程上,会在下一篇文章中分享。请期待噢: )
Generator函数语法解析的更多相关文章
- Generator函数异步应用
转载请注明出处: Generator函数异步应用 上一篇文章详细的介绍了Generator函数的语法,这篇文章来说一下如何使用Generator函数来实现异步编程. 或许用Generator函数来实现 ...
- Generator函数执行器-co函数库源码解析
一.co函数是什么 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行.短小精悍只有短短200余行,就可以免去手动编写G ...
- 15.Generator 函数的语法
Generator 函数的语法 Generator 函数的语法 简介 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generat ...
- 16.Generator函数的语法
1.简介 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同. 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机 ...
- ES6的新特性(16)——Generator 函数的语法
Generator 函数的语法 简介 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generator 函数的语法和 API,它的 ...
- Generator 函数的语法
简介 § ⇧ 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generator 函数的语法和 API,它的异步编程应用请看< ...
- Generator函数的语法
简介 Generator函数是ES6关于异步编程的解决方案.Generator函数能够让函数暂停执行(即交出函数的执行权),简单直白点来理解,Generator函数就是一个状态机,内部封装了多个状态( ...
- With语句以及@contextmanager的语法解析
with 语句以及@contextmanager的语法解析 with语句可以通过很简单的方式来替try/finally语句. with语句中EXPR部分必须是一个包含__enter__()和__e ...
- 转: ES6异步编程:Generator 函数的含义与用法
转: ES6异步编程:Generator 函数的含义与用法 异步编程对 JavaScript 语言太重要.JavaScript 只有一根线程,如果没有异步编程,根本没法用,非卡死不可. 以前,异步编程 ...
随机推荐
- .NET 构造Class返回多个json值
上次总结使用DataTable返回多个值,后来看到一个小哥的返回方式和我的有所不同,便留意了一下.原来他构造一个Class,而我构造的是一个Table. 首先说说两者的区别:拿student举例,st ...
- 企业级nosql数据库应用与实战-redis
一.NoSQL简介 1.1 常见的优化思路和方向 1.1.1 MySQL主从读写分离 由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力.读写集中在一个数据库上让数据库不堪重负,大部 ...
- 《修改代码的艺术》【PDF】下载
<修改代码的艺术>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382309 内容简介 <修改代码的艺术>针对大型的. ...
- C#中的GET和SET访问器
我们在学习C#语法的属性时,都要首先和GET,SET访问器打交道,从英文的字面意思上理解,GET应该就是获得什么什么,而SET应该是设置什么什么,那我们看一下,官方是怎么定义这对访问器的:get是读取 ...
- ImportError: No module named 'request'
使用系统自带的Python 2.7执行python时出现ImportError: No module named 'request'这样的报错,这是系统自带的Python没有requests库,这里可 ...
- RESTClient调试POST方法&Reflector+de4dot反混淆破解dll
RESTClient调试POST方法 RESTClient是火狐的一款WebAPI测试工具. 1.先看下我们要调试的接口
- ES6/7 异步编程学习笔记
前言 在ES6的异步函数出现之前,Js实现异步编程只有settimeout.事件监听.回调函数等几种方法 settTmeout 这种方法常用于定时器与动画的功能,因为其本质上其实是浏览器的WebAPI ...
- 视觉SLAM
SLAM:Simultaneous Localization And Mapping.中文:同时定位与地图重建. 它是指搭载特定传感器的主体,在没有实验先验信息的情况下,于运动过程中建立环境的模型,同 ...
- PredictionIO+Universal Recommender快速开发部署推荐引擎的问题总结(3)
PredictionIO+Universal Recommender虽然可以帮助中小企业快速的搭建部署基于用户行为协同过滤的个性化推荐引擎,单纯从引擎层面来看,开发成本近乎于零,但仍然需要一些前提条件 ...
- ubuntu12.04destdrop删除不必要的软件
sudo apt-get -y --auto-remove purge unity unity-2d* sudo apt-get -y purge empathy sudo apt-get -y ...