es6新的数据类型——generator
todo
一、Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。
一是,function
关键字与函数名之间有一个星号;
二是,函数体内部使用yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
} var hw = helloWorldGenerator();
上面代码定义了一个 Generator 函数helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
hw.next()
// { value: 'hello', done: false } //调用第一次.next()时,会执行yield前面的代码和yield紧跟着的代码,直到遇到下一个yield为止 hw.next()
// { value: 'world', done: false } hw.next()
// { value: 'ending', done: true } hw.next()
// { value: undefined, done: true }
上面代码一共调用了四次next
方法。
第一次调用,Generator 函数开始执行,直到遇到第一个yield
表达式为止。next
方法返回一个对象,它的value
属性就是当前yield
表达式的值hello
,done
属性的值false
,表示遍历还没有结束。
第二次调用,Generator 函数从上次yield
表达式停下的地方,一直执行到下一个yield
表达式。next
方法返回的对象的value
属性就是当前yield
表达式的值world
,done
属性的值false
,表示遍历还没有结束。
第三次调用,Generator 函数从上次yield
表达式停下的地方,一直执行到return
语句(如果没有return
语句,就执行到函数结束)。next
方法返回的对象的value
属性,就是紧跟在return
语句后面的表达式的值(如果没有return
语句,则value
属性的值为undefined
),done
属性的值true
,表示遍历已经结束。
第四次调用,此时 Generator 函数已经运行完毕,next
方法返回对象的value
属性为undefined
,done
属性为true
。以后再调用next
方法,返回的都是这个值。
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
ES6 没有规定,function
关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
由于 Generator 函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在function
关键字后面。本书也采用这种写法。
二、yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
遍历器对象的next
方法的运行逻辑如下。
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
需要注意的是,yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
function* gen() {
yield 123 + 456;
}
上面代码中,yield
后面的表达式123 + 456
,不会立即求值,只会在next
方法将指针移到这一句时,才会求值。
yield
表达式与return
语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return
语句,但是可以执行多次(或者说多个)yield
表达式。正常函数只能返回一个值,因为只能执行一次return
;Generator 函数可以返回一系列的值,因为可以有任意多个yield
。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。
Generator 函数可以不用yield
表达式,这时就变成了一个单纯的暂缓执行函数。
function* f() {
console.log('执行了!')
} var generator = f(); setTimeout(function () {
generator.next()
}, 2000);
上面代码中,函数f
如果是普通函数,在为变量generator
赋值时就会执行。但是,函数f
是一个 Generator 函数,就变成只有调用next
方法时,函数f
才会执行。
另外需要注意,yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
上面代码在一个普通函数中使用yield
表达式,结果产生一个句法错误。
下面是另外一个例子。
var arr = [1, [[2, 3], 4], [5, 6]]; var flat = function* (a) {
a.forEach(function (item) {
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
});
}; for (var f of flat(arr)){
console.log(f);
}
上面代码也会产生句法错误,因为forEach
方法的参数是一个普通函数,但是在里面使用了yield
表达式(这个函数里面还使用了yield*
表达式,详细介绍见后文)。一种修改方法是改用for
循环。
var arr = [1, [[2, 3], 4], [5, 6]]; var flat = function* (a) {
var length = a.length;
for (var i = 0; i < length; i++) {
var item = a[i];
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
}
}; for (var f of flat(arr)) {
console.log(f);
}
// 1, 2, 3, 4, 5, 6 回调嵌套
另外,yield
表达式如果用在另一个表达式之中,必须放在圆括号里面。
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
yield
表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
三、与 Iterator 接口的关系
https://www.cnblogs.com/wangtong111/p/11335616.html 中讲过,任意一个对象的Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator
属性,从而使得该对象具有 Iterator 接口。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
}; [...myIterable] // [1, 2, 3]
扩展运算符(...)也会调用默认的 Iterator 接口。上面代码中,Generator 函数赋值给Symbol.iterator
属性,从而使得myIterable
对象具有了 Iterator 接口,可以被...
运算符遍历了。
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator
属性,执行后返回自身。
function* gen(){
// some code
} var g = gen(); g[Symbol.iterator]() === g
上面代码中,gen
是一个 Generator 函数,调用它会生成一个遍历器对象g
。它的Symbol.iterator
属性,也是一个遍历器对象生成函数,执行后返回它自己。
简单来说,遍历器对象的Symbol.iterator属性执行后返回自身
四、next 方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
} var g = f(); g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
上面代码先定义了一个可以无限运行的 Generator 函数f
,如果next
方法没有参数,每次运行到yield
表达式,变量reset
的值总是undefined
。当next
方法带一个参数true
时,变量reset
就被重置为这个参数(即true
),因此i
会等于-1
,下一轮循环就会从-1
开始递增。
这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next
方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
再看一个例子。
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 }
上面代码中,第二次运行next
方法的时候不带参数,导致 y 的值等于2 * undefined
(即NaN
),除以 3 以后还是NaN
,因此返回对象的value
属性也等于NaN
。第三次运行Next
方法的时候不带参数,所以z
等于undefined
,返回对象的value
属性等于5 + NaN + undefined
,即NaN
。
如果向next
方法提供参数,返回结果就完全不一样了。上面代码第一次调用b
的next
方法时,返回x+1
的值6
;第二次调用next
方法,将上一次yield
表达式的值设为12
,因此y
等于24
,返回y / 3
的值8
;第三次调用next
方法,将上一次yield
表达式的值设为13
,因此z
等于13
,这时x
等于5
,y
等于24
,所以return
语句的值等于42
。
注意,由于next
方法的参数表示上一个yield
表达式的返回值,所以在第一次使用next
方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next
方法时的参数,只有从第二次使用next
方法开始,参数才是有效的。从语义上讲,第一个next
方法用来启动遍历器对象,所以不用带有参数。
再看一个通过next
方法的参数,向 Generator 函数内部输入值的例子。
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
} let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b
上面代码是一个很直观的例子,每次通过next
方法向 Generator 函数输入值,然后打印出来。
如果想要第一次调用next
方法时,就能够输入值,可以在 Generator 函数外面再包一层。
function wrapper(generatorFunction) {
return function (...args) {
let generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
} const wrapped = wrapper(function* () {
console.log(`First input: ${yield}`);
return 'DONE';
}); wrapped().next('hello!')
// First input: hello!
上面代码中,Generator 函数如果不用wrapper
先包一层(在内部先执行一次next()函数),是无法第一次调用next
方法,就输入参数的。
五、for...of循环
for...of
循环可以自动遍历 Generator 函数运行时生成的Iterator
对象,且此时不再需要调用next
方法。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
} for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
上面代码使用for...of
循环,依次显示 5 个yield
表达式的值。这里需要注意,一旦next
方法的返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象,所以上面代码的return
语句返回的6
,不包括在for...of
循环之中。
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
yield curr;
[prev, curr] = [curr, prev + curr]; //解构赋值:http://es6.ruanyifeng.com/#docs/destructuring
}
} for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
从上面代码可见,使用for...of
语句时不需要使用next
方法。
利用for...of
循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口,无法使用for...of
循环,通过 Generator 函数为它加上这个接口,就可以用了。
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj); for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
} let jane = { first: 'Jane', last: 'Doe' }; for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
es6新的数据类型——generator的更多相关文章
- ES6 新的数据类型Map和Set
Map Map是一组键值对的结构,具有极快的查找速度. 举个例子,假设要根据同学的名字查找对应的成绩,如果用Array实现,需要两个Array: var names = ['Michael', 'Bo ...
- ES6新的数据类型 generato,在AJAX中的应用
1.next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”.返回的value就是yield的 ...
- ES6新特性三: Generator(生成器)函数详解
本文实例讲述了ES6新特性三: Generator(生成器)函数.分享给大家供大家参考,具体如下: 1. 简介 ① 理解:可以把它理解成一个函数的内部状态的遍历器,每调用一次,函数的内部状态发生一次改 ...
- ES6新特性:Javascript中Generator(生成器)
ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajax, filesystem, 或者数组对象遍历等都可以用到: Generator的使用: G ...
- 前端入门21-JavaScript的ES6新特性
声明 本篇内容全部摘自阮一峰的:ECMAScript 6 入门 阮一峰的这本书,我个人觉得写得挺好的,不管是描述方面,还是例子,都讲得挺通俗易懂,每个新特性基本都还会跟 ES5 旧标准做比较,说明为什 ...
- ES6新语法
ES6新语法概览 简介 ES6是JavaScript语言的新一代标准,加入了一些新的功能和语法,正式发布于2015年6月,亦称ES2015:该标准由ECMA(欧洲计算机制造联合会)的第39号技术专家委 ...
- 34.js----JS 开发者必须知道的十个 ES6 新特性
JS 开发者必须知道的十个 ES6 新特性 这是为忙碌的开发者准备的ES6中最棒的十个特性(无特定顺序): 默认参数 模版表达式 多行字符串 拆包表达式 改进的对象表达式 箭头函数 =&> ...
- ES6新语法概览
简介 ES6是JavaScript语言的新一代标准,加入了一些新的功能和语法,正式发布于2015年6月,亦称ES2015:该标准由ECMA(欧洲计算机制造联合会)的第39号技术专家委员会(TC39)制 ...
- ES6新特性概览
本文基于lukehoban/es6features ,同时参考了大量博客资料,具体见文末引用. ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony( ...
随机推荐
- 为webService添加Interceptor(拦截器)
今天写一个简单的拦截器,以webService接口为例: 背景:H5的一个项目,只要调用H5webService 接口下面的方法都会触发一个AuthorityInterceptor去验证是否调用类型是 ...
- tf-图像预处理
tensorflow 中自带了很多图像处理的方法,基本都在 tf.image 模块中,虽然不如 opencv 强大,但也比较常用,这里做个记录. 图像编解码 1. 用 tf 的方法读取图片后,都需要进 ...
- PostgreSQL-优化之分表
分表概述 数据库分表,就是把一张表分成多张表,物理上虽然分开了,逻辑上彼此仍有联系. 分表有两种方式:水平分表,即按列分开:垂直分表,即按行分开 优势 1. 查询速度大幅提升 2. 删除数据速度更快 ...
- vim学习(二)之模式
vim模式 基本上 vi/vim 共分为三种模式,分别是命令模式(Command mode),输入模式(Insert mode)和底线命令模式(Last line mode). 命令模式: 用户刚刚启 ...
- 解决 find: 路径必须在表达式之前:
通配符前面加\ 转义 或者 换个目录操作
- luogu P5328 [ZJOI2019]浙江省选
传送门 每个人都可以看成一条直线\(y=ax+b\),所以我们要求的是每条线在整点处,上方线的数量的最小值(注意多条直线如果交于同一整点互不影响) 如果\(m=1\),其实只要求出半平面交,然后在半平 ...
- Genymotion模拟器使用camera
1.前言 最近开发react-native的app,上传图片功能需要使用相机,发现Genymotion默认的相机不工作.查看同行的博客解决了,归纳整理一下. 2.步骤 2.1安装Genymotion: ...
- 一文看懂HttpServletResponse
https://www.jianshu.com/p/8bc6b82403c5 Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象.和代表响应的resp ...
- Linux学习--第十三天--日志、系统运行级别、grub加密
日志 rsyslogd取代了syslogd. /var/log/cron #定时任务相关日志 /var/log/cups #打印信息相关日志 /var/log/dmesg #开机内核自检相关日志,dm ...
- 使用switchshow/supportshow命令确认Brocade交换机型号(转载)
switchshow命令(或supportshow日志)中的switchType是以数字来代表不同的交换机型号,完整的对应表格如下: Switchtype EMC / Brocade名称 / 端口 / ...