generator概念的理解
generator它的出现其实就是为了解决异步方案、可以将generator理解成是一个状态机、
同时他也是迭代器iterator的实现的语法糖。
generator的定义和使用
generator其实就是在函数前面加个* 然后函数里面使用了yield字段。
举一个简单例子
定义
function* test(){yield 1;yield 2;yield 3;}
使用
let t = test();t.next();//{value:1,done:false}t.next();//{value:2,done:false}t.next();//{value:3,done:false}t.next();//{value:undefined,done:true}
总结一下generator 包含两个部分一个是在函数外面使用了* 然后函数里面使用了yield字段 这样就构成了一个简单generator
generator中的* 星号
上面的例子 如何generator函数中也可以不直接使用yield字段、也是可以的。
也是正常调用的、和普通函数一样。
举个例子
function * test(){console.log('aa');console.log('bb');}var p = test();//这里里面结果并不会打印//需要调用p.next()//打印 aa bb
generator中yield
generator是由*(星)和yield两个部分包含的。
那么yield字段不可以单独在函数内部直接使用。
yield字段不可以单独在函数内部直接使用
举个例子
function test(){yield 1;}
这里会报错 VM501:2 Uncaught SyntaxError: Unexpected number
yield字段作为表达式必须放在圆括号里
function* demo() {console.log('Hello' + yield); // SyntaxErrorconsole.log('Hello' + yield 123); // SyntaxErrorconsole.log('Hello' + (yield)); // OKconsole.log('Hello' + (yield 123)); // OK}
yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
function* demo() {foo(yield 'a', yield 'b'); // OKlet input = yield; // OK}
Generator.prototype.next
generator中的next是用于调用yield返回的值。而yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
next可以传参且该参数会作为上一次yield表达式的返回值
举个例子
function *test(a){var b = yield 1 + a;var c = yield 2 + b;return a + b + c}//调用testvar tt = test(10);//第一次调用 //yield 1+10 => 11 (a = 10)tt.next() //11//第二次调用 //这里首先的执行的是 b = yiled 20;(b = 20); 然后是yield = 2 + 20 => 22tt.next(20) //22//第三次调用 //这里会执行上一次的 c = yield 30; a+b+c = 10 + 20 + 30 = 60tt.next(30) //60
Generator.prototype.return
这里的return可以等价于在内部定义的return 返回是一样的。
在内部使用return
//函数内部使用returnfunction* test(){yield 1;yield 2;return 3;yield 4;}//调用for(let i of test()){console.log(i)}//打印结果是1 2 后面就会打印
在外部使用return
function* test(){yield 1;yield 2;yield 3;yield 4;}//调用var tt = test();for(let i of tt){if(i == 2) {tt.return('stop')}console.log(i);}
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语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
next()&throw()&return()区别
generator.prototype.next、generator.prototype.throw、generator.prototype.return都是Generator原型提供全局方法。他们三者在理解上可以是一个替换作用。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。
throw()是将yield表达式替换成一个throw语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了// 相当于将 let result = yield x + y// 替换成 let result = throw(new Error('出错了'));
return()是将yield表达式替换成一个return语句。
gen.return(2); // Object {value: 2, done: true}// 相当于将 let result = yield x + y// 替换成 let result = return 2;
yield* 表达式
yield可以理解成是for…of的语法糖。
举个例子
function *test(){
yield 1;
yield 2;
}
function *test2(){
yield 3;
yield 4;
}
function *test3(){
yield* test();
yield* test2();
}
//等上面的方法等价于
function *test3(){
for(let i of test()){yield(i)}
for(let i of test2()){yield(i)}
}
//也等价于
function *test3(){
yield 1;
yield 2;
yield 3;
yield 4;
}
作为对象属性的Generator的函数
举个例子
var obj = {
*test() {
yield 1;
yield 2;
return 3;
}
}
//或者
var obj = {
test:function *() {
yield 1;
yield 2;
return 3;
}
}
generator中的this
this默认下只能被当做函数来使用
generator之前在使用时 一直都是把它作为函数来使用。
如果是new方式来调用呢?
举个例子
function *hh (){
yield this.a = 1;
yield this.b = 2;
}
//new 这个hh
var obj = new hh();
//这里会报错 VM2171:1 Uncaught TypeError: hh is not a constructor
//at <anonymous>:1:11
this可以改变指向作为对象使用
那么,有没有办法让 Generator 函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this?
下面是一个变通方法。首先,生成一个空对象,使用call方法绑定 Generator 函数内部的this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。
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内部的this对象绑定obj对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。
上面代码中,执行的是遍历器对象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
