Skip to content

Commit f8d5619

Browse files
committed
update
1 parent abc86fd commit f8d5619

11 files changed

+954
-40
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,16 @@
3434

3535
- [ES6 惊现 Generator](./part4-generator/01-generator-in-es6.md)
3636
- [Iterator 遍历器](./part4-generator/02-iterator.md)
37-
- [带着 Iterator 来看 Generator](./part4-generator/03-iterator-for-generator.md)
38-
- [Generator 与异步结合](./part4-generator/04-generator-for-async.md)
39-
- [使用大名鼎鼎的 co 库](./part4-generator/05-co.md)
37+
- [Generator 的具体应用](./part4-generator/03-iterator-use.md)
38+
- [Thunk 函数](./part4-generator/04-thunk.md)
39+
- [Generator 与异步操作](./part4-generator/05-generator-for-async.md)
4040
- [koa 中使用 Generator](./part4-generator/06-generator-for-koa.md)
4141
- [Generator 的本质是什么?是否取代了 callback](./part4-generator/07-generator-callback.md)
4242

4343
**part5 async-await**
4444

4545
- ES7 中引入 async-await
46-
- async-await 值不值得期待
47-
- async-await 的本质是什么?是否取代了 callback
46+
- 如何在 nodejs`v6.x`版本中使用 async-await
4847

4948
**最后**
5049

part4-generator/02-iterator.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,130 @@
11
# Iterator 遍历器
22

3+
ES6 中引入了很多此前没有但是却非常重要的概念,`Iterator`就是其中一个。`Iterator`对象是一个指针对象,实现类似于单项链表的数据结构,通过`next()`将指针指向下一个节点 ———— 这里也就是先简单做一个概念性的介绍,后面将通过实例为大家演示。
34

5+
本节演示的代码可参考[这里](./test.js)
46

57
## 本节内容概述
68

9+
- 简介`Symbol`数据类型
10+
- 具有`[Symbol.iterator]`属性的数据类型
11+
- 生成`Iterator`对象
12+
- `Generator`返回的也是`Iterator`对象
13+
- 接下来...
714

