Javascript中Generator(生成器)
阅读目录
ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajax, filesystem, 或者数组对象遍历等都可以用到;
Generator的使用:
Generator函数和普通的函数区别有两个, 1:function和函数名之间有一个*号, 2:函数体内部使用了yield表达式;比如这样:
运行下面代码
function* gen() {
yield "1";
yield "2"
}
这个玩意儿如果运行的话,会返回一个Iterator实例, 然后再执行Iterator实例的next()方法, 那么这个函数才开始真正运行, 并把yield后面的值包装成固定对象并返回,直到运行到函数结尾, 最后再返回undefined;
运行下面代码

"use strict";
function* fibonacci() {
yield 1;
yield 2;
} var it = fibonacci();
console.log(it); // "Generator { }"
console.log(it.next()); // 1
console.log(it.next()); // 2
console.log(it.next()); //undefined

yield
Generator函数返回的Iterator运行的过程中,如果碰到了yield, 就会把yield后面的值返回, 此时函数相当于停止了, 下次再执行next()方法的时候, 函数又会从上次退出去的地方重新开始执行;
如果把yield和return一起使用的话, 那么return的值也会作为最后的返回值, 如果return语句后面还有yield, 那么这些yield不生效:
运行下面代码

function* gen() {
yield 0;
yield 1;
return 2;
yield 3;
};
let g = gen();
console.log(g.next(),g.next(),g.next(),g.next());
//输出:{ value: 0, done: false } { value: 1, done: false } { value: 2, done: true } { value: undefined, done: true }

我们也不能在非Generator函数中使用yield,比如:
运行下面代码

<script>
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);
}
</script>

上面的demo因为callback是一个普通函数, 所以编译的时候直接抛出错误提示, 我们需要改成在Generator的函数体中:
运行下面代码

<script>
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);
}
</script>

或者有个更奇怪的方法,我们把数组的forEach改成Generator函数:
运行下面代码

<script>
var arr = [1, [[2, 3], 4], [5, 6]];
Array.prototype.forEach = function* (callback) {
for(var i=0; i<this.length; i++) {
yield* callback(this[i],i ,this[i]);
}
}
var flat = function* (a) {
yield* a.forEach(function* (item) {
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
})
}; for (var f of flat(arr)){
console.log(f);
}
</script>

而且Iterator的return的值不会被for...of循环到 , 也不会被扩展符遍历到, 以下Demo的return 2 和yield 3完全不生效了, 这个是要注意的;
运行下面代码

function* gen() {
yield 0;
yield 1;
return 2;
yield 3;
};
let g = gen();
console.log([...g]); //输出:[ 0, 1 ]
for(let foo of g) {
console.log( foo ); //输出 0, 1
}

yield*
yield*这种语句让我们可以在Generator函数里面再套一个Generator, 当然你要在一个Generator里面调用另外的Generator需要使用: yield* 函数() 这种语法, 都是套路啊:
运行下面代码

function* foo() {
yield 0;
yield 1;
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
};

next()方法
Generator函数返回的Iterator执行next()方法以后, 返回值的结构为:
运行下面代码
{
value : "value", //value为返回的值
done : false //done的值为一个布尔值, 如果Interator未遍历完毕, 他会返回false, 否则返回true;
}
所以我们可以模拟一个Generator生成器, 利用闭包保存变量, 每一次执行next()方法, 都模拟生成一个{value:value,done:false}的键值对:
运行下面代码

function gen(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}; var it = gen(["arr0", "arr1", "arr2", "arr3"]);
console.log( it.next() );
console.log( it.next() );
console.log( it.next() );
console.log( it.next() );
console.log( it.next() );

再浪一点的话,我们也可以模拟一个对象的Iterator, 因为本身对象是没有Iterator的, 我们为对象添加[Symbol.iterator]方法:
运行下面代码
next()方法的参数
如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值 ,这个特性在异步处理中是非常重要的, 因为在执行异步代码以后, 有时候需要上一个异步的结果, 作为下次异步的参数, 如此循环::
运行下面代码

<script>
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 }
</script>

