深入解析 ES6 的 Generator,从基本语法到高级应用,探索其在异步操作、状态管理和迭代控制中的强大功能和实用技巧,助力你高效编程!
Generator,生成器函数,ES6中最难理解的语法,没有之一。Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
- 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
- 形式上,Generator 函数是一个普通函数,但是有两个特征 :
function关键字与函数名之间有一个星号*;- 函数体内部使用
yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
语法
只要给一个函数关键字后面添加一个星号
*,那么这个函数就被称之为生成器generator函数
调用
调用生成器函数,不会立即执行函数体,而是会返回一个Iterator迭代器对象,调用
next()方法这继续往后执行,碰到yield关键字就暂停。
next只是给yield下指令,有了next就执行一个yield。yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的 “惰性求值” (Lazy Evaluation)的语法功能。
value:表示每次执行时yield后面的内容(每次调用都会从上一次停止的地方继续运行)done:表示当前函数状态,是否执行完
function* generator (){
yield 'a'
yield 'b'
yield 'c'
return 'ending'
}
let g = generator() // 并不是执行,相当于new了一个实例对象,这就是*作用
console.log(g.next()); // { value: 'a', done: false }
console.log(g.next()); // { value: 'b', done: false }
console.log(g.next()); // { value: 'c', done: false }
console.log(g.next()); // { value: 'ending', done: true }
console.log(g.next()); // { value: undefined, done: true }
function* foo () {
var o = 1
yield o++
yield o++
yield o++
}
let gen = foo()
console.log(gen.next()); // { value: 1, done: false }
let gener = foo()
console.log(gener.next()); // { value: 1, done: false }
yield没有返回值:undefined
function* g() {
let a = 1
let b = yield a++
console.log(b);
let c = yield a++
}
let gen = g()
console.log(gen.next());
console.log(gen.next());
// 输出如下:
// { value: 1, done: false } a
// undefined b
// { value: 2, done: false } a
调用的是异步代码(先执行同步代码再执行异步代码):
- 分步调用
next():
由运行结果可知:前四次调用中,返回{value: Promise, done: false}是同步的先执行,而setTimeout中的代码(打印)是异步,等同步代码执行完后再执行。而第五次调用时,返回{value: undefined, done: false}和调用B()打印b都是同步代码,故会按顺序执行。
function A(num) {
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log(`异步的a${num}`)
resolve()
},0)
})
}
function B() {
console.log('同步的b')
}
function* gen() {
yield A(1)
yield A(2)
yield A(3)
yield A(4)
yield B()
}
let g = gen()
g.next() // 第一次调用
// {value: Promise, done: false} 异步的a1
g.next() // 第二次调用
// {value: Promise, done: false} 异步的a2
g.next() // 第三次调用
// {value: Promise, done: false} 异步的a3
g.next() // 第四次调用
// {value: Promise, done: false} 异步的a4
g.next() // 第五次调用
// 同步的b {value: undefined, done: false}
g.next() // 第六次调用
// {value: undefined, done: true}
- 一次性调用
next():
function A(num) {
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log(`异步的a${num}`)
resolve()
},0)
})
}
function B() {
console.log('同步的b')
}
function* gen() {
yield A(1)
yield A(2)
yield A(3)
yield A(4)
yield B()
}
let g = gen()
g.next()
g.next()
g.next()
g.next()
g.next()
执行结果:
分析:前四个yield表达式后面都是setTimeout异步代码,会按顺序加入任务队列;执行到第5个yield ,后面是同步代码,会打印出同步的b ,之后返回 {value: undefined, done: false} ,同步任务完成后再从任务队列中依次执行刚刚的异步任务。 注意:如果一次性调用的是6次g.next() ,则打印完同步的b后返回的是 {value: undefined, done: true} 。
遍历
因为执行 Generator 函数会返回一个遍历器对象,所以可以对其进行遍历操作:返回所有的
value,最后return的值不会被返回,因为for of遇到done为true就停止遍历了。for-of 循环只会遍历迭代器 done 的值为 false 的结果
function* generator (){
yield 'a'
yield 'b'
yield 'c'
return 'ending'
}
for(let item of generator()){
console.log(item)
}
// 输出:
// a
// b
// c
与 Iterator 接口的关系
任意一个对象的
Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。
function* gen(){
// some code}
var g = gen();
g[Symbol.iterator]() === g // true
next()方法传参
next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值,也就是替换掉上一个整个yield表达式。
function* g() {
let a = 1
let b = yield a++
console.log(b);
let c = yield a++
console.log(a);
console.log(c);
}
let gen = g()
console.log(gen.next());
console.log(gen.next(3)); // next的参数,用于指定被我触发的yield的执行结果
console.log(gen.next(2));
// 输出如下:
// { value: 1, done: false }
// 3
// { value: 2, done: false }
// 3
// 2
// { value: undefined, done: true }
Generator.prototype.throw()
Generator 函数返回的遍历器对象,都有一个
throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
var g = function* () {
try {
yield;
} catch (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语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
throw方法可以接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例。
不要混淆遍历器对象的throw方法和全局的throw命令。throw命令与g.throw方法是无关的,两者互不影响。 上面代码的错误,是用遍历器对象的throw方法抛出的,而不是用throw命令抛出的。全局的throw命令只能被函数体外的catch语句捕获。
function* gen() {
try {
yield 1;
} catch (e) {
console.log('内部捕获');
}
}
var g = gen();
g.throw(1);// Uncaught 1
上面代码中,g.throw(1) 执行时, next方法一次都没有执行过。这时,抛出的错误不会被内部捕获,而是直接在外部抛出,导致程序出错。
这种行为其实很好理解,因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw方法抛错只可能抛出在函数外部。throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。
一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了 。如果此后还调用next方法,将 返回一个value属性等于undefined、done属性等于true的对象 ,即 JavaScript 引擎认为这个 Generator 已经运行结束了。
如下面的例子:
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke!');
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('第一次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第二次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第三次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
console.log('caller done');
}
log(g());
// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 { value: 1, done: false }
// 第三次运行next方法 { value: undefined, done: true }
// caller done
上面代码一共三次运行next方法,第二次运行的时候会抛出错误,然后第三次运行的时候,Generator 函数就已经结束了,不再执行下去了。
Generator.prototype.return()
Generator 函数返回的遍历器对象,还有一个
return方法,可以返回给定的值,并且终结遍历 Generator 函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
// 遍历器对象g调用return方法后,返回值的value属性就是return方法的参数foo。
// 并且,Generator 函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,
// done属性总是返回true。如果return方法调用时,不提供参数,则返回值的value属性为undefined。
如果 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会导致立刻进入finally代码块, finally执行完以后,再次调用next() 方法会刚刚return的参数,如果没传参就返回undefined ,至此整个函数才会结束。
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
// 上面代码中,调用return()方法后,就开始执行finally代码块,不执行try里面剩下的代码了,
// 然后等到finally代码块执行完,再返回return()方法指定的返回值。
next()、throw()、return() 的共同点
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是 让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式 。next()是将yield表达式替换成一个值。
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next();
// Object {value: 3, done: false}
gen.next(1);
// Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
throw() 是将yield表达式替换成一个throw语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
return() 是将yield表达式替换成一个return语句。
yield* 表达式
如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。但如果有多个 Generator 函数嵌套,写起来就非常麻烦。ES6 提供了
yield*表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。
function g1(){
yield 2
yield 3
yield 4
}
function g2(){
yield 1
yield* g1()
yield 5
}
// -----------------------------------------
// 等同于:
function* g2(){
yield 1
yield 2
yield 3
yield 4
yield 5
}
// -----------------------------------------
// 也等同于:
function* g2(){
yield 1
for(let i of g1()){
yield i
}
yield 5
}
作为对象属性的 Generator 函数
如果一个对象的属性是 Generator 函数,可以简写。
let obj = {
myGeneratorMethod: function* () {
// ··· }
};
let obj = {
* myGeneratorMethod() {
···
}
};
Generator 函数的this
Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
- 如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。
function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
// 上面代码中,Generator 函数g在this对象上面添加了一个属性a,但是obj对象拿不到这个属性。
- Generator 函数也不能跟
new命令一起用,会报错。
function* F() {
yield this.x = 2;
yield this.y = 3;
}
new F()
// TypeError: F is not a constructor
- 让 Generator 函数返回一个正常的对象实例,既可以用
next方法,又可以获得正常的this:首先,生成一个空对象,使用call方法绑定 Generator 函数内部的this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。
下面代码中,首先是F内部的this对象绑定obj对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var obj = {};
var f = F.call(obj);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
obj.a // 1
obj.b // 2
obj.c // 3
- 执行的是遍历器对象f,但是生成的对象实例是
obj,将这两个对象统一呢:一个办法就是将obj换成F.prototype。
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
再将F改成构造函数,就可以对它执行new命令了。
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
Generator 与状态机
Generator 是实现状态机的最佳结构。
比如,下面的clock函数就是一个状态机:
let ticking = true;
let clock = function() {
if (ticking)
console.log('Tick!');
else
console.log('Tock!');
ticking = !ticking;
}
clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态。这个函数如果用 Generator 实现:
let clock = function* () {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
上面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改) 、更符合函数式编程的思想,在写法上也更优雅
Generator与协程(coroutine)
- 由于 JavaScript 是单线程语言,只能保持一个调用栈。引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。
- Generator 函数是 ES6 对协程的实现,但属于不完全实现 :
- Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。
- 如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。
- 如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用yield表达式交换控制权。
Generator与上下文
- JavaScript 代码运行时,会产生一个全局的上下文环境(context,又称运行环境),包含了当前所有的变量和对象。
- 然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,变成当前(active)的上下文,由此形成一个上下文环境的堆栈(context stack)。
- 这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。
Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。
function* gen() {
yield 1;
return 2;
}
let g = gen();
console.log(
g.next().value,
g.next().value,
);
上面代码中,第一次执行g.next()时,Generator 函数gen的上下文会加入堆栈,即开始运行gen内部的代码。等遇到yield 1时,gen上下文退出堆栈,内部状态冻结。第二次执行g.next()时,gen上下文重新加入堆栈,变成当前的上下文,重新恢复执行。
自动执行Generator函数
我们可以借助 Promise + Generator 可以手动控制异步代码和同步代码的执行顺序了,可是这么写目前依然存在一个很严峻的问题——类似于“回掉地狱”,代码层次不清晰等。
g.next().value.then(() => {
g.next().value.then(() => {
g.next()
// ......
})
})
Thunk函数
Thunk 函数是一种用于延迟执行的函数包装器,通常用于处理异步操作或生成器函数的执行。Thunk 函数的目标是将一个函数的执行推迟到稍后的时间,以便在需要时再执行。在 JavaScript 中,Thunk 函数通常是一个带有回调函数的函数包装器。
如下例子:A函数执行完毕,递归进去执行 B函数,B函数执行完毕,递归进去执行 C函数。 这样一来,我们就通过打造一个 Thunk 函数和一个 Thunk 函数执行器(run)来实现了让 Generator 函数自动执行下一次层的 next()。
// 异步函数 A、B 和 C,现在它们接受一个回调函数作为参数,并在异步操作完成后调用回调函数。
function A(callback){
setTimeout(() => {
console.log('异步a');
callback();
}, 1000);
}
function B(callback){
setTimeout(() => {
console.log('异步b');
callback();
}, 500);
}
function C(){
setTimeout(() => {
console.log('异步c');
callback()
}, 100);
}
// Thunk函数:
function SimpleThunk(fn){
return function(callback){
fn(function (result){
callback(result)
})
}
}
// 使用Thunk函数自动执行生成器函数
function run(generator){
const g = generator()
function iterator(g){
// iterate(g); 执行了第一个 g.next(),这就开启了 yeild simpleThunk(A); 的执行,
// 得到的仍然是一个函数体,所以 const { value, done } = g.next(); 中value还是一个函数
const {value, done} = g.next()
if(done){
return
}
if(typeof value === 'function'){
// 调用 value() 带来了 函数A 的执行,A在1秒钟之后才会调用自己内部的 callback,
// 这就导致了 A函数没有执行完毕的话,iterate(g) 这一处的递归就无法开始
value(()=>iterator(g))
} else {
throw new Error('Generator函数应该返回一个Thunk函数')
}
}
iterator(g)
}
// 生成器函数
function* generator(){
yield SimpleThunk(A)
yield SimpleThunk(B)
yield SimpleThunk(C)
}
// 使用run函数自动执行生成器函数
run(generator)
// 输出:
// 异步a
// 异步b
// 异步c
co模块
除了 Thunk 函数的方式,还有一个方法可以实现 Generator 的自动执行,就是 co 模块。
co 模块是大佬 TJ Holowaychuk封装的一个库,一个用于控制生成器函数执行的库,它允许你以同步的方式编写异步代码,使得生成器函数内部的异步操作看起来像同步代码一样。
co 模块的实现基于 Promise 和生成器函数的特性,它自动迭代生成器并处理 Promise 对象的返回值。
用法
原理
和Thunk函数一样同样是使用递归,不过 co 借助了 Promise中的
then方法,所以需要使用者注意 yeild 后面的内容一定要返回一个 Promise 对象,当上一个yeild执行完毕且状态变更为fulfilled,then才能执行,也就才能走进下一层的递归。
async/await 的实现就是由 Generator + Promise + co的手段 来封装的
Thunk和co的区别
co 和 Thunk 函数都有各自的用途和优势,选择使用哪个取决于你的具体需求和代码结构 :
- co 更适合复杂的异步控制流程
- 而 Thunk 函数更适合将异步操作封装成可延迟执行的函数。
当然,实际开发过程中,肯定是直接 async/await呀!
1. 用途
- co 函数通常用于协调和管理异步操作的流程,使得异步操作看起来像同步代码一样执行。它通常与生成器函数结合使用。
- Thunk 函数主要用于封装异步操作,将其包装成一个函数,以便在需要时延迟执行。Thunk 函数通常用于构建异步流程控制工具。
2. 执行方式
- co 函数是一个库或工具,它使用 Promise 和生成器函数的协作来实现异步控制。co 内部会自动执行生成器函数,并管理异步操作的执行流程。
- Thunk 函数是一个函数包装器,它接受回调函数作为参数,并通常需要手动调用来执行。Thunk 函数的执行需要显式地调用,而不像 co 那样自动进行异步流程控制。
3. 使用场景
- co 函数适用于较复杂的异步流程控制,例如需要按顺序执行多个异步操作、处理错误等情况。它在管理多个异步任务时非常有用。
- Thunk 函数通常用于构建异步库或处理单一异步操作的情况,它更侧重于将异步操作封装成可延迟执行的函数,以便在需要时执行。
Generator应用
异步操作的同步化表达
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();
上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined。
function* numbers() {
let file = new FileReader("numbers.txt");
try {
while(!file.eof) {
yield parseInt(file.readLine(), 10);
}
} finally {
file.close();
}
}
let n = numbers()
// 使用yield表达式可以手动逐行读取文件。
n.next()
n.next()
n.next()
...
n.next()
控制流管理
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value); // 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
作为数据结构
因为 Generator 函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。
function* doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
// task是一个函数,可以像回调函数那样使用它
}
等价于:
function doStuff() {
return [
fs.readFile.bind(null, 'hello.txt'),
fs.readFile.bind(null, 'world.txt'),
fs.readFile.bind(null, 'and-such.txt')
];
}
状态模式的语法糖
如果我们要自己去写状态模式的代码的话,会显得比较繁琐,相当于要自己去切换上下文,而有了Generator我们就可以直接利用Generator的能力为我们进行状态切换了。
频繁切换状态 :
function* func() {
while (true) {
yield console.log("红灯亮起");
yield console.log("绿灯亮起");
yield console.log("黄灯闪烁,红灯即将亮起");
}
}
const light = func();
function start(immediate) {
setTimeout(() => {
light.next();
start();
}, 1000);
immediate && light.next();
}
作为迭代器的语法糖
请添加某些代码使得以下代码正常运行:
const obj = { a: 1, b: 2 }
// 在此处添加你的代码
const [a, b] = obj;
这个题是要为obj对象补迭代器 :
obj[Symbol.iterator] = function () {
const keys = Object.keys(this);
let idx = 0;
const _this = this;
return {
next() {
const i = idx++;
return {
value: _this[keys[i]],
done: i >= keys.length,
};
},
};
}
// 定义一个Generator,读取目标对象的
function *objIterator(obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
yield obj[key];
}
}
}
const obj = { a: 1, b: 2 };
obj[Symbol.iterator] = function() {
return objIterator(this)
}
// 在此处添加你的代码
const [a, b] = obj;