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()方法的时候, 函数又会从上次退出去的地方重新开始执行;

  如果把yieldreturn一起使用的话, 那么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(生成器)的更多相关文章

  1. ES6新特性:Javascript中Generator(生成器)

    ES6的很多特性都跟Generator扯上关系,而且实际用处比较广, 包含了任何需要异步的模块, 比如ajax, filesystem, 或者数组对象遍历等都可以用到: Generator的使用: G ...

  2. 掌握JavaScript中的迭代器和生成器,顺便了解一下async、await的原理

    掌握JavaScript中的迭代器和生成器,顺便了解一下async.await的原理 前言 相信很多人对迭代器和生成器都不陌生,当提到async和await的原理时,大部分人可能都知道async.aw ...

  3. php中trait(性状)与generator(生成器)

    PHP中trait(性状)与generator(生成器) 一.trait (性状) 最近在看Josh Lockhat的<Modern PHP>,这本书很薄.但是其中给出了一个很重要的学习方 ...

  4. JavaScript中的Generator函数

    1. 简介 Generator函数时ES6提供的一种异步编程解决方案.Generator语法行为和普通函数完全不同,我们可以把Generator理解为一个包含了多个内部状态的状态机. 执行Genera ...

  5. JavaScript中的迭代器和生成器[未排版]

    JavaScript中的迭代器 在软件开发领域,"迭代"的意思是按照顺序反复多次执行一段程序,通常会有明确的终止条件. ECMAScript 6规范新增了两个高级特性:迭代器和生成 ...

  6. JavaScript 中回调地狱的今生前世

    1. 讲个笑话 JavaScript 是一门编程语言 2. 异步编程 JavaScript 由于某种原因是被设计为单线程的,同时由于 JavaScript 在设计之初是用于浏览器的 GUI 编程,这也 ...

  7. 理解 JavaScript 中的 for…of 循环

    什么是 for…of 循环 for...of 语句创建一个循环来迭代可迭代的对象.在 ES6 中引入的 for...of 循环,以替代 for...in 和 forEach() ,并支持新的迭代协议. ...

  8. JavaScript中的异步操作

    什么是异步操作? 异步模式并不难理解,比如任务A.B.C,执行A之后执行B,但是B是一个耗时的工作,所以,把B放在任务队列中,去执行C,然后B的一些I/O等返回结果之后,再去执行B,这就是异步操作. ...

  9. ES6笔记(5)-- Generator生成器函数

    系列文章 -- ES6笔记系列 接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术. 在异步编程中,还 ...

随机推荐

  1. Vcenter虚拟化三部曲----VMWare ESXi 5.5安装及配置

    VMWare ESXi 5.5安装大概过程如下:制作虚拟化ESXi系统的USB启动盘,安装ESXi系统到USB,用USB启动ESXi系统.比较难理解,下面图解过程. 下载UNetbootin   (下 ...

  2. Ubuntu install 错误 E:Unable to locate package

    今天在 Ubuntu 上执行 sudo apt install sl 命令,结果报错:E:Unable to locate package sl 上网查询了一下,先更新一下 apt-get,执行:su ...

  3. JDK1.8降到1.7技巧

    前言: 最近部署一个产品,该产品不支持JDK1.8,碰巧我的机器安装的是1.8,这就需要降到1.7才能部署启动成功.那么我也是不赞成卸载1.8来安装1.7,因为很多时候可能需要1.8和1.7来回切换. ...

  4. windows 使用npm安装webpack 4.0以及配置问题的解决办法

    输入cmd点击打开 输入node -v 出现nodejs版本号 输入npm -v 出现npm版本号则安装npm安装成功, 2.安装webpack 桌面新建一个webpack-test文件夹,点击进入文 ...

  5. 执行mongod其他实例出现的问题

    windows环境下,配置其他mongo实例,会出现一些问题 1.配置路径不对,执行bat文件时出现闪屏 根据提示创建C:\data\db\ 目录(因为mongodb默认在/data/db下创建数据库 ...

  6. laravel5.5源码笔记(六、中间件)

    laravel中的中间件作为一个请求与响应的过滤器,主要分为两个功能. 1.在请求到达控制器层之前进行拦截与过滤,只有通过验证的请求才能到达controller层 2.或者是在controller中运 ...

  7. linux-2.6.22.6内核启动分析之编译体验

    1 解压缩.打补丁操作 1.1 打开ubuntu,通过FTP将windows相应文件夹下的linux-2.6.22.6.tar.bz2和补丁文件linux-2.6.22.6-jz2440.patch上 ...

  8. stm32 IO口八种模式区别

    初学STM32,遇到I/O口八种模式的介绍,网上查了一下资料,下面简明写出这几种模式的区别,有不对的地方请大家多多指正! 上拉输入模式:区别在于没有输入信号的时候默认输入高电平(因为有弱上拉).下拉输 ...

  9. maven拓展——使用tomcat插件运行maven项目

    首先,在pom.xml中配置插件: <build> <plugins> <plugin> <groupId>org.apache.tomcat.mave ...

  10. 一个servlet如何处理多个请求

    页面1:表单的action=login?method=login 页面2:表单的action=login?method=insert ..... 然后通过method的值采用不同方法进行处理. 如下 ...