上面的demo看懂了, next()方法的参数怎么使用也就懂了;
throw方法()
如果执行Generator生成器的throw()方法, 如果在Iterator执行到的yield语句写在try{}语句块中, 那么这个错误会被内部的try{}catch(){}捕获 :
运行下面代码

<script>
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获0', e);
}
}; var i = g();
i.next(); //让代码执行到yield处;
try {
i.throw('a');
} catch (e) {
console.log('外部捕获', e);
}
</script>

如果Interator执行到的yield没有写在try{}语句块中, 那么这个错误会被外部的try{}catch(){}语句块捕获;
运行下面代码

<script>
var g = function* () {
while(true) {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
}
}; var i = g();
i.next(); try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
</script>

return()方法:
如果执行Iterator的return()方法, 那么这个迭代器的返回会被强制设置为迭代完毕, 执行return()方法的参数就是这个Iterator的返回值,此时done的状态也为true:
运行下面代码

<script>
function* gen() {
yield 0;
yield 1;
yield 2;
yield 3;
};
let g = gen();
console.log(g.return("heheda")); //输出:{ value: 'heheda', done: true }
</script.

Generator中的this和他的原型
Generator中的this就是谁调用它,那么this就是谁, 我们利用Reflect.apply可以改变Generator的上下文:
运行下面代码

function* gen() {
console.log(this);
yield 0;
};
console.log(gen().next());
console.log(Reflect.apply(gen,"heheda").next());

Generator生成的Iterator,不但继承了Iterator的原型, 也继承了Generator的原型:
运行下面代码

<script>
function* gen() {
console.log(this);
yield 0;
};
gen.prototype.foo = ()=> {
console.log("foo");
}
let g = gen();
console.log(Reflect.getPrototypeOf(g) === gen.prototype); //输出:true
</script>

所以如果要让生成器继承方法, 我们可以这样, 感觉好酷, 但是Generator内部的this是指向原型的, 也就是说已经把原型污染了:
运行下面代码

<script>
function* gen() {
this.bar = "bar";
yield 0;
};
gen.prototype.foo = ()=> {
console.log("foo");
}
let g = Reflect.apply(gen, gen.prototype,[]);
console.log(g.next()); //输出:Object {value: 0, done: false}
console.log(g.bar); //输出:bar
</script>

实际使用:
ajax的异步处理, 利用生成器的特性,不但可以用于ajax的异步处理, 也能够用于浏览器的文件系统filesystem的异步:
运行下面代码

<html>
<head>
<meta charset="utf-8">
<script src="//cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js"></script>
</head>
<body>
<script>
"use strict";
function* main() {
var result = yield request("http://www.filltext.com?rows=10&f={firstName}");
console.log(result);
//do 别的ajax请求;
} function request(url) {
var r = new XMLHttpRequest();
r.open("GET", url, true);
r.onreadystatechange = function () {
if (r.readyState != 4 || r.status != 200) return;
var data = JSON.parse(r.responseText);
//数据成功返回以后, 代码就能够继续往下走了;
it.next(data);
};
r.send();
} var it = main();
it.next();
console.log("执行到这儿啦");
</script>
</body>
</html>

以上代码中的console.log("执行到这儿啦");先被执行了, 然后才出现了ajax的返回结果, 也就说明了Generator函数是异步的了;
利用Generator函数,可以在任意对象上部署iterator接口:
运行下面代码

function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
} let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) {
console.log(key, value); //输出:foo 3 , bar 7
}

