Generator函数的语法

简介

Generator函数是ES6关于异步编程的解决方案。Generator函数能够让函数暂停执行(即交出函数的执行权),简单直白点来理解,Generator函数就是一个状态机,内部封装了多个状态(暂停执行的位置)

  • 定义:
  1. function 关键字后有一个*
  2. 函数体内部有yield表达式,表示暂停的位置,定义不同状态。
function* helloWorld(){
    yield 'hello';
    yield 'world';
    return 'end';
}
var hw = helloWorld();
  • 调用
  1. 通过next()方法调用,返回结果形如{value:xxx, done: bool}。并在下一个yield处暂停。
  2. 当函数执行完毕(碰到return或者所有yield执行完毕),那么返回的结果value为return的值(无return 则返回undefined),done为true。
  3. 函数执行完毕后无论怎么调用next()方法,结果都是{ value: undefined, done: true }
  4. 通过next()可以传入参数进入状态机,即传入值取代yield表达式。
hw.next();
//{value:'hello',done:false}
hw.next();
//{value:'world',done:false}
hw.next();
//{value:'end',done:true}
hw.next()
//{value:'undefined',done:true}
  • yield*表达式
    yield*表达式用于在Generator函数内部调用另外一个Generator函数。

  • throw()方法
    在函数体外部抛出错误,如果内部有catch语句,那么优先内部捕获,否则为外部捕获。全局的throw命令只能够外部catch捕获

  • return()方法
    用于终结遍历Generator函数。Generator函数调用return方法后,那么返回值的done变为true,遍历终止。
    当内部有try...finally代码块时,return会推迟到finally代码块执行完后再执行。

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 }
  • 其他
  1. yield只能在Generator函数内使用
  2. Generator函数执行后会返回一个遍历器对象,通过把Generator函数赋值给对象的Symbol.iterator属性,即可使对象获得Iterator接口,可以被for...of...等遍历

应用

1. 利用Generator遍历Object的属性

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);
  
  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}

2. 利用yield*遍历嵌套数组

let arr = [1,2,[3,4,5,[6,7,8],9],10];

function* iterArr(arr){
	for(let value of arr){
		if(Array.isArray(value)){
			yield* iterArr(value);
		}else{
			yield value;
		}
	}
}
for(let x of iterArr(arr)){
	console.log(x);
}

3. 利用yield*中序遍历完全二叉树

//节点的构造函数

function Node(left, value, right){
	this.left = left;
	this.value = value;
	this.right = right;
}
//完全二叉树遍历函数
function* inorder(node){
	if(node){
		yield* inorder(node.left);
		yield node.value;
		yield* inorder(node.right);
	}
}

//二叉树生成函数
function binaryTree(arr){
	if(arr.length === 1){
		return new Node(null, arr[0], null);
	}
	return new Node(binaryTree(arr[0]),arr[1],binaryTree(arr[2]));
}

let tree = binaryTree([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);

console.log(tree);

let result = [];
for (let node of inorder(tree)){
	result.push(node);
}
console.log(result);

4. Generator函数的状态机实现

Generator 的确是用来实现状态机的最佳数据结构。

首先,在没有外部输入的情况,一个状态机只有内部既定的逻辑,与Generator函数一样:若未经处理,那么也是一个按照既定顺序执行的多阶段的任务队列。

通过yield 可以表示状态机的状态,利用yield来接受外界对Generator的输入,即状态机接受外部的输入参数。
然后通过内部的逻辑,将之切换到不同的下一个yield,即可实现状态机的切换。

提供一个利用Generator函数实现的简单状态机案例:

  • 状态机的状态变化如下图所示:

  • Generator函数模拟
let machine = function* (){
	let states = ['初始化','状态一', '状态二', '状态三', '状态四', '退出'];
	let i = 0;
	let input = yield states[0];
	while(true){
		if(typeof input !== 'number'){
			input = 0;
		}
		switch (i) {
			case 1:
				if(input <= 0) i = 3;
				else if(input > 10) i = 2;
				else if(input > 100) i = 4;
				break;

			case 2:
				if(input > 10) i = 4;
				break;
			case 3:
				if(input > 10) i = 4;
				else if(input < 0) i = 1;
				break;
			case 4:
				if(input > 100) i = 1;
				if(input < -100){
					yield states[5];
					return '状态机关闭';
				}
				break;
			default:
				i = 1;
				break;
		}
		input = yield states[i];
	}
};
machine = machine();
console.log(machine.next());
  • 函数执行结果:

参考文献

  1. ECMAScript 6入门--第16章 Generator函数的语法
原文地址:https://www.cnblogs.com/omg-two/p/6875617.html