ES6引进的最令人兴奋的特性就是一种新的函数生成方式,称为生成器(generator)。名称有点奇怪,但是第一眼看上去行为更加奇怪。文章主要介绍生成器如何工作,然后让你明白为什么他们对于未来的JS会有很大的影响。

完成运行

首先看看生成器和普通函数有什么不同。无论你是否已经意识到,关于你的函数,总是可以很基本的假设一些东西:一但函数开始运行,它总是在其他JS代码可以运行前运行完毕。例子:

setTimeout(function(){
console.log("Hello World");
},1); function foo() {
// NOTE: don't ever do crazy long-running loops like this
for (var i=0; i<=1E10; i++) {
console.log(i);
}
} foo();
// 0..1E10
// "Hello World"

在这里,完成for循环会花相当长的时间,比1毫秒长很多,但是我们的计时器回调函数console.log("Hello World")并不能在foo()函数运行期间打断他,所以它卡住了,排在最后面(在循环后面),耐心地等待轮到他。

但是如果foo()可以被打断是什么样的?难道不会对程序造成严重破坏吗?

这就是噩梦般的多线程编程挑战,但是很幸运,在JS领土中,不用担心这种情况,因为JS总是单线程的(在任何给定的时间内,只有一条命令\函数执行)。

注:Web开发人员对于一部分JS程序,可以作为一个独立的线程来运行,完全与主JS线程并行。这样不会引起多线程并行到程序中的理由,是两个线程只能通过普通异步事件和彼此交互,而异步事件遵循的是一次只执行一条的原则。

运行...停止...运行

通过ES6生成器,有了一种不同的函数,这种函数可以停在中间,一次或者多次,过后又恢复,允许其他代码在这个停止期运行。

如果阅读过关于并发或者多线程的任何东西,会看到“合作”期,即一个进程(一个函数)自己选择什么时候允许打断,以便它可以和其他代码“合作”。这一概念与"先发"形成对比,表明进程/函数可能违背其意愿被打断。

ES6生成器函数在并发中是“合作”的。在生成器函数体内,可以使用新的yield关键字从内部暂停自己。没有什么可以从外部暂停一个生成器,函数在内部遇到yield时可以暂停自己。

但是,生成器函数包含yield暂停自己的时候,它不能恢复自己。必须使用外部控制来重启生成器。后续会介绍这个怎么发生的。

所以,一个生成器函数可以停止,被重新启动,可以自定义次数。事实上,可以使用一个无限循环(类似while (true) { .. })来指定一个生成器函数。在平常的JS程序里面这可能是疯狂的或者是一个错误,对于生成器函数来说是很理智的,有时就是你想要做到的!

更重要的,这个停止和开始并不是在生成器函数执行中的一个控制,允许2通道消息传入和传出生成器。在平常的函数中,在开始可以获取到参数,在结尾处获得一个return值。利用生成器函数,使用每个yield向外输出消息,使用每个start回送消息。

语法!

让我们看看这个新的、令人兴奋的生成器函数的语法。

首先,新的声明语法:

function *foo() {
// ..
}

注意到这里的*了么?这就是全新的有点奇怪的格式。在其他的一些语言里面,看上去很像糟糕的函数返回指针。但是不要疑惑!这只是一个标记生成器函数的方式。
    你也许在其他文章里面看到过function* foo(){ } 来替代 function *foo(){ } (*的位置不同),这两种情况都是合法的,但是最近觉得function *foo() { }更明确一些,也就是这里用到的方式。

现在我们讨论一下生成器函数的内容。大多数情况下生成器函数只是普通的JS函数。在生成器函数内部基本没什么新的语法可学的。

我们主要必须玩会的新玩具,在上面提到过,就是yield关键字。yield__被称为"yield表达式"(不是声明)是因为当启动生成器的时候,我们会传递一个值进去,传递进去的值会是yield表达式的计算结果。例如:

function *foo() {
var x = 1 + (yield "foo");
console.log(x);
}

