ES6

作者: 悄敲 | 来源:发表于2019-03-12 22:06 被阅读0次
1.Generator

详细资料参见链接。
Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,即Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态(通过next方法)。
yield:Generator函数体内部使用yield表达式,定义不同的内部状态。yield表达式是暂停执行的标记,而next方法可以恢复执行。(每次调用next方法,遇到 yield 表达式就会暂停,准确地讲,是紧跟在 yield 关键字后面的表达式执行完毕再暂停)
每次调用 next 方法,返回的都是一个一个包含 valuedone属性的对象;而调用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))
fig 2-1

上面代码中,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))
fig 2-2.png

相关文章

网友评论

      本文标题:ES6

      本文链接:https://www.haomeiwen.com/subject/fgmopqtx.html