Generator函数

弘烨烁
2023-12-01

前言

Generator 函数是 ES6 提供的一种异步编程解决方案

一、Generator是什么?

语法上,可以把理解成,Generator 函数是一个状态机,封装了多个内部状态。
形式上,Generator 函数是一个普通函数。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。

Generator函数特征:

  • function 关键字和函数之间有一个星号(*),且内部使用·yield·表达式,定义不同的内部状态
  • 调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象

二、Gernerator函数的使用方式

2.1示例:

function* fn() {
	yield '111';
	yield '222';
	return '333';
}
let fn1 =fn();// 调用Generator函数
console.log(fn1); //fn {[[GeneratorStatus]]: "suspended"}
console.log(fn1.next());// {value:'111', done:false}
console.log(fn1.next());// {value:'222', done:false}
console.log(fn1.next());// {value:'333', done:true}
console.log(fn1.next());// {value:undefined, done:true}

调用Generator函数后,函数并不执行,返回的也不是函数执行后的结果,而是一个指向内部状态的指针对象。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。即:每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行

Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

所以可以看出,Generator 函数的特点就是:

  1. 分段执行,可以暂停
  2. 可以控制阶段和每个阶段的返回值
  3. 可以知道是否执行到结尾

迭代器对象的next方法的运行逻辑如下。
(1)遇到yield语句,就暂停执行后面的操作,并将紧跟yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
(3)如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

yield语句与return语句既有相似之处,也有区别。

  • 相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
  • 区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;``Generator函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。

注意:yield语句只能用于function的作用域,如果function的内部还定义了其他的普通函数,则函数内部不允许使用yield语句。
注意:yield语句如果参与运算,必须用括号括起来

2.2next()方法参数

表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

function* fn(x) {
	let y  = 2 * (yield (x+1));
	let z = yield(y + 1);
	return (x+y+z);
}
let fn1 = fn(1);
console.log(fn1.next()); // {value: 2, done:false}
//第二次运行next方法的时候不带参数,导致y的值等于2 * undefined(即NaN),除以3以后还是NaN
// console.log(fn1.next()); // {value: NaN, done:false}
console.log(fn1.next(1)); // {value: 3, done:false}
console.log(fn1.next(1)); // {value: 4, done:true}
console.log(fn1.next(1)); // {value: undefined, done:true}

2.3for…of循环

for…of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。for…of循环的基本语法是:

for(let i of fn()){
	...
}

示例:

function* fn(x) {
	yield '111';
	yield '222';
	yield '333';
	return '444';
}
for(let i of fn(1)) {
	console.log(i); // 111 222 333
}

注意⚠️,一旦next方法的返回对象的done属性为truefor…of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的444,不包括在for…of循环之中。

三、Genarator函数使用示例

3.1斐波那契数列

function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) { 
  // 这里请思考:为什么这个循环不设定结束条件?
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  if (n > 1000) {
    break;
  }
  console.log(n);
}

3.2遍历完全二叉树

function* preOrder(root){ // 前序遍历
    if(root){
        yield  root.mid;
        yield* preOrder(root.left);
        yield* preOrder(root.right);
    }
}
function* inOrder(root){  // 中序遍历
    if(root){
        yield* inOrder(root.left);
        yield  root.mid;
        yield* inOrder(root.right);
    }
}
function* postOrder(root){ // 后序遍历
    if(root){
        yield* postOrder(root.left);
        yield* postOrder(root.right);
        yield  root.mid;
    }
}
function Node(left, mid, right){  // 二叉树构造函数
    this.left = left;
    this.mid = mid;
    this.right = 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 bTree = binaryTree([[['d'], 'b', ['e']], 'a', [['f'], 'c', ['g']]]);
 
// 遍历结果
var preResult = [];
for(let node of preOrder(bTree)){  // 前序遍历结果
    preResult.push(node);
}
console.log(preResult);            // (7) ["a", "b", "d", "e", "c", "f", "g"]
 
var inResult = [];
for(let node of inOrder(bTree)){   // 中序遍历结果
    inResult.push(node);
}
console.log(inResult);              // (7) ["d", "b", "e", "a", "f", "c", "g"]
 
var postResult = [];
for(let node of postOrder(bTree)){  // 后序遍历结果
    postResult.push(node);
}
console.log(postResult);            // (7) ["d", "e", "b", "f", "g", "c", "a"]

四、Generator的原理

原理实现请参考这里

 类似资料: