Generator
函数是 ES6
提供的一种异步编程解决方案。
语法上,可以把理解成,Generator 函数是一个状态机,封装了多个内部状态。
形式上,Generator 函数是一个普通函数。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。
Generator函数特征:
function
关键字和函数之间有一个星号(*),且内部使用·yield·表达式,定义不同的内部状态Generator
函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象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
函数的特点就是:
迭代器对象的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语句如果参与运算,必须用括号括起来
表示上一个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}
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
属性为true
,for…of
循环就会中止,且不包含该返回对象,所以上面代码的return
语句返回的444,不包括在for…of循环之中。
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);
}
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"]
原理实现请参考这里