yield "foo"表达式会在暂停生成器函数的时候将foo值传出来,当生成器重新启动的时候,传递进来的值会是那个表达式的结果,会加1指给x.
    看到2通道的交互了么?你将"foo"传出,暂停了自己,在过后的某时间点(可能是立即,也可能是距离现在很长的一段时间),生成器函数会重新启动会传递一个回送值。

在任何表达式的位置,在表达式或声明中可以单独使用yield。yield有一个假定的值undefined:

// note: `foo(..)` here is NOT a generator!!
function foo(x) {
console.log("x: " + x);
} function *bar() {
yield; // just pause
foo( yield ); // pause waiting for a parameter to pass into `foo(..)`
}

生成器迭代器

迭代器是一种特殊的用法,事实上是一种设计模式,当使用next()进入一系列预选的值中的时候,想象下在一个有5个值的数组上使用迭代器的时候,第一个next()调用会返回1,第二个next()调用会返回2,以此类推。等到所有值被返回,next()会返回null或false或者其他标志位来表示你已经迭代完了所有的值。

从外部控制生成器函数的方法是使用生成器迭代器构建和交互。听起来比实际复杂些。例子:

function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}

进入*foo()生成器的值中,需要构建一个迭代器:

var it = foo();

所以使用平常的方法调用生成器函数不会执行它的任何一部分。

这看起来有点奇怪。你也许想要知道,为什么不是var it = new foo()。这语法后的原因很复杂已经超出了这里的讨论范围。

所以,现在开始迭代我们的生成器函数,我们只需:

var message = it.next();

这会从yield 1声明返回1,但那不是我们仅仅得到的。

console.log(message); // { value:1, done:false }

实际上我们从每个next()调用得到了一个对象,它有一个yield传出值的value属性,done是一个表示生成器函数是否运行完成的布尔变量。

继续看迭代器:

console.log( it.next() ); // { value:2, done:false }
console.log( it.next() ); // { value:3, done:false }
console.log( it.next() ); // { value:4, done:false }
console.log( it.next() ); // { value:5, done:false }

有趣的是,done在我们得到5这个值的时候仍然是false。这是因为,技术上,生成器函数没有完成。我们不得不调用最后一个next()调用,如果我们传入一个值,它必须作为yield5表达式的结果。只有那时生成器函数完成了。
    所以,现在:

console.log( it.next() ); // { value:undefined, done:true }

生成器函数的最后的结果是我们调用完成了,但是没有结果给出来。
    你也许想知道,在生成器函数内可以用return么,如果用了,那个值会在value属性里面输出么?

是的...

function *foo() {
yield 1;
return 2;
} var it = foo(); console.log( it.next() ); // { value:1, done:false }
console.log( it.next() ); // { value:2, done:true }

不是...

在生成器内依赖return不是一个好的做法,因为使用for-of循环迭代生成器的时候,最后return的值会被忽略。

为降低风险,我们迭代生成器的时候看看传递进去和出来信息:

function *foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
} var it = foo( 5 ); // note: not sending anything into `next()` here
console.log( it.next() ); // { value:6, done:false }
console.log( it.next( 12 ) ); // { value:8, done:false }
console.log( it.next( 13 ) ); // { value:42, done:true }

可以看到,仍然可以通过内部的foo(5)迭代器实例化调用向内传递参数,就像普通函数一样,使x为5.
    第一个next()调用,我们没有传递任何参数。为什么?因为没有yield表达式来接收传递的参数。

英文原文:http://davidwalsh.name/es6-generators

