首发地址https://mp.weixin.qq.com/s/A-...

 用最精简的方式,抓住最核心的知识点,帮助你快读ES6。

    这几个知识点在【框架设计】以及【任务流控制】中起着重要作用,属于必备知识。本文部分内容需要你具备相关基本知识,帮你划出重点结合笔者的一些思考,有查缺补漏之功效

关键词:Proxy、Promise、Iterator、Generator、Async

Proxy

  1. 原理:重载了点运算符
  2. 存在This指向问题 
    Proxy 针对目标对象的访问,不是透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理
  3. Proxy实例的方法

    1)get(target, property, proxy)

    • 作为原型对象Object.create(new Proxy({}, handler)), handler中的get可以被继承
    • 实现属性链式操作(内部DSL:**级联属性**):pipe(3).double.pow.get;
    • dom.div | dom.h1 |... :用作生成各种DOM节点的通用函数,避免穷举(内部DSL:**动态代理**

      备注:内部DSL相关介绍,可参考:内部DSL,你不可不知

2)set(target, property, value, proxy)

  • vue实现的数据双向绑定:数据初始化时拦截getter做依赖收集,当编译模板时创建watcher对象,触发数据的读操作,将watcher对象存入deps,当数据变更,触发setter执行deps中的回调进行更新操作;
  • 内部会调用Proxy的defineProperty()

Promise

异步编程(回调函数 -> 事件监听 -> 发布订阅 -> promise,Generator,async 对 流程、时间 控制的探索)

特点

  1. 链式调用的方式:适合流程、时间敏感的场景;
  2. 状态不受外界影响;
  3. 回调函数注册时机不必须在异步执行完前。

不足

  1. 进度未知
  2. 无法中途取消

原型方法

  1. Promise.prototype.then()
  2. Promise.prototype.**catch**().then(undefined, rejection)的语法糖
  3. Promise.prototype.**finally**().then(() => {}, () => {})的语法糖

Promise私有方法

  • resolve():等价于new Promise(resolve => resolve(参数)),其中参数的形式如下:

    • promise:原样返回此实例
    • thenable
    • 原始值
    • 无参数
  • reject()

    • 其参数会原封不动的作为reject的理由,和resolve不同;
  • all()

    • 用于将多个 Promise 实例,包装成一个新的 Promise 实例;
    • 如果数组中的值不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise 实例
    • 参数可以不是数组,但必须具有Iterator接口
    • 所有参数中的Promise实例为fulfilled,才会为fulfilled
  • race()

    • 基本同all;差异在于参数中的Promise实例最先改变的状态决定了包装实例的状态
  • allSettled() :该方法由 ES2020 引入

    • 等到所有状态变更后,无论是fulfilled还是rejected,才会执行外层,且返回的Promise实例状态始终是fulfilled。
  • any()

    • 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态

【注意】

  • 如果没有对应状态的回调函数,会冒泡传递,直到被相应的回调函数处理
  • 状态为reject且没有相应处理函数,会最终被catch捕获
  • 返回的状态值,仅能被消费一次;之后的传递值由对应回调函数的return值决定

错误处理

1. Promise执行过程中抛出异常的处理

throw new Error('test');
//等价于
try {
    throw new Error('test');
} catch(e) {
    reject(e);
}
//或者 
reject(new Error('test'));

2. 内部的promise的异常捕获

new Promise((r,j)=>{r()}).then(() => {     
   try{
        new Promise(() => { throw 'error' }) 
        return 'try';

    }catch(e) {
        console.log('inner', e);
        return 'catch'

    }
}).then(info => console.log(info)).catch(err=> {console.log('outer:', err)})
//执行结果:try

 由上可知,promise会吃掉错误(即不会传递到外层代码不能被外层代码捕获不会影响外层代码的执行) 
Q:想要捕获内部错误怎么办?

  • 对抛出异常的promise加上catch
  • return掉promise,就可以统一使用最外层的catch捕获处理

promise的实现原理:

    1. .then返回新的上下文

     2. status完成,记录value,等待回调到位执行;回调就位,等待status完成执行

