1.Generator
详细资料参见链接。
Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,即Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态(通过next方法)。
yield:
Generator函数体内部使用yield表达式,定义不同的内部状态。yield表达式是暂停执行的标记,而next方法可以恢复执行。(每次调用next方法,遇到 yield 表达式就会暂停,准确地讲,是紧跟在 yield 关键字后面的表达式执行完毕再暂停)
每次调用 next 方法,返回的都是一个一个包含 value
和done
属性的对象;而调用next方法的时候,可以传入参数,作为上一步整个 yield 表达式的执行结果(若不传入,就是 undefined)。
Generator 函数暂停执行(yield
)时,就对应内部状态改变,会将紧跟 yield 的表达式执行结果(通过返回对象的 value
属性)传出去;通过调用 next
,恢复执行,并可将外部数据传到 Generator 内。
{value: result of the express after 'yield', done:true/false}
g.next(arg)
const someTimeConsumingOper=function () {
const startTime=Date.now();
while(Date.now()-startTime<2000){ }
console.log('the time-consuming operation complete.');
}
function* foo() {
console.log("generator started!");
console.log("result of the 'yield' express:"+(yield someTimeConsumingOper()));
console.log("ending.");
}
const f=foo();
f.next();// 控制台依次输出"generator started!"、'the time-consuming operation complete.'
f.next();// 控制台依次输出"result of the 'yield' express:undefined"、"ending."
(1)如何使用yield*
语句遍历完全二叉树
// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// 下面是中序(inorder)遍历函数。
// 由于返回的是一个遍历器,所以要用generator函数。
// 函数体内采用递归算法,所以左树和右树要用yield*遍历
function* inorder(t) {
if (t) {
yield* inorder(t.left);
yield t.label;
yield* inorder(t.right);
}
}
// 下面生成二叉树
function make(array) {
// 判断是否为叶节点
if (array.length == 1) return new Tree(null, array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];
for (let node of inorder(tree)) {
result.push(node);
}
console.log(result); // ['a', 'b', 'c', 'd', 'e', 'f', 'g']
(2)Generator中的this:Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。
2-1. 但是,如果把Generator 函数当作普通的构造函数,并不会生效,因为Generator 函数返回的总是遍历器对象,但函数内部的this
指向的却是window对象
。不可直接对Generator 函数使用new
。
const g=function* () {
console.log(this);//全局window对象
this.a=11;
}
let obj=g();
obj.next();
console.log(obj.a);// undefined
2.2 如何让 Generator 函数既可以用next方法,又可以获得正常的this?
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
const obj = {};
//使用call方法绑定 Generator 函数内部的this。
const 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
console.log(f instanceof F);// true
console.log(obj instanceof F);// false
2.3 上面代码中,执行的是遍历器对象 f,但是生成的对象实例是 obj ,如何将这两个对象统一呢?
可将 obj 换成 F.prototype,这样在 Generator 函数内部通过this
设置的属性值就成了原型对象上的属性值,遍历器对象 f
也就可以通过原型链获取到这些属性值。
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
const 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}
console.log(f.a);// 1
console.log(f.b); // 2
console.log(f.c); // 3
console.log(f instanceof F); // true
2.4 在Generator 函数外部包裹一个函数,外层函数就可作为构造函数使用了。
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
console.log(this); // 注意,F函数内部 this 指向构造函数本身
this.d = 4;// 通过F构造的实例 f 并不包含属性 d
return gen.call(gen.prototype);
}
const 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
f.d; // undefined
注意:上段代码中,F函数内部 this 指向构造函数本身,并不指向构造函数创建的实例 f .背后原因我推测是:改造后的构造函数F
并不是常规的构造函数(构造函数不需要显式 return 实例对象),形式上反而更像是工厂模式的写法。
2.Promise
详细资料参见链接。
这里只说下 resolve 函数的实参是另一个 Promise 实例的情形。
let p1=new Promise((resolve,reject)=>{
setTimeout(()=>reject(new Error('fail')),3000)
})
let p2=new Promise((resolve,reject)=>{
setTimeout(()=>resolve(p1),1000)
})
p2.then(result=>console.log(result)) // 仅设置了对 resolved 情况的处理
p2.catch(err=>console.log('p2 catches:'+err))
p1.catch(err=>console.log('p1 catches:'+err))

上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then、catch 语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。
结果如图2-1所示,注意由于 p2.then未设置状态为 rejected 的处理,导致有异常未被捕获。 p2.then 和 p2.catch 这样调用 等于是 p2 添加两条回调的链条: 一条是有catch的 一条是没有catch的。如果像下面这样链式写法, 那么回调的链就只有一条,就没有未捕获的异常了。
p2.then(result=>console.log(result))
.catch(err=>console.log('p2 catches: '+err))
还有一点,就是如果 p2 是 reject ,如下所示,那么结果如图2-2所示,1s 后,p2 的状态为 rejected,且 reject 传出的参数就是 p1 对象本身,而不是其状态,所以打印出第一行。再过2s后,p1的状态为 rejected,再打印出第二行。
let p1=new Promise((resolve,reject)=>{
setTimeout(()=>reject(new Error('fail')),3000)
})
let p2=new Promise((resolve,reject)=>{
setTimeout(()=>reject(p1),1000)
})
p2.then(result=>console.log(result))
.catch(err=>console.log('p2 catches: '+err))
p1.catch(err=>console.log('p1 catches: '+err))

网友评论