ES6生成器基础的更多相关文章

  1. 深入探讨ES6生成器

    如果对于ES6生成器不熟悉,请先阅读并运行下http://www.cnblogs.com/linda586586/p/4282359.html里面的代码.当你感觉掌握了基础之后,我们可以深入探讨一些细 ...

  2. 学习ES6生成器(Generator)

    背景 在JS的使用场景中,异步操作的处理是一个不可回避的问题,如果不做任何抽象.组织,只是“跟着感觉走”,那么面对“按顺序发起3个ajax请求”的需求,很容易就能写出如下代码(假设已引入jQuery) ...

  3. 【翻译】ES6生成器简介

    原文地址:http://davidwalsh.name/es6-generators ES6生成器全部文章: The Basics Of ES6 Generators Diving Deeper Wi ...

  4. ES6生成器函数generator

    ES6生成器函数generator generator是ES6新增的一个特殊函数,通过 function* 声明,函数体内通过 yield 来指明函数的暂停点,该函数返回一个迭代器,并且函数执行到 y ...

  5. es6常用基础合集

    es6常用基础合集 在实际开发中,ES6已经非常普及了.掌握ES6的知识变成了一种必须.尽管我们在使用时仍然需要经过babel编译. ES6彻底改变了前端的编码风格,可以说对于前端的影响非常巨大.值得 ...

  6. es6属性基础教学,30分钟包会

    ES6基础智商划重点在实际开发中,ES6已经非常普及了.掌握ES6的知识变成了一种必须.尽管我们在使用时仍然需要经过babel编译.ES6彻底改变了前端的编码风格,可以说对于前端的影响非常巨大.值得高 ...

  7. 小白学 Python(21):生成器基础

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  8. ES6生成器与迭代器

    ES6迭代器的一个例子 function run(taskDef) { var task = taskDef(); var result = task.next(); // 递归执行迭代 functi ...

  9. ES6 的基础教程

    一.介绍 1.历史 ECMAScript和JavaScript ECMA是标准,JS是实现 类似于HTML5是标准,IE10.Chrome.FF都是实现 换句话说,将来也能有其他XXXScript来实 ...

随机推荐

  1. 黄聪:C#如何通过MeasureString、Graphics获取字符串的像素长度

    1.    使用g.MeasureString()获得 使用MeasureString测量出来的字符宽度,总是比实际宽度大一些,而且随着字符的长度增大,貌似实际宽度和测量宽度的差距也越来越大了.查了一 ...

  2. 黄聪:百度知道中对HTML字符实体、字符编号,&开头字符的使用

    http://www.w3school.com.cn/tags/html_ref_entities.html 带有实体名称的 ASCII 实体 结果 描述 实体名称 实体编号 " quota ...

  3. [JS]深入理解JavaScript系列(4):立即调用的函数表达式

    转自:汤姆大叔的博客 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.在详细了解这个之前,我们来谈了解一下"自执行"这个叫法 ...

  4. DB2 SQL性能调优秘笈

    SQL优化技巧 1.去除在谓词列上编写的任何标量函数 2.去除在谓词列上编写的任何数学运算 3.SQL语句的Select部分只写必要的列 4.尽可能不用Distinct 5.尽量将In子查询重写为Ex ...

  5. 如何给ubuntu虚拟机添加硬盘和快捷键(转载)

    From:http://os.51cto.com/art/201003/188721.htm 刚开始建立Ubuntu虚拟机时间,把容量设置为8Gb 了,然后没过几天就没有地方了,郁闷!查了一下,有几种 ...

  6. google protobuf 简单实例

    1.定义proto文件: User.proto package netty; option java_package="myprotobuf"; option java_outer ...

  7. sql2008连接数据库问题

    配置系统未能初始化 (System.Configuration) ------------------------------ 无法识别的配置节 runtime. (C:\Program Files ...

  8. 将多个网页制作成一个CHM文件

    有时我们想将一个网站上的多个页面集中保存起来,在即使没有网络的情况下也能够查看. 这时可以将这些网页保存成.mht的单个文件(在IE中打开时,点击 文件 -> 另存) 再使用Easy CHM去将 ...

  9. ruby字符串学习笔记4

    1 单独处理字符串的字符 如果处理的是ASCII码的文档使用string#each_byte 注意 没有 string#each方法,String#each_byte 速度比 String#scan快 ...

  10. vmware虚拟机挂起后无法再恢复(转)

    之前一直使用vmware调试程序,但有一天它被挂起后,就一直无法恢复. 提示: 无法获取该配置文件上的排他锁 另一个VMware进程可能正在使用此配置文件. 后来在google里查了一下,发现其实是一 ...