携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 31 天,点击查看活动详情
start
- 今天学习什么? ES6的 Generator 函数。
开始
Generator
定义:
阮一峰的ES6入门:Generator 函数是ES6提供的异步解决方案,执行 Generator 函数会返回一个遍历器对象。
MDN:function\*
这种声明方式 (function
关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个 Generator
对象。
写法:
Generator 函数是一个普通函数,但是有两个特征:
-
function
关键字与函数名之间有一个星号; -
函数体内部使用
yield
表达式,定义不同的内部状态
function* myGenerator() {
yield 'hello'
yield 'world'
return 'ending'
}
var obj = myGenerator()
console.log(obj)
// Object [Generator] {}
console.log(obj.next())
// { value: 'hello', done: false }
console.log(obj.next())
// { value: 'world', done: false }
console.log(obj.next())
// { value: 'ending', done: true }
简单总结一下上述内容:
-
Generator
意思是 “生成器”; -
yield
意思是“产出”; -
生成器函数function后的
*
存放的位置,ES6没有规定;以下写法都是可以的
function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } // 一般偏向于这种写法 function*foo(x, y) { ··· }
-
生成器函数的返回值是一个对象,它符合 "可迭代协议" 和 "迭代器协议"。
-
既然是迭代器,所以可以用
for of
遍历它,不熟悉迭代器的建议先去学习一下迭代器(Iterator)。
yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
yield执行的逻辑:
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
yield 后面的那个表达式的值,会惰性求值。
代码示例:
function* myGenerator() {
console.log('开始执行')
yield '1'
console.log('1---2')
yield '2'
console.log('2---3')
/* `yield`表达式如果用在另一个表达式之中,必须放在圆括号里面。 */
console.log(1 + (yield '3'))
console.log('3---4')
yield 4 + 5
/* 最后的 return 也会执行,没有 return 也会返回 undefined */
}
var obj = myGenerator()
console.log(obj)
// Object [Generator] {}
console.log(obj.next())
// 开始执行
// { value: '1', done: false }
console.log(obj.next())
// 1---2
// { value: '2', done: false }
console.log(obj.next())
// 2---3
// { value: '3', done: false }
// NaN
console.log(obj.next())
// 3---4
// { value: 9, done: false }
console.log(obj.next())
// { value: undefined, done: true }
其他注意事项:
-
yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。 -
yield
表达式如果用在另一个表达式之中,必须放在圆括号里面。 -
yield
表达式用作函数参数或放在赋值表达式的右边,可以不加括号。 -
yield
表达式本身没有返回值,或者说总是返回undefined
。 -
next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
next传参
function* foo() {
var a = yield 1
console.log('a---', a)
var b = yield 2
console.log('b---', b)
var c = yield 3
console.log('c---', c)
}
var obj = foo()
/* 1. 正常使用 */
console.log(obj.next())
// { value: 1, done: false }
/* 2. 调用 .next 不传值 */
console.log(obj.next())
// a--- undefined
// { value: 2, done: false }
/* 3. 调用 .next 传值*/
console.log(obj.next('tomato'))
// b--- tomato
// { value: 3, done: false }
for of的使用
既然 Generator 函数返回的一个对象符合 "可迭代协议" 和 "迭代器协议";所以我们可以使用 for of对这个对象进行遍历。
function* foo() {
yield 1
yield 2
yield 3
return 'hh'
}
var obj = foo()
var obj2 = foo()
/* 1. 正常的使用 next依次触发 */
console.log(obj.next())
// { value: 1, done: false }
console.log(obj.next())
// { value: 2, done: false }
console.log(obj.next())
// { value: 3, done: false }
console.log(obj.next())
// { value: 'hh', done: true }
/* 2. 使用for of触发已经迭代过的 obj */
for (const iterator of obj) {
console.log('obj', iterator)
}
/* 使用for of触发新的 obj2 */
for (const iterator of obj2) {
console.log('obj2', iterator)
}
// obj2 1
// obj2 2
// obj2 3
由上可得:
**注意:**一旦next
方法的返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象,所以上面代码的return
语句返回的6
,不包括在for...of
循环之中。
yield*
如果在 Generator 函数内部,调用另一个 Generator 函数。
1.直接yield foo() , 返回值是一个 Generator对象:
function* foo() {
yield 1
yield 2
}
function* boo() {
yield 3
yield foo()
yield 4
}
for (const iterator of boo()) {
console.log(iterator)
}
// 3
// Object [Generator] {}
// 4
/* ps: 扩展运算符就底层实现就是 `for of`, 所以 ... 就类似于依次调用了 next 方法; */
2.使用for of嵌套调用
/* 2.嵌套调用 */
function* foo() {
yield 1
yield 2
}
function* boo() {
yield 3
for (let iterator of foo()) {
console.log(iterator)
}
yield 4
}
for (const iterator of boo()) {
console.log(iterator)
}
// 3
// 1
// 2
// 4
3.利用 yield* 简化嵌套调用
/* 3.简化嵌套调用 */
function* foo() {
yield 1
yield 2
}
function* boo() {
yield 3
yield* foo()
yield 4
}
for (const iterator of boo()) {
console.log(iterator)
}
// 3
// 1
// 2
// 4
4.总结:
对比实例2和实例3:
-
yield*
后面的 Generator 函数(没有return
语句时),不过是for...of
的一种简写形式,完全可以用后者替代前者。 -
反之,在有
return
语句时,则需要用var value = yield* iterator
的形式获取return
语句的值。
5.其他
很久之前学习数组扁平化,就有借助 Generator 来解决数组扁平话,我们再回头看看这里的写法,其实也是借助 yield*
效果类似 for of
去递归返回每一项。
// 1.定义一个 Generator 函数
function* flatten(array) {
// 2. for循环遍历数组
for (const item of array) {
// 3.如果是数组
if (Array.isArray(item)) {
// 4. 用for of形式处理 flatten(item)
yield* flatten(item)
} else {
// 5.如果不是数组 直接 yield
yield item
}
}
}
var arr = [1, 2, [3, 4, [5, 6]]]
const flattened = [...flatten(arr)]
// [1, 2, 3, 4, 5, 6]
next()、throw()、return()
next()
function* foo() {
var a = yield 1
// yield 1 相当于 '传入参数'
console.log(a)
yield 2
yield 3
return 4
}
var obj = foo()
obj.next()
obj.next('传入参数')
// 传入参数
throw()
function* foo() {
try {
yield 1
// yield 1 相当于 throw xxx
} catch (error) {
console.log('手动捕获错误', error)
}
yield 2
yield 3
return 4
}
var obj = foo()
obj.next()
obj.throw('出错啦!!')
// 手动捕获错误 出错啦!!
return()
function* foo() {
yield 1
// yield 1相当于 return xxx,后续代码就不执行了
yield 2
yield 3
return 4
}
var obj = foo()
obj.next()
console.log(obj.return('直接返回'))
// { value: '直接返回', done: true }
console.log(obj.next())
// value: undefined, done: true }
简单总结一下:
-
obj.next(xxx) 相当于
yield语句
转换成 xxx -
obj.throw(xxx) 相当于
yield语句
转换成throw xxx
-
obj.return() 相当于
yield语句
转换成return xxx
Generator 函数的this
问题1: 既然 Generator 是一个函数,能使用new关键词调用吗?
function* foo() {
yield 1
yield 2
return 3
}
var f = new foo()
// TypeError: foo is not a constructor
答案:不可以使用 new调用
问题2: Generator函数的返回对象原型上什么
function* foo() {
yield 1
yield 2
return 3
}
foo.prototype.say = function () {
console.log('原型上添加方法')
}
var f = foo()
f.say()
// 原型上添加方法
console.log(f instanceof foo)
// true
console.log(f.__proto__ === foo.prototype)
// true
答案:虽然没有使用 new关键词,但是返回对象的隐式原型指向的就是函数的显示原型。
问题3:原型看完了,那么this指向呢?
function* foo() {
console.log('foo的this', this)
this.a = 'tomato'
yield 1
yield this.a
yield 2
}
var f = foo()
console.log(f.next())
// foo的this Window
// value: 1, done: false}
console.log(f.next())
// {value: 'tomato', done: false}
console.log(f.next())
// {value: 2, done: false}
console.log(f.a)
// undefined
答案:
返回的对象的无法获取到 foo中this上的属性;
通过遍历器去依次 next的时候,this指向全局;
其实和 new 还是有很大差距的
如果非要实现和 new 类似的效果的话 可以这样写:
function* foo() { console.log('foo的this', this) this.a = 'tomato' yield 1 yield 2 } function a() { return foo.call(foo.prototype) } var f = new a() f.next() // foo的this Object [Generator] {} f.next() f.next() console.log(f.a) // tomato
需要注意一下,需要
f.next()
才会开始执行 Generator 函数中的代码。
end
- 其实在学习过遍历器的基础上,再看 Generator,就相对来说没有那么难理解了。
- 本文主要是学习了 Generator主要用法,
- 后续再详细看看 ,异步编程的解决方案