.then的返回值为什么不使用同一个上下文?

  1.状态不固定status在变动:pedding->fulfilled | rejected->pedding...

  2..then返回promise时resolve(消费)多次问题

  3.中间状态不能被记录

               

Iterator

Iterator:遍历器,本质为指针对象

  • Iterator
  • 集合:数组、对象、Map、Set。可以组合使用的前提是需要统一的接口机制,来处理所有不同数据结构的访问。
  • 遍历器可以不依附于别的数据结构;
  • 遍历器对象的特征是具有next方法,返回{value:当前成员的值, done:是否结束标志}对象:
{
    next:()=> {
        //...
        return {
            value: ,//任意类型值
            done: ,//Boolean
        }
    }
}
  • Iterator接口:遍历器生成函数

    • 部署到数据结构的Symbol.iterator属性
{
    [Symbol.iterator]() {
        return { next: () => {}}
    }

}
  • 因此具有Symbol.iterator属性的数据结构,可以认为是可遍历的。
  • 提供了统一的访问机制: for...of,会调用Symbol.iterator方法,返回遍历器对象。
  • 原生具备Iterator接口的数据结构:ArrayMapSetStringTypedarrayArguments对象Nodelist对象

注意

  • 对象(Object)之所以没有默认部署Iterator接口,是因为属性遍历次序不确定;对象部署遍历器接口并不是很必要,因为其等效于Map结构;
  • 类数组的对象(存在数值键名和length属性,获取Iterator接口的方式:

    • 可以引用数组的Iterator接口
    • Array.from()
    • Array.prototype.slice.call(arguments, 0)
    • ...

应用场景(默认调用symbol.iterator)

  • 解构赋值
  • 扩展运算符(只要某个数据结构部署了iterator接口,就可以将其转为数组
  • yield*后是一个可遍历的结构
  • for...of
  • 相关Tips
  • Symbol.iterator方法的最简单实践是使用Generator函数
let myIterable = {
    //简洁写法:* [Symbol.iterator]() {
    [Symbol.iterator]: function* () {
        yield 1;
        yield 2;
        yield 3;
    }
}
[...myIterable] // [1, 2, 3]
  • 遍历器对象的return():遍历时的提前退出「异常」或「break语句」都会调用return()
  • forEach(无法中断);for...in 循环读取键名(包括原型链上的key);for...of 循环读取键值(只返回具有数字索引的属性;可以中断)

Generator

状态机 | 遍历器生成函数:返回的遍历器对象依次遍历内部的每个状态

使用

// 形式1
function* () {
    yield ;

}
// 形式2:作为对象属性的写法
let obj = {
  * myGeneratorMethod() {
    ···
  }
};

特性

  • 惰性求值的语法功能
  • 分段执行,yield表达式是暂停执行的标记,而next方法可以恢复执行,每次next返回对象中的value为yield后的表达式值
  • yield表达式的返回值:其本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表示的返回值,因此第一次传参无效。
  • yield 表达式如果用在另一个表达式中,需放在括号中(表示其为表达式,和({})作用相似)
function* demo() {
    console.log('Hello' + yield 123);  // SyntaxError
    console.log('Hello' + (yield));  // OK
    console.log('Hello' + (yield 123));  // OK
}

注意return的使用

for...of循环中,一旦done为true,就会中止,且不会包含该返回对象

yield* 

yield*表达式:表明后面的表达式是一个带有[Symbol.iterator]的数据结构。如:yield* [1,2,3,4]。

  • 案例1:等同于在 Generator 函数内部,部署一个for...of循环:
let A = (function* () {
  yield 'Hello!';
  yield 'Bye!';
}());


let B = (function* () {
  yield 'Greetings!';
  //yield* A; 等价于
  for (var value of A) {
    yield value;
  }
  yield 'Ok, bye.';
}());
  • 案例2:yield*的返回值
function* () {
  Let res = yield* gen();  // res为generator中return的值
}
  • 应用:取出嵌套数组的所有成员
function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

next 、 throw、return
    都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

  • next():是将yield表达式替换成一个值,供下一段执行代码使用:
// 相当于将 
let result = yield x + y
// 替换成 
let result = 1;
  • throw():是将yield表达式替换成一个throw语句
// 相当于将 
let result = yield x + y
// 替换成 
let result = throw(new Error('出错了'));
  • return():是将yield表达式替换成一个return语句
// 相当于将 
let result = yield x + y
// 替换成 
let result = return 2;   // {value:2,done:true}

注意点 

  • gen.return:只能执行一次
  • gen.throw(async内部实现reject中用到

    • 可以外部捕获try...catch捕获
    • 也可以内部try...catch就可以内部捕获(内部错误未捕获,就不会再执行下去,认为generator已结束);
var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

【疑问】For...of、扩展运算符(...)、解构赋值、Array.from()可以直接适用于Generator的原因?如下例:

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}
for (let n of numbers()) {
  console.log(n)
}  // 1, 2

    已知for...of需要处理的是一个带有[Symbol.iterator]的对象,而生成器执行(numbers())返回的是遍历器对象;那为什么可以如下使用:for...of 
    原因如下:

numbers() === numbers()[Symbol.iterator]

应用

  • 状态机
  • 控制流管理:函数嵌套 -> promise -> generator+自动执行器 (异步操作的同步化表达)
  • Iterator接口

异步编程的完整解决方案

  • Generator 函数可以暂停执行恢复执行,这是它能封装异步任务的根本原因;
  • 函数体内外的数据交换(双向消息传递机制)

        1) 通过next()的参数给下一个代码块传参

2) 通过yield返回上一个代码块执行的结果

  • 错误处理机制。

