ES6 Generator函数(一)

Generator

概念

  • Generator是ES6提供的一种异步编程解决方案
  • 从语法上来看,Generator 函数是一个状态机,封装了多个内部状态
  • 执行Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数.返回的遍历器对象,可以依次遍历Generator 函数内部的每一个状态
  • 形式上,Generator 函数是一个普通函数,但是有两个特征:一是function关键字与函数名之间有一个*号,二是函数体内使用yield(产出)表达式定义不同的内部状态

调用方法

  • Generator 函数的调用方法和普通函数一样,也是在函数名后面加上一对圆括号.不同的是,Generator 函数调用后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器.下一步必须调用遍历器的next方法使得指针下移,使得指针移向下一个状态.也就是说每次调用next方法,函数指针就从函数头部或上一次停下来的地方开始执行,直到遇到yield表达式或return语句.换言之,Generator函数是分段执行的,yield是暂停执行的标记,而next方法可以恢复执行

    function* helloWorldGenerator() {	//这个Generator函数有三种状态,hello,world,ending
    	yield 'hello';
    	yield 'world';
    	return 'ending';
    }
    var hw = helloWorldGenerator();
    hw.next()
    // { value: 'hello', done: false }
    hw.next()
    // { value: 'world', done: false }
    hw.next()
    // { value: 'ending', done: true }	注意这里done也是true,和前面的不太一致
    hw.next()
    // { value: undefined, done: true }
    
  • *号也在哪个位置都可以.最好采用如下写法:function* foo(){}

yield表达式

  • 由于Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以提供了可以暂停执行的函数,yield表达式就是暂停标志
  • yield表达式后面的表达式只有调用next方法,内部指针指向该语句才会执行,因此等于为JavaScript提供了手动的惰性求值.即yield表达式后面的值不会立即执行,只有next方法移到这一句时才会求值
  • yield表达式与return语句的相似之处:都能返回紧跟在语句后面的表达式的值
  • yield表达式与return语句的区别:遇到yield时,函数暂停执行,下一次从该位置开始继续向后执行.而return语句不具备位置记忆功能,一个函数里面只能执行一个return语句
  • yield表达式只能用在Generator函数里,用在其他地方都会报错.即使是在Generator函数里嵌套的方法内使用也会报错
    //下面这个函数虽然使用了Generator函数,但在forEach中使用yield,因此forEach中的参数是普通函数,因此会报错
    var arr = [1, [[2, 3], 4], [5, 6]];
    var flat = function* (a) {
    	a.forEach(function (item) {
    		if (typeof item !== 'number') {
    			yield* flat(item);
    		} else {
    			yield item;
    		}
    	});
    };
    for (var f of flat(arr)){
    	console.log(f);
    }
    
  • yield表达式如果用在另一个表达式中必须放在圆括号里面
    function* demo() {
    	console.log('Hello' + yield); // SyntaxError
    	console.log('Hello' + yield 123); // SyntaxError
    	console.log('Hello' + (yield)); // OK
    	console.log('Hello' + (yield 123)); // OK
    }
    

-yield表达式用作函数参数或放在赋值表达式的右边可以不加括号

与Iterator接口的关系

  • 前面说过任何一个对象的Symbol.iterator方法等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象.由于Generator函数就是遍历器生成函数,因此可以把Generator函数赋值给对象的Symbol.iterator属性,从而使该对象具有Iterator接口

  • Generator 函数执行后,返回一个遍历器对象。该对象本身也具有 Symbol.iterator 属性,执行后返回自身

    function* gen(){
    // some code
    }
    var g = gen();
    g[Symbol.iterator]() === g
    // true
    

next方法的参数

  • yield表达式本身没有返回值,或者说总是返回undefined.next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

  • 举个栗子

    //第一个例子
    function* f() {
    	for(var i = 0; true; i++) {
    		var reset = yield i;
    		if(reset) { i = -1; }
    	}
    }
    var g = f();
    g.next() // { value: 0, done: false } 第一次执行,yield输出i=0,没有返回值,reset为undefined
    g.next() // { value: 1, done: false } 第二次执行,yield输出i=1,没有返回值,reset为undefined
    g.next(true) // { value: 0, done: false }	第三次执行,带有参数true,true被当作第二次执行的返回值,也就是在第二次next中返回了true,因此进入if,将i置为-1,然后i++.所以这次执行时yield输出0
    ---------------------------------------
    //第二个例子
    function* foo(x) {
    	var y = 2 * (yield (x + 1));
    	var z = yield (y / 3);
    	return (x + y + z);
    }
    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}
    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }
    //分析,关于a的调用不作解释,没有传入参数,y,z都为undefined,运算后为NaN
    //关于b,第一次调用结果为6,第二次调用传入参数为12,也就是将第一次调用中的yield(x+1)返回值设为12,运算后y=12
    //第三次调用传入13,也就是将第二次调用中的yield(y/3)返回值设为13,即z=13,因此x+y+z=5+24+13=42
    

for…of循环

  • 可以自动遍历Generator函数生成的Iterator对象,且不需要调用next方法.除了for…of循环以外,扩展运算符(…),解构赋值和Array.from方法内部调用的都是遍历器接口,都可以将Generator函数返回的Iterator 对象,作为参数

    function *foo() {
    	yield 1;
    	yield 2;
    	yield 3;
    	yield 4;
    	yield 5;
    	return 6;
    }
    for (let v of foo()) {
    	console.log(v);
    }
    // 1 2 3 4 5
    //会依次显示5个yield的值,因为一旦next方法返回的对象中的done属性为true,for...of循环就会终止,且不包含该返回对象,因此return 6 不会显示出来
    //因为对象本身没有Iterator接口,因此可以通过Generator函数为它添加接口
    function* objectEntries() {
    	let propKeys = Object.keys(this);
    	for (let propKey of propKeys) {
    		yield [propKey, this[propKey]];
    	}
    }
    let jane = { first: 'Jane', last: 'Doe' };
    jane[Symbol.iterator] = objectEntries;
    for (let [key, value] of jane) { //也可以写为for(let [key,value] of objectEntries(jane))
    	console.log(`${key}: ${value}`);
    }
    

你可能感兴趣的:(JavaScript,ES6,Generator)