1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 10:22:23 +00:00

修改generator

This commit is contained in:
Ruan Yifeng 2014-12-26 09:08:56 +08:00
parent 956a8860cc
commit 15547a5abc
2 changed files with 213 additions and 178 deletions

View File

@ -2,7 +2,7 @@
## 含义
所谓Generator有多种理解角度。首先可以把它理解成一个内部状态的遍历器每调用一次遍历器内部状态发生一次改变可以理解成发生某些事件。ES6引入Generator函数作用就是可以完全控制内部状态的变化依次遍历这些状态。
所谓Generator有多种理解角度。首先可以把它理解成一个内部状态的遍历器每调用一次内部状态发生一次改变可以理解成发生某些事件。ES6引入Generator函数作用就是可以完全控制内部状态的变化依次遍历这些状态。
在形式上Generator是一个普通函数但是有两个特征。一是function命令与函数名之间有一个星号二是函数体内部使用yield语句定义遍历器的每个成员即不同的内部状态yield语句在英语里的意思就是“产出”
@ -165,6 +165,217 @@ for (let n of fibonacci()) {
从上面代码可见使用for...of语句时不需要使用next方法。
## throw方法
Generator函数还有一个特点它可以在函数体外抛出错误然后在函数体内捕获。
```javascript
var g = function* () {
while (true) {
try {
yield;
} catch (e) {
if (e != 'a') {
throw e;
}
console.log('内部捕获', e);
}
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
```
上面代码中遍历器i连续抛出两个错误。第一个错误被Generator函数体内的catch捕获然后Generator函数执行完成于是第二个错误被函数体外的catch捕获。
这种函数体内捕获错误的机制,大大方便了对错误的处理。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数写一个错误处理语句。
```javascript
foo('a', function (a) {
if (a.error) {
throw new Error(a.error);
}
foo('b', function (b) {
if (b.error) {
throw new Error(b.error);
}
foo('c', function (c) {
if (c.error) {
throw new Error(c.error);
}
console.log(a, b, c);
});
});
});
```
使用Generator函数可以大大简化上面的代码。
```javascript
function* g(){
try {
var a = yield foo('a');
var b = yield foo('b');
var c = yield foo('c');
} catch (e) {
console.log(e);
}
console.log(a, b, c);
}
```
## yield*语句
如果yield命令后面跟的是一个遍历器需要在yield命令后面加上星号表明它返回的是一个遍历器。这被称为yield*语句。
```javascript
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
```
上面代码中delegatingIterator是代理者delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值是一个遍历器所以要用星号表示。运行结果就是使用一个遍历器遍历了多个Genertor函数有递归的效果。
下面是一个稍微复杂的例子使用yield*语句遍历完全二叉树。
```javascript
// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// 下面是中序inorder遍历函数。
// 由于返回的是一个遍历器所以要用generator函数。
// 函数体内采用递归算法所以左树和右树要用yield*遍历
function* inorder(t) {
if (t) {
yield* inorder(t.left);
yield t.label;
yield* inorder(t.right);
}
}
// 下面生成二叉树
function make(array) {
// 判断是否为叶节点
if (array.length == 1) return new Tree(null, array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];
for (let node of inorder(tree)) {
result.push(node);
}
result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
```
## 作为对象属性的Generator函数
如果一个对象的属性是Generator函数可以简写成下面的形式。
```javascript
let obj = {
* myGeneratorMethod() {
···
}
};
```
它的完整形式如下,两者是等价的。
```javascript
let obj = {
myGeneratorMethod: function* () {
···
}
};
```
## Generator与状态机
Generator是实现状态机的最佳结构。比如下面的clock函数就是一个状态机。
```javascript
var ticking = true;
var clock = function() {
if (ticking)
console.log('Tick!');
else
console.log('Tock!');
ticking = !ticking;
}
```
上面代码的clock函数一共有两种状态Tick和Tock每运行一次就改变一次状态。这个函数如果用Generator实现就是下面这样。
```javascript
var clock = function*(_) {
while (true) {
yield _;
console.log('Tick!');
yield _;
console.log('Tock!');
}
};
```
上面的Generator实现与ES5实现对比可以看到少了用来保存状态的外部变量ticking这样就更简洁更安全状态不会被非法篡改、更符合函数式编程的思想在写法上也更优雅。Generator之所以可以不用外部变量保存状态是因为它本身就包含了一个状态信息即目前是否处于暂停态。
## 应用
Generator可以暂停函数执行返回任意表达式的值。这种特点使得Generator有多种应用场景。
@ -350,180 +561,3 @@ function doStuff() {
```
上面的函数可以用一模一样的for...of循环处理两相一比较就不难看出Generator使得数据或者操作具备了类似数组的接口。
## throw方法
Generator函数还有一个特点它可以在函数体外抛出错误然后在函数体内捕获。
```javascript
var g = function* () {
while (true) {
try {
yield;
} catch (e) {
if (e != 'a') {
throw e;
}
console.log('内部捕获', e);
}
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
```
上面代码中遍历器i连续抛出两个错误。第一个错误被Generator函数体内的catch捕获然后Generator函数执行完成于是第二个错误被函数体外的catch捕获。
这种函数体内捕获错误的机制,大大方便了对错误的处理。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数写一个错误处理语句。
```javascript
foo('a', function (a) {
if (a.error) {
throw new Error(a.error);
}
foo('b', function (b) {
if (b.error) {
throw new Error(b.error);
}
foo('c', function (c) {
if (c.error) {
throw new Error(c.error);
}
console.log(a, b, c);
});
});
});
```
使用Generator函数可以大大简化上面的代码。
```javascript
function* g(){
try {
var a = yield foo('a');
var b = yield foo('b');
var c = yield foo('c');
} catch (e) {
console.log(e);
}
console.log(a, b, c);
}
```
## yield*语句
如果yield命令后面跟的是一个遍历器需要在yield命令后面加上星号表明它返回的是一个遍历器。这被称为yield*语句。
```javascript
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
```
上面代码中delegatingIterator是代理者delegatedIterator是被代理者。由于`yield* delegatedIterator`语句得到的值是一个遍历器所以要用星号表示。运行结果就是使用一个遍历器遍历了多个Genertor函数有递归的效果。
下面是一个稍微复杂的例子使用yield*语句遍历完全二叉树。
```javascript
// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// 下面是中序inorder遍历函数。
// 由于返回的是一个遍历器所以要用generator函数。
// 函数体内采用递归算法所以左树和右树要用yield*遍历
function* inorder(t) {
if (t) {
yield* inorder(t.left);
yield t.label;
yield* inorder(t.right);
}
}
// 下面生成二叉树
function make(array) {
// 判断是否为叶节点
if (array.length == 1) return new Tree(null, array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];
for (let node of inorder(tree)) {
result.push(node);
}
result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
```
## 作为对象属性的Generator函数
如果一个对象的属性是Generator函数可以简写成下面的形式。
```javascript
let obj = {
* myGeneratorMethod() {
···
}
};
```
它的完整形式如下,两者是等价的。
```javascript
let obj = {
myGeneratorMethod: function* () {
···
}
};
```

View File

@ -47,6 +47,7 @@
- Kyle Simpson, [ES6 Generators: Complete Series](http://davidwalsh.name/es6-generators): 由浅入深探讨Generator的系列文章共四篇
- Gajus Kuizinas, [The Definitive Guide to the JavaScript Generators](http://gajus.com/blog/2/the-definetive-guide-to-the-javascript-generators): 对Generator的综合介绍
- Jan Krems, [Generators Are Like Arrays](https://gist.github.com/jkrems/04a2b34fb9893e4c2b5c): 讨论Generator可以被当作数据结构看待
- Harold Cooper, [Coroutine Event Loops in Javascript](http://syzygy.st/javascript-coroutines/): Generator用于实现状态机
## Promise对象