参考:
https://davidwalsh.name/es6-generators
https://davidwalsh.name/es6-generators-dive
https://davidwalsh.name/async-generators
https://davidwalsh.name/concurrent-generators
http://www.2ality.com/2015/03/es6-generators.html
http://es6.ruanyifeng.com/#docs/generator
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
Javascript中Generator(生成器)的更多相关文章
- ES6新特性:Javascript中Generator(生成器)
ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajax, filesystem, 或者数组对象遍历等都可以用到: Generator的使用: G ...
- 掌握JavaScript中的迭代器和生成器,顺便了解一下async、await的原理
掌握JavaScript中的迭代器和生成器,顺便了解一下async.await的原理 前言 相信很多人对迭代器和生成器都不陌生,当提到async和await的原理时,大部分人可能都知道async.aw ...
- php中trait(性状)与generator(生成器)
PHP中trait(性状)与generator(生成器) 一.trait (性状) 最近在看Josh Lockhat的<Modern PHP>,这本书很薄.但是其中给出了一个很重要的学习方 ...
- JavaScript中的Generator函数
1. 简介 Generator函数时ES6提供的一种异步编程解决方案.Generator语法行为和普通函数完全不同,我们可以把Generator理解为一个包含了多个内部状态的状态机. 执行Genera ...
- JavaScript中的迭代器和生成器[未排版]
JavaScript中的迭代器 在软件开发领域,"迭代"的意思是按照顺序反复多次执行一段程序,通常会有明确的终止条件. ECMAScript 6规范新增了两个高级特性:迭代器和生成 ...
- JavaScript 中回调地狱的今生前世
1. 讲个笑话 JavaScript 是一门编程语言 2. 异步编程 JavaScript 由于某种原因是被设计为单线程的,同时由于 JavaScript 在设计之初是用于浏览器的 GUI 编程,这也 ...
- 理解 JavaScript 中的 for…of 循环
什么是 for…of 循环 for...of 语句创建一个循环来迭代可迭代的对象.在 ES6 中引入的 for...of 循环,以替代 for...in 和 forEach() ,并支持新的迭代协议. ...
- JavaScript中的异步操作
什么是异步操作? 异步模式并不难理解,比如任务A.B.C,执行A之后执行B,但是B是一个耗时的工作,所以,把B放在任务队列中,去执行C,然后B的一些I/O等返回结果之后,再去执行B,这就是异步操作. ...
- ES6笔记(5)-- Generator生成器函数
系列文章 -- ES6笔记系列 接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术. 在异步编程中,还 ...
随机推荐
- iOS:位置相关(18-03-09更)
1.定位设置 2.定位页面逻辑 1.定位设置 2.定位页面逻辑 1).第一次进入该VC,在 viewDidLoad 调用刷新页面 refreshLocationView .这时用户还没决定,会刷出“正 ...
- 获取Linux下的IP地址 java代码
/** * 获取Linux下的IP地址 * * @return IP地址 * @throws SocketException */ public static String getLinuxLocal ...
- OO课程总结
OO课程总结 OO终于划上了句号,这学期熬夜熬得最多的一门课,掉了最多头发的一门课. 一.测试与正确性 测试是最最最常见的用来找到程序错误以及验证程序正确的手段,在之前的作业中,写完代码还是会存在很多 ...
- TopJUI Combobox onSelect 事件失效BUG
版本:2.2.8 onChange : function(b, c) 两个参数:当前选择后在Combobox中显示的数据,实际是textField:另一个是改变前的数据 onSelect : func ...
- Windows搭建SFTP服务器
1.项目需要搭建一个SFTP服务器,网上搜了一下,用的是freeSSHd软件,网上查一下我用的是1.3.1版本https://freesshd.updatestar.com/网址自己下载即可. 2.安 ...
- pdf.js 打印出错
两种方法:1.使用0.8.223版本的pdf.js2.viewer.js中 line 3642 PRINT_OUTPUT_SCALE=1,line 3639 pdfPage.getViewPort(2 ...
- php中的Register Globals
参考: http://php.net/manual/zh/security.globals.php
- 开篇~试试word写博客
word发布博客设置,mark一下 原帖地址:http://www.cnblogs.com/liuxianan/archive/2013/04/13/3018732.html word代码高亮插件:h ...
- 20155206 2016-2017-2 《JAVA程序设计》 第二周学习总结
20155206 2016-2017-2<JAVA程序设计>第二周学习总结 教材学习内容总结 类型 整数 字节 浮点数 字符 布尔 变量 变量在命名时,不可以使用数字或一些特殊字符:*.& ...
- 20155236 《信息安全概论》实验二(Windows系统口令破解)实验报告
20155236 <信息安全概论>实验二(Windows系统口令破解)实验报告 北京电子科技学院(BESTI) 实验报告 课程:信息安全概论 班级:1552 姓名:范晨歌 学号:20155 ...