1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-28 21:32:20 +00:00

edit generator

This commit is contained in:
Ruan Yifeng 2015-07-12 20:08:11 +08:00
parent 004ad1717f
commit 7232ad8061

View File

@ -8,7 +8,7 @@ Generator函数是ES6提供的一种异步编程解决方案语法行为与
Generator函数有多种理解角度。从语法上首先可以把它理解成一个函数的内部状态的遍历器也就是说Generator函数是一个状态机。它每调用一次就进入下一个内部状态。Generator函数可以控制内部状态的变化依次遍历这些状态。
形式上Generator函数是一个普通函数但是有两个特征。一是function命令与函数名之间有一个星号二是函数体内部使用yield语句定义遍历器的每个成员即不同的内部状态yield语句在英语里的意思就是“产出”
形式上Generator函数是一个普通函数但是有两个特征。一是function命令与函数名之间有一个星号二是函数体内部使用yield语句定义遍历器的每个成员即不同的内部状态yield语句在英语里的意思就是“产出”
```javascript
function* helloWorldGenerator() {
@ -236,7 +236,6 @@ it.next(13)
for...of循环可以自动遍历Generator函数且此时不再需要调用next方法。
```javascript
function *foo() {
yield 1;
yield 2;
@ -250,7 +249,6 @@ for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
```
上面代码使用for...of循环依次显示5个yield语句的值。这里需要注意一旦next方法的返回对象的done属性为truefor...of循环就会中止且不包含该返回对象所以上面代码的return语句返回的6不包括在for...of循环之中。
@ -258,7 +256,6 @@ for (let v of foo()) {
下面是一个利用generator函数和for...of循环实现斐波那契数列的例子。
```javascript
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
@ -271,7 +268,6 @@ for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
```
从上面代码可见使用for...of语句时不需要使用next方法。
@ -513,7 +509,6 @@ log(g());
如果yield命令后面跟的是一个遍历器需要在yield命令后面加上星号表明它返回的是一个遍历器。这被称为yield*语句。
```javascript
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
@ -532,11 +527,32 @@ for(let value of delegatingIterator) {
// "Hello!"
// "Bye!"
// "Ok, bye."
```
上面代码中delegatingIterator是代理者delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值是一个遍历器所以要用星号表示。运行结果就是使用一个遍历器遍历了多个Genertor函数有递归的效果。
yield*语句等同于在Generator函数内部部署一个for...of循环。
```javascript
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}
// 等同于
function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}
```
上面代码说明yield*不过是for...of的一种简写形式完全可以用后者替代前者。
再来看一个对比的例子。
```javascript
@ -572,13 +588,11 @@ gen.next() // -> 'close'
如果`yield*`后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。
```javascript
function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
```
上面代码中yield命令后面如果不加星号返回的是整个数组加了星号就表示返回的是数组的遍历器。
@ -586,7 +600,6 @@ gen().next() // { value:"a", done:false }
如果被代理的Generator函数有return语句那么就可以向代理它的Generator函数返回数据。
```javascript
function *foo() {
yield 2;
yield 3;
@ -607,7 +620,6 @@ it.next(); //
it.next(); //
it.next(); // "v: foo"
it.next(); //
```
上面代码在第四次调用next方法的时候屏幕上会有输出这是因为函数foo的return语句向函数bar提供了返回值。
@ -615,7 +627,6 @@ it.next(); //
`yield*`命令可以很方便地取出嵌套数组的所有成员。
```javascript
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
@ -636,13 +647,11 @@ for(let x of iterTree(tree)) {
// c
// d
// e
```
下面是一个稍微复杂的例子使用yield*语句遍历完全二叉树。
```javascript
// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
@ -678,7 +687,6 @@ for (let node of inorder(tree)) {
result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
```
## 作为对象属性的Generator函数
@ -724,7 +732,7 @@ console.log(...squared);
// 0 1 4 9 16 25
```
“推导”这种语法结构,在ES6只能用于数组ES7将其推广到了Generator函数。for...of循环会自动调用遍历器的next方法将返回值的value属性作为数组的一个成员。
“推导”这种语法结构,不仅可以用于数组ES7将其推广到了Generator函数。for...of循环会自动调用遍历器的next方法将返回值的value属性作为数组的一个成员。
Generator函数推导是对数组结构的一种模拟它的最大优点是惰性求值即直到真正用到时才会求值这样可以保证效率。请看下面的例子。
@ -759,7 +767,6 @@ console.log(squared.next());
Generator是实现状态机的最佳结构。比如下面的clock函数就是一个状态机。
```javascript
var ticking = true;
var clock = function() {
if (ticking)
@ -768,13 +775,11 @@ var clock = function() {
console.log('Tock!');
ticking = !ticking;
}
```
上面代码的clock函数一共有两种状态Tick和Tock每运行一次就改变一次状态。这个函数如果用Generator实现就是下面这样。
```javascript
var clock = function*(_) {
while (true) {
yield _;
@ -783,7 +788,6 @@ var clock = function*(_) {
console.log('Tock!');
}
};
```
上面的Generator实现与ES5实现对比可以看到少了用来保存状态的外部变量ticking这样就更简洁更安全状态不会被非法篡改、更符合函数式编程的思想在写法上也更优雅。Generator之所以可以不用外部变量保存状态是因为它本身就包含了一个状态信息即目前是否处于暂停态。
@ -817,19 +821,17 @@ Generator可以暂停函数执行返回任意表达式的值。这种特点
Generator函数的暂停执行的效果意味着可以把异步操作写在yield语句里面等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了因为异步操作的后续操作可以放在yield语句下面反正要等到调用next方法时再执行。所以Generator函数的一个重要实际意义就是用来处理异步操作改写回调函数。
```javascript
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()
loader.next()
// 卸载UI
loader.next()
```
上面代码表示第一次调用loadUI函数时该函数不会执行仅返回一个遍历器。下一次对该遍历器调用next方法则会显示Loading界面并且异步加载数据。等到数据加载完成再一次使用next方法则会隐藏Loading界面。可以看到这种写法的好处是所有Loading界面的逻辑都被封装在一个函数按部就班非常清晰。
@ -837,7 +839,6 @@ loader.next()
Ajax是典型的异步操作通过Generator函数部署Ajax操作可以用同步的方式表达。
```javascript
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
@ -852,7 +853,6 @@ function request(url) {
var it = main();
it.next();
```
上面代码的main函数就是通过Ajax操作获取数据。可以看到除了多了一个yield它几乎与同步操作的写法完全一样。注意makeAjaxCall函数中的next方法必须加上response参数因为yield语句构成的表达式本身是没有值的总是等于undefined。
@ -860,18 +860,16 @@ it.next();
下面是另一个例子通过Generator函数逐行读取文本文件。
```javascript
function* numbers() {
let file = new FileReader("numbers.txt");
try {
while(!file.eof) {
yield parseInt(file.readLine(), 10);
}
} finally {
file.close();
}
let file = new FileReader("numbers.txt");
try {
while(!file.eof) {
yield parseInt(file.readLine(), 10);
}
} finally {
file.close();
}
}
```
上面代码打开文本文件使用yield语句可以手动逐行读取文件。
@ -881,7 +879,6 @@ function* numbers() {
如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。
```javascript
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
@ -891,32 +888,28 @@ step1(function (value1) {
});
});
});
```
采用Promise改写上面的代码。
```javascript
Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
}, function (error) {
// Handle any error from step1 through step4
})
.done();
})
.done();
```
上面代码已经把回调函数改成了直线执行的形式但是加入了大量Promise的语法。Generator函数可以进一步改善代码运行流程。
```javascript
function* longRunningTask() {
try {
try {
var value1 = yield step1();
var value2 = yield step2(value1);
var value3 = yield step3(value2);
@ -926,13 +919,11 @@ function* longRunningTask() {
// Handle any error from step1 through step4
}
}
```
然后,使用一个函数,按次序自动执行所有步骤。
```javascript
scheduler(longRunningTask());
function scheduler(task) {
@ -945,15 +936,13 @@ function scheduler(task) {
}
}, 0);
}
```
注意yield语句是同步运行不是异步运行否则就失去了取代回调函数的设计目的了。实际操作中一般让yield语句返回Promise对象。
```javascript
var Q = require('q');
function delay(milliseconds) {
var deferred = Q.defer();
setTimeout(deferred.resolve, milliseconds);
@ -963,7 +952,6 @@ function delay(milliseconds) {
function* f(){
yield delay(100);
};
```
上面代码使用Promise的函数库Qyield语句返回的就是一个Promise对象。
@ -971,7 +959,6 @@ function* f(){
多个任务按顺序一个接一个执行时yield语句可以按顺序排列。多个任务需要并列执行时比如只有A任务和B任务都执行完才能执行C任务可以采用数组的写法。
```javascript
function* parallelDownloads() {
let [text1,text2] = yield [
taskA(),
@ -979,7 +966,6 @@ function* parallelDownloads() {
];
console.log(text1, text2);
}
```
上面代码中yield语句的参数是一个数组成员就是两个任务taskA和taskB只有等这两个任务都完成了才会接着执行下面的语句。
@ -989,24 +975,22 @@ function* parallelDownloads() {
利用Generator函数可以在任意对象上部署iterator接口。
```javascript
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 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);
console.log(key, value);
}
// foo 3
// bar 7
```
上述代码中myObj是一个普通对象通过iterEntries函数就有了iterator接口。也就是说可以在任意对象上部署next方法。
@ -1014,10 +998,9 @@ for (let [key, value] of iterEntries(myObj)) {
下面是一个对数组部署Iterator接口的例子尽管数组原生具有这个接口。
```javascript
function* makeSimpleGenerator(array){
var nextIndex = 0;
while(nextIndex < array.length){
yield array[nextIndex++];
}
@ -1028,7 +1011,6 @@ var gen = makeSimpleGenerator(['yo', 'ya']);
gen.next().value // 'yo'
gen.next().value // 'ya'
gen.next().done // true
```
### 4作为数据结构
@ -1036,29 +1018,24 @@ gen.next().done // true
Generator可以看作是数据结构更确切地说可以看作是一个数组结构因为Generator函数可以返回一系列的值这意味着它可以对任意表达式提供类似数组的接口。
```javascript
function *doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
```
上面代码就是依次返回三个函数但是由于使用了Generator函数导致可以像处理数组那样处理这三个返回的函数。
```javascript
for (task of doStuff()) {
// task是一个函数可以像回调函数那样使用它
}
```
实际上如果用ES5表达完全可以用数组模拟Generator的这种用法。
```javascript
function doStuff() {
return [
fs.readFile.bind(null, 'hello.txt'),
@ -1066,7 +1043,6 @@ function doStuff() {
fs.readFile.bind(null, 'and-such.txt')
];
}
```
上面的函数可以用一模一样的for...of循环处理两相一比较就不难看出Generator使得数据或者操作具备了类似数组的接口。