自动执行

    需要有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权

  • 回调函数【缓存数据延迟执行,如compose】

      将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。

        函数传参的底层设计方式有如下2种方式:

      1) 传名调用(thunk是"传名调用"的一种实现策略,用来替换某个表达式)

f(x + 5)
// 传名调用时,等同于
(x + 5) * 2

        2) 传值调用

f(x + 5)
// 传值调用时,等同于
f(6)

   js是传值调用,而js中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

  • Promise 对象

    将异步操作包装成 Promise 对象,用then方法交回执行权。

Async

Generator的语法糖

  • 内置执行器
  • 语义清晰(async和await 对比 * yield)
  • 更广的适用性(await后可以是「promise」、「thenable」和原始类型的值,会被用promise.resove()包装成promise
  • 返回值是promise对象,Generator返回的是Iterator对象

错误处理 

    任何一个await语句后面的 Promise 对象变为reject状态,等同于「async函数返回的Promise对象」被reject。那么整个async函数都会中断执行:

  1. await后面的可以使用try...catch捕获: 
    await后面的表达式抛出异常,内部promise实例reject对应的回调函数为使用「gen.throw(e)」抛出异常,因此当执行下一个代码块时,遵循Generator的错误处理,错误抛出,可以被外部捕获(tip:promise内部的错误不会被被外界感知,注意不要混淆)。因此可以使用try...catch捕获异常,不影响函数内之后代码的执行

    async function test() {
        try {
            await myPromise;
        }
    }
  2. 可以用返回的promise的catch捕获: 
    无论是否使用try...catch捕获异常,内部的错误都不会影响外层,是因为自执行器对next的执行做了try...catch处理,异常时等同于try{gen.throw(e)}catch(e){return reject(e)},那么等同于async函数返回的 Promise 对象被reject。

    let promise = async function test() {
        await myPromise;
    }
    promise.catch(err => console.log(err));
  3. await后的promise内部消化异常: 

    let myPromise = Promise().catch(err => console.log(err));
    async function test() {
        await myPromise;
    }

简单实现原理如下:

function fn(args) {
  return spawn(function* () {
    //代码如下
  });
}


function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

递归的设计:每一步step负责执行yield后的表达式,并用promise包装好(根据状态决定下一个step是执行next还是throw)。

至此,ES6【代理】【流程控制】相关的内容就介绍这么多~

更多技术分享及时获取,欢迎关注~
1584413667(1).jpg
1585058173(1).jpg

基础薄弱的的童鞋,可以自行品读:阮一峰的ECMAScript 6 入门.


夜暮sky
97 声望5 粉丝