15+
## 简介`Symbol`数据类型
16+
17+
`Symbol`是一个特殊的数据类型,和`number` `string`等并列,详细的教程可参考[阮一峰老师 ES6 入门的 Symbol 篇](http://es6.ruanyifeng.com/#docs/symbol)。先看两句程序
18+
19+
```javascript
20+
console.log(Array.prototype.slice) // [Function: slice]
21+
console.log(Array.prototype[Symbol.iterator]) // [Function: values]
22+
```
23+
24+
数组的`slice`属性大家都比较熟悉了,就是一个函数,可以通过`Array.prototype.slice`得到。这里的`slice`是一个字符串,但是我们获取`Array.prototype[Symbol.iterator]`可以得到一个函数,只不过这里的`[Symbol.iterator]``Symbol`数据类型,不是字符串。但是没关系,`Symbol`数据类型也可以作为对象属性的`key`。如下:
25+
26+
```javascript
27+
var obj = {}
28+
obj.a = 100
29+
obj[Symbol.iterator] = 200
30+
console.log(obj) // {a: 100, Symbol(Symbol.iterator): 200}
31+
```
32+
33+
在此小节中,你只需要知道`[Symbol.iterator]`是一个特殊的数据类型`Symbol`类型,但是也可以像`number` `string`类型一样,作为对象的属性`key`来使用
34+
35+
## 原生具有`[Symbol.iterator]`属性的数据类型
36+
37+
在 ES6 中,原生具有`[Symbol.iterator]`属性数据类型有:数组、某些类似数组的对象(如`arguments``NodeList`)、`Set``Map`。其中,`Set``Map`也是 ES6 中新增的数据类型。
38+
39+
```javascript
40+
// 数组
41+
console.log([1, 2, 3][Symbol.iterator]) // function values() { [native code] }
42+
// 某些类似数组的对象,NoeList
43+
console.log(document.getElementsByTagName('div')[Symbol.iterator]) // function values() { [native code] }
44+
```
45+
46+
原生具有`[Symbol.iterator]`属性数据类型有一个特点,就是可以使用`for...of`来取值,例如
47+
48+
```javascript
49+
var item
50+
for (item of [100, 200, 300]) {
51+
console.log(item)
52+
}
53+
// 打印出:100 200 300
54+
// 注意,这里每次获取的 item 是数组的 value,而不是 index ,这一点和 传统 for 循环以及 for...in 完全不一样
55+
```
56+
57+
而具有`[Symbol.iterator]`属性的对象,都可以一键生成一个`Iterator`对象。如何生成以及生成之后什么样子,还有生成之后的作用,下文分解。
58+
59+
不要着急,也不要跳过本文的任何步骤,一步一步跟着我的节奏来看。
60+
61+
## 生成`Iterator`对象
62+
63+
定义一个数组,然后生成数组的`Iterator`对象
64+
65+
```javascript
66+
const arr = [100, 200, 300]
67+
const iterator = arr[Symbol.iterator]() // 通过执行 [Symbol.iterator] 的属性值(函数)来返回一个 iterator 对象
68+
```
69+
70+
好,现在生成了`iterator`,那么该如何使用它呢 ———— 有两种方式:`next``for...of`
71+
72+
先说第一种,`next`
73+
74+
```javascript
75+
console.log(iterator.next()) // { value: 100, done: false }
76+
console.log(iterator.next()) // { value: 200, done: false }
77+
console.log(iterator.next()) // { value: 300, done: false }
78+
console.log(iterator.next()) // { value: undefined, done: true }
79+
```
80+
81+
看到这里,再结合上一节内容,是不是似曾相识的感觉?(额,没有的话,那你就回去重新看上一节的内容吧) `iterator`对象可以通过`next()`方法逐步获取每个元素的值,以`{ value: ..., done: ... }`形式返回,`value`就是值,`done`表示是否到已经获取完成。
82+
83+
再说第二种,`for...of`
84+
85+
```javascript
86+
let i
87+
for (i of iterator) {
88+
console.log(i)
89+
}
90+
// 打印:100 200 300
91+
```
92+
93+
上面使用`for...of`遍历`iterator`对象,可以直接将其值获取出来。这里的“值”就对应着上面`next()`返回的结果的`value`属性
94+
95+
## `Generator`返回的也是`Iterator`对象
96+
97+
看到这里,你大体也应该明白了,上一节演示的`Generator`,就是生成一个`Iterator`对象。因此才会有`next()`,也可以通过`for...of`来遍历。拿出上一节的例子再做一次演示:
98+
99+
```javascript
100+
function* Hello() {
101+
yield 100
102+
yield (function () {return 200})()
103+
return 300
104+
}
105+
const h = Hello()
106+
console.log(h[Symbol.iterator]) // [Function: [Symbol.iterator]]
107+
```
108+
109+
执行`const h = Hello()`得到的就是一个`iterator`对象,因为`h[Symbol.iterator]`是有值的。既然是`iterator`对象,那么就可以使用`next()``for...of`进行操作
110+
111+
```javascript
112+
console.log(h.next()) // { value: 100, done: false }
113+
console.log(h.next()) // { value: 200, done: false }
114+
console.log(h.next()) // { value: 300, done: false }
115+
console.log(h.next()) // { value: undefined, done: true }
116+
117+
let i
118+
for (i of h) {
119+
console.log(i)
120+
}
121+
```
122+
123+
## 接下来...
124+
125+
这一节我们花费很大力气,从`Iterator`又回归到了`Generator`,目的就是为了看看`Generator`到底是一个什么东西。了解其本质,才能更好的使用它,否则总有一种抓瞎的感觉。
126+
127+
接下来我们就`Generator`具体有哪些使用场景。
8128

9129
## 求打赏
10130

part4-generator/03-iterator-for-generator.md

Lines changed: 0 additions & 11 deletions
This file was deleted.

part4-generator/03-iterator-use.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Generator 的具体应用
2+
3+
前面用两节的内容介绍了`Generator`可以让执行处于暂停状态,并且知道了`Generator`返回的是一个`Iterator`对象,这一节就详细介绍一下`Generator`的一些基本用法。
4+
5+
本节演示的代码可参考[这里](./test.js)
6+
7+
## 本节内容概述
8+
9+
- `next``yield`参数传递
10+
- `for...of`的应用示例
11+
- `yield* `语句
12+
- `Generator`中的`this`
13+
- 接下来...
14+
15+
## `next``yield`参数传递
16+
17+
我们之前已经知道,`yield`具有返回数据的功能,如下代码。`yield`后面的数据被返回,存放到返回结果中的`value`属性中。这算是一个方向的参数传递。
18+
19+
```javascript
20+
function* G() {
21+
yield 100
22+
}
23+
const g = G()
24+
console.log( g.next() ) // {value: 100, done: false}
25+
```
26+
27+
还有另外一个方向的参数传递,就是`next``yield`传递,如下代码。
28+
29+
```javascript
30+
function* G() {
31+
const a = yield 100
32+
console.log('a', a) // a aaa
33+
const b = yield 200
34+
console.log('b', b) // b bbb
35+
const c = yield 300
36+
console.log('c', c) // c ccc
37+
}
38+
const g = G()
39+
g.next() // value: 100, done: false
40+
g.next('aaa') // value: 200, done: false
41+
g.next('bbb') // value: 300, done: false
42+
g.next('ccc') // value: undefined, done: true
43+
```
44+
45+
捋一捋上面代码的执行过程:
46+
47+
- 执行第一个`g.next()`时,为传递任何参数,返回的`{value: 100, done: false}`,这个应该没有疑问
48+
- 执行第二个`g.next('aaa')`时,传递的参数是`'aaa'`,这个`'aaa'`就会被赋值到`G`内部的`a`标量中,然后执行`console.log('a', a)`打印出来,最后返回`{value: 200, done: false}`
49+
- 执行第三个、第四个时,道理都是完全一样的,大家自己捋一捋。
50+
51+
**有一个要点需要注意,就`g.next('aaa')`是将`'aaa'`传递给上一个已经执行完了的`yield`语句前面的变量,而不是即将执行的`yield`前面的变量**。这句话要能看明白,看不明白就说明刚才的代码你还没看懂,继续看。
52+
53+
## `for...of`的应用示例
54+
55+
针对`for...of``Iterator`对象的操作之前已经介绍过了,不过这里用一个非常好的例子来展示一下。用简单几行代码实现斐波那契数列。通过之前学过的`Generator`知识,应该不能解读这份代码。
56+
57+
```javascript
58+
function* fibonacci() {
59+
let [prev, curr] = [0, 1]
60+
for (;;) {
61+
[prev, curr] = [curr, prev + curr]
62+
// 将中间值通过 yield 返回,并且保留函数执行的状态,因此可以非常简单的实现 fibonacci
63+
yield curr
64+
}
65+
}
66+
for (let n of fibonacci()) {
67+
if (n > 1000) {
68+
break
69+
}
70+
console.log(n)
71+
}
72+
```
73+
74+
## `yield* `语句
75+
76+
如果有两个`Generator`,想要在第一个中包含第二个,如下需求:
77+
78+
```javascript
79+
function* G1() {
80+
yield 'a'
81+
yield 'b'
82+
}
83+
function* G2() {
84+
yield 'x'
85+
yield 'y'
86+
}
87+
```
88+
89+
针对以上两个`Generator`,我的需求是:一次输出`a x y b`,该如何做?有同学看到这里想起了刚刚学到的`for..of`可以实现————不错,确实可以实现(大家也可以想想到底该如何实现)
90+
91+
但是,这要演示一个更加简洁的方式`yield* `表达式
92+
93+
```javascript
94+
function* G1() {
95+
yield 'a'
96+
yield* G2() // 使用 yield* 执行 G2()
97+
yield 'b'
98+
}
99+
function* G2() {
100+
yield 'x'
101+
yield 'y'
102+
}
103+
for (let item of G1()) {
104+
console.log(item)
105+
}
106+
```
107+
108+
之前学过的`yield`后面会接一个普通的 JS 对象,而`yield* `后面会接一个`Generator`,而且会把它其中的`yield`按照规则来一步一步执行。**如果有多个`Generator`串联使用的话(例如`Koa`源码中),用`yield* `来操作非常方便**
109+
110+
## `Generator`中的`this`
111+
112+
对于以下这种写法,大家可能会和构造函数创建对象的写法产生混淆,这里一定要注意 —— **Generator 不是函数,更不是构造函数**
113+
114+
```javascript
115+
function* G() {}
116+
const g = G()
117+
```
118+
119+
而以下这种写法,更加不会成功。只有构造函数才会这么用,构造函数返回的是`this`,而`Generator`返回的是一个`Iterator`对象。完全是两码事,千万不要搞混了。
120+
121+
```javascript
122+
function* G() {
123+
this.a = 10
124+
}
125+
const g = G()
126+
console.log(g.a) // 报错
127+
```
128+
129+
## 接下来...
130+
131+
本节基本介绍了`Generator`的最常见的用法,但是还是没有和咱们的最终目的————异步操作————沾上关系,而且现在看来有点八竿子打不着的关系。但是话说回来,这几节内容,你也学到了不少知识啊。
132+
133+
别急哈,即便是下一节,它们还不会有联系,再下一节就真相大白了。下一节我们又给出一个新概念————`Thunk`函数
134+
135+
## 求打赏
136+
137+
如果你看完了,感觉还不错,欢迎给我打赏 ———— 以激励我更多输出优质内容
138+
139+
![](http://images2015.cnblogs.com/blog/138012/201702/138012-20170228112237798-1507196643.png)

part4-generator/04-generator-for-async.md

Lines changed: 0 additions & 11 deletions
This file was deleted.

part4-generator/04-thunk.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Thunk 函数
2+
3+
要想让`Generator`和异步操作产生联系,就必须过`thunk`函数这一关。这一关过了之后,立即就可以着手异步操作的事情,因此大家再坚持坚持。至于`thunk`函数是什么,下文会详细演示。
4+
5+
本节演示的代码可参考[这里](./test.js)
6+
7+
## 本节内容概述
8+
9+
- 一个普通的异步函数
10+
- 封装成一个`thunk`函数
11+
- `thunk`函数的特点
12+
- 使用`thunkify`
13+
- 接下来...
14+
15+
## 一个普通的异步函数
16+
17+
就用 nodejs 中读取文件的函数为例,通常都这么写
18+
19+
```javascript
20+
fs.readFile('data1.json', 'utf-8', (err, data) => {
21+
// 获取文件内容
22+
})
23+
```
24+
25+
其实这个写法就是将三个参数都传递给`fs.readFile`这个方法,其中最后一个参数是一个`callback`函数。这种函数叫做 **多参数函数**,我们接下来做一个改造
26+
27+
## 封装成一个`thunk`函数
28+
29+
改造的代码如下所示。不过是不是感觉越改造越复杂了?不过请相信:你看到的复杂仅仅是表面的,**这一点东西变的复杂,是为了让以后更加复杂的东西变得简单**。对于个体而言,随性比较简单,遵守规则比较复杂;但是对于整体(包含很多个体)而言,大家都随性就不好控制了,而大家都遵守规则就很容易管理 ———— 就是这个道理!
30+
31+
```javascript
32+
const thunk = function (fileName, codeType) {
33+
// 返回一个只接受 callback 参数的函数
34+
return function (callback) {
35+
fs.readFile(fileName, codeType, callback)
36+
}
37+
}
38+
const readFileThunk = thunk('data1.json', 'utf-8')
39+
readFileThunk((err, data) => {
40+
// 获取文件内容
41+
})
42+
```
43+
44+
先自己看一看以上代码,应该是能看懂的,但是你可能就是看懂了却不知道这么做的意义在哪里。意义先不管,先把它看懂,意义下一节就会看到。
45+
46+
- 执行`const readFileThunk = thunk('data1.json', 'utf-8')`返回的其实是一个函数
47+
- `readFileThunk`这个函数,只接受一个参数,而且这个参数是一个`callback`函数
48+
49+
## `thunk`函数的特点
50+
51+
就上上面的代码,我们经过对传统的异步操作函数进行封装,**得到一个只有一个参数的函数,而且这个参数是一个`callback`函数,那这就是一个`thunk`函数**。就像上面代码中`readFileThunk`一样。
52+
53+
## 使用`thunkify`
54+
55+
上面代码的封装,是我们手动来做的,但是没遇到一个情况就需要手动做吗?在这个开源的时代当让不会这样,直接使用第三方的`thunkify`就好了。
56+
57+
首先要安装`npm i thunkify --save`,然后在代码的最上方引用`const thunkify = require('thunkify')`。最后,上面我们手动写的代码,完全可以简化成这几行,非常简单!
58+
59+
```javascript
60+
const thunk = thunkify(fs.readFile)
61+
const readFileThunk = thunk('data1.json', 'utf-8')
62+
readFileThunk((err, data) => {
63+
// 获取文件内容
64+
})
65+
```
66+
67+
## 接下来...
68+
69+
了解了`thunk`函数,我们立刻就将`Generator`和异步操作进行结合
70+
71+
## 求打赏
72+
73+
如果你看完了,感觉还不错,欢迎给我打赏 ———— 以激励我更多输出优质内容
74+
75+
![](http://images2015.cnblogs.com/blog/138012/201702/138012-20170228112237798-1507196643.png)

0 commit comments

Comments
 (0)