ES6

ES6

一、 变量赋值与解构

  • 默认值赋值注意点:
    • 只有当一个数组成员严格等于undefined,默认值才会生效。
        let [x = 1] = [undefined];
        x // 1
        
        let [x = 1] = [null];
        x // null
    
    • 默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
        let [x = 1, y = x] = [];     // x=1; y=1
        let [x = 1, y = x] = [2];    // x=2; y=2
        let [x = 1, y = x] = [1, 2]; // x=1; y=2
        let [x = y, y = 1] = [];     // ReferenceError: y is not defined
    

  • 变量的解构注意点:

    • 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
        let arr = [1, 2, 3];
        let {0 : first, [arr.length - 1] : last} = arr;
        first // 1
        last // 3
    
    • 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
        let { prop: x } = undefined; // TypeError
        let { prop: y } = null; // TypeError
    
    • 为了区分模式,还是表达式 --> 只要有可能导致解构的歧义,就不得使用圆括号。
        //变量申明
        let [(a)] = [1];
        let {x: (c)} = {};
        
        // 函数参数
        function f([(z)]) { return z; }
        function f([z,(x)]) { return x; }
        
        //赋值语句的模式部分
        ({ p: a }) = { p: 42 };
        ([a]) = [5];
        
        //可以使用(赋值语句的非模式部分)
        [(b)] = [3]; // 正确
        ({ p: (d) } = {}); // 正确
        [(parseInt.prop)] = [3]; // 正确
    
     > 与`for...of`配合 获取键名和键值就非常方便。
    

二、- 字符串拓展

  1. 详见阮一峰老师网站-String



三、- 正则拓展

  1. 详见阮一峰老师网站-RegExp
    • u修饰符,含义为“Unicode 模式”

    • y 修饰符“粘连”(sticky)修饰符:确保匹配必须从剩余的第一个位置开始

    • ‘后行断言’

      • 先行断言:x只有在y前面才匹配,必须写成/x(?=y)/
        比如:只匹配百分号之前的数字,要写成/\d+(?=%)/

      • 先行否定断言:x只有不在y前面才匹配,必须写成/x(?!y)/

      • 后行断言:x只有在y后面才匹配,必须写成/(?<=y)x/
        如:只匹配美元符号之后的数字,要写成/(?<=\$)\d+/

      • 后行否定断言:x只有不在y后面才匹配,必须写成/(?<!y)x/

      后行断言与正常情况顺序是相反的,为“先右后左”的执行顺序

    • 具名组匹配:允许为每一个组匹配指定一个名字;
      引用某个“具名组匹配” \k<组名>或者\1
      具名组匹配在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名。

          const REG = /^(?<word>[a-z]+)!\k<word>!\1$/;
          REG.test('abc!abc!abc') // true
          REG.test('abc!abc!ab') // false
      



四、- 数值的拓展

  1. 二进制和八进制表示法(前缀0b | 0o)
  2. Number.isFinite(), Number.isNaN()
  3. Number.parseInt(), Number.parseFloat()
  4. Number.isInteger()
  5. Number.EPSILON(最小精度) 2 ** -52
  6. 安全整数和 Number.isSafeInteger(): 判断是否在安全值范围
  7. Math 对象的扩展
  8. 指数运算符**



五、- 函数的扩展

  1. 函数参数的默认值

  2. rest 参数 …props

  3. 严格模式

  4. name 属性

  5. 箭头函数 =>this引用的外层函数
    注意点:

    • (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    • (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
    • (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    • (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  6. 双冒号运算符

     foo::bar;
     // 等同于
     bar.bind(foo);
     
     var method = obj::obj.foo;
    // 等同于
    var method = ::obj.foo;
    
  7. 尾调用:(指某个函数的最后一步是调用另一个函数)优化

    • 只保留内层调用

    注意点:只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

  8. 尾递归:尾调用自身

    //计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。
     function factorial(n) {
        if (n === 1) return 1;
        return n * factorial(n - 1);
    }
    
    factorial(5) // 120
    
    //尾递归优化,只保留一个调用记录,复杂度 O(1) 。
    function factorial(n, total = 1) {
        if (n === 1) return total;
        return factorial(n - 1, n * total);
    }
    
    factorial(5) // 120
    

    tips:把所有用到的内部变量改写成函数的参数



六、- 数组的扩展

  1. 扩展运算符(...[])的应用

    • 转成参数序列

    • 复制数组

      const a1 = [1, 2];
      const a2 = a1.concat(); //es5
      
      const a3 = [...a1];     //es6
      const [...a4] = a1;
      
    • 合并数组 [...arr1, ...arr2, ...arr3] (浅拷贝)

    • 解构赋值 const [first, ...rest] = [1, 2, 3, 4, 5]

    • 正确返回字符长度 [...str].length

      'x\uD83D\uDE80y'.length // 4
      [...'x\uD83D\uDE80y'].length // 3
      
    • 实现 Iterator 接口的对象.
      任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组。
      对于那些没有部署 Iterator接口的类似数组的对象,使用Array.from(arrayLike),实际上任何有length属性的对象,都可以通过Array.from方法转为数组。

  2. Array.from():

    • 用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象(包括 ES6 新增的数据结构 Set 和 Map)。
    • Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
       Array.from(arrayLike, x => x * x);
       // 等同于
       Array.from(arrayLike).map(x => x * x);
    
       Array.from([1, 2, 3], (x) => x * x)
       // [1, 4, 9]
    
  3. Array.of():

- 用于将一组值,转换为数组。
- 为了弥补构造函数`Array()`的不足

    ```
        //参数个数不一样行为不同
        Array() // []
        Array(3) // [, , ,]
        Array(3, 11, 8) // [3, 11, 8]
    ```
    `Array.of`总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
  1. 数组实例的 copyWithin()

    • 将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。。
    • Array.prototype.copyWithin(target, start = 0, end = this.length)
  2. 数组实例的 fill()

    • 可用于填充数组,数组中已有的元素,会被全部抹去。

    • fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

      ['a', 'b', 'c', 'd'].fill(7, 1, 3)
      // ['a', 7, 7, 'd']
      
      //> 注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
      let arr = new Array(3).fill([]);
      arr[0].push(5);
      // arr = [[5], [5], [5]]
      
  3. 数组实例的 entries()keys()values()

    • for...of循环进行遍历,keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

      for (let index of ['a', 'b'].keys()) {
          console.log(index);
      }
      // 0
      // 1
      
      for (let elem of ['a', 'b'].values()) {
        console.log(elem);
      }
      // 'a'
      // 'b'
      
      for (let [index, elem] of ['a', 'b'].entries()) {
        console.log(index, elem);
      }1
      // 0 "a"
      // 1 "b"
      
  4. 数组实例的 includes()

    • 表示某个数组是否包含给定的值(第二个参数表示搜索的起始位置),与字符串的includes方法类似
       [1, 2, 3].includes(3, 3)   // false
       [1, 2, 3].includes(3, -1)  // true
       [1, 2, [3]].includes([3])  // false
       [NaN].includes(NaN)        // true
    
  5. 数组实例的 flat()flatMap() —Chrome无此方法(2018/8/21)

    • flat()用于将嵌套的数组“拉平”,变成一维的数组。参数:想要拉平的层数,默认为1。

      [1, 2, [3, [4, 5]]].flat()
      // [1, 2, 3, [4, 5]]
      
      //如果原数组有空位,flat()方法会跳过空位。
      [1, 2, , 4, 5].flat()
      // [1, 2, 4, 5]
      
      [1, [2, [3]]].flat(Infinity) //Infinity:不管有多少层嵌套,都要转成一维数组
      // [1, 2, 3]
      
    • flatMap() 方法对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。传入函数(类似map),只能展开一层

  6. 数组的空位的处理

    • ES5:

      • forEach(), filter(), reduce(), every() 和some()都会跳过空位。
      • map()会跳过空位,但会保留这个值
      • join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
    • ES6:

      • 明确将空位转为undefined

        [...['a',,'b']]
        // [ "a", undefined, "b" ]
        



七、- 对象的扩展

  1. 简写{foo} === {foo: foo}

  2. Object.is():

        +0 === -0 //true
        NaN === NaN // false
        
        Object.is(+0, -0) // false
        Object.is(NaN, NaN) // true
    
  3. 对象详情



八、Symbol类型:表示独一无二的值

  1. Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
  2. Symbol 值不能与其他类型的值进行运算,会报错。
  3. Symbol 值可以显式转为字符串|布尔值但不能转为数值。
  4. Symbol 作为属性名,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。



九、 Set 和 Map 数据结构

  1. Set: 它类似于数组,但是成员的值都是唯一的,没有重复的值。
   const set = new Set([1, 2, 3, 4, 4, 4, 4]);
   [...set]
   // [1, 2, 3, 4]
   
   // 去除数组的重复成员
   [...new Set(array)]
   
   //在 Set 内部,两个NaN是相等。
   //另外,两个对象总是不相等的。
- add(value):添加某个值,返回 Set 结构本身。
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- has(value):返回一个布尔值,表示该值是否为Set的成员。
- clear():清除所有成员,没有返回值。
  1. WeakSet :与 Set 类似,但 WeakSet 的成员只能是对象,而不能是其他类型的值。
    没有size属性,没有办法遍历它的成员。

  2. Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

        const map = new Map();
        const k1 = ['a'];
        const k2 = ['a'];
        
        map
        .set(k1, 111)
        .set(k2, 222);
        
        map.get(k1) // 111
        map.get(k2) // 222
    
  3. WeakMap :与Map结构类似,也是用于生成键值对的集合。

    • 区别:
      • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。

      • WeakMap的键名所指向的对象,不计入垃圾回收机制。

        const wm = new WeakMap();
        
        const element = document.getElementById('example');
        
        wm.set(element, 'some information');
        wm.get(element) // "some information"
        /*
        先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,
        并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element
        的引用就是弱引用,不会被计入垃圾回收机制即DOM节点对象的引用计数是1,而不是2。
        可以有效防止内存泄漏。
        (WeakMap 的另一个用处是部署私有属性。如果删除实例,它们也就随之消失)
        */
        



十、 Proxy

  1. 含义:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。(代理器)

  2. new Proxy(target, handler)

  3. Proxy支持的拦截操作

    1. get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
    2. set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
    3. has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
    4. deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
    5. ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
    6. getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
    7. defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
    8. preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
    9. getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
    10. isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
    11. setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
    12. apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
    13. construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)



十一、 Reflect

  1. 从Reflect对象上可以拿到语言内部的方法。

  2. 13个静态方法

    • Reflect.apply(target, thisArg, args)

    • Reflect.construct(target, args)

    • Reflect.get(target, name, receiver)

      const myObject = {
        foo: 1,
        bar: 2,
        get baz() {
          return this.foo + this.bar;
        },
      }
      console.log(Reflect.get(myObject, 'foo')) // 1
      console.log(Reflect.get(myObject, 'bar')) // 2
      console.log(Reflect.get(myObject, 'baz')) // 3
      
    • Reflect.set(target, name, value, receiver)

      myObject.foo // 1
      
      Reflect.set(myObject, 'foo', 2);
      myObject.foo // 2
      
    • Reflect.defineProperty(target, name, desc)

      // 新写法
      Reflect.defineProperty(MyDate, 'now', {
        value: new Date()
      })
      
      console.log(MyDate.now)
      
    • Reflect.deleteProperty(target, name)

    • Reflect.has(target, name)

    • Reflect.ownKeys(target)

    • Reflect.isExtensible(target)

    • Reflect.preventExtensions(target)

    • Reflect.getOwnPropertyDescriptor(target, name)

      const myObject = {};
      Reflect.defineProperty(myObject, 'hidden', {
        value: true,
        writable:true,
      });
      
      const theDescriptor = Reflect.getOwnPropertyDescriptor(myObject,'hidden');
      console.log(theDescriptor)
      //{value: true, writable: true, enumerable: false, configurable: false}
      myObject.hidden = 1
      console.log(myObject) //{hidden: 1}
      
    • Reflect.getPrototypeOf(target)

    • Reflect.setPrototypeOf(target, prototype)

  3. 实例:使用 Proxy 实现观察者模式

    const queuedObservers = new Set();
    
    const observe = fn => queuedObservers.add(fn);
    const observable = obj => new Proxy(obj, {set});
    
    function set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      queuedObservers.forEach(observer => observer());
      return result;
    }
    
    const person = observable({
      name: '张三',
    });
    function print() {
      console.log(`${person.name}`)
    }
    
    observe(print);
    person.name = '李四';  //李四
    person.name = '33';     //33
    
    

    上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。然后,observable函数返回原始对象的代理,拦截赋值操作。拦截函数set之中,会自动执行所有观察者—这跟vue很相似



十二、 Promise 对象

  1. 含义:Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

  2. Promise.prototype.then()
    then方法返回的是一个新的Promise实例。因此可以采用链式写法

  3. Promise.prototype.catch()
    .then(null, rejection)的别名,一般建议总是使用catch方法。catch方法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。

如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码

  1. Promise.prototype.finally()
    不管 Promise 对象最后状态如何,都会执行的操作。

    promise
    .finally(() => {
      // 语句
    });
    
    // 等同于
    promise
    .then(
      result => {
        // ...
        return result;
      },
      error => {
        //...
        throw error;
      }
    )
    
  2. Promise.all()
    const p = Promise.all([p1, p2, p3]);

    注意:

    • Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
    • 只有所有的状态都变成fulfilled,返回值组成一个数组,传递给回调函数,
      或者有一个变为rejected,被reject的实例的返回值才会继续传递给回调函数。
  3. Promise.race()
    const p = Promise.race([p1, p2, p3]);
    只要有一个状态改变就。。。

    //当接口五秒内未返回就报超时错误
    const p = Promise.race([
      fetch('/resource-that-may-take-a-while'),
      new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
      })
    ]);
    
    p
    .then(console.log)
    .catch(console.error);
    
  4. Promise.resolve()

    Promise.resolve('foo')
    // 等价于(可以将现有对象转成Promise对象)
    new Promise(resolve => resolve('foo'))
    
    • 参数是个Promise 实例:不做修改直接返回这个实例
    • 参数是个具有then方法的对象:先转Promise然后立即执行then方法
    • 参数不是具有then方法的对象,或根本就不是对象:返回状态为resolved的Promise对象
      不带有任何参数:直接返回一个resolved状态的 Promise 对象。
  5. Promise.reject()
    Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

  6. Promise.try()
    不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它时。

    解决方案1 :

    const f = () => console.log('now');
    (async () => f())()
    console.log('next')
    // now
    // next
    

    解决方案2 :

    const f = () => console.log('now');
    (
      () => new Promise(
        resolve => resolve(f())
      )
    )();
    console.log('next');
    // now
    // next
    


十三、Iterator 和 for…of 循环

①. Iterator
  1. 凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
    原生具备Iterator接口的数据结构 (可以用for…of遍历它们):

    • Array
    • Map
    • Set
    • String
    • TypedArray
    • 函数的 arguments 对象
    • NodeList 对象

  2. 默认调用Symbol.iterator方法的情况

    • 对数组和 Set 结构进行解构赋值时
    • 使用扩展运算符(…)
    • yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
    const generator = function* () {
      yield* [1,2];
    };
    
    var iterator = generator();
    
    iterator.next() // { value: 1, done: false }
    iterator.next() // { value: 2, done: false }
    iterator.next() // { value: undefined, done: true }
    
    • 数组的遍历会调用遍历器接口
      • for…of
      • Array.from()
      • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
      • Promise.all()
      • Promise.race()
  3. 字符串是一个类似数组的对象,也原生具有 Iterator 接口。

  4. 遍历器对象的 return()

    可以触发return的情况

    // 情况一:break
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      break;
    }
    
    // 情况二:Error
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      throw new Error();
    }
    
    • 情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;
    • 情况二会在执行return方法关闭文件之后,再抛出错误。

#### ②. for...of 循环 1. `for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。 2. `for...in`循环读取键名,`for...of`循环读取键值。 `for...of`循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。 ``` let arr = [3, 5, 7]; arr.foo = 'hello';
for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for (let i of arr) {
  console.log(i); //  "3", "5", "7"
}
```
Set和Map
  1. Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组([k,v])
其他
  1. 对于没有Iterator接口的类数组对象:使用Array.from方法将其转为数组。



十四、Generator 函数的语法

  1. Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
   const myIterable = {}
   myIterable[Symbol.iterator] = function* () {
     yield 1
     yield 2
     yield 3
   };
   
   console.log([...myIterable])  // [1, 2, 3]

2. 注意: - `yield`表达式只能用在 Generator 函数里面,用在其他地方都会报错。 - `yield`表达式如果用在另一个表达式之中,必须放在圆括号里面。 - `yield`表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

  1. next 方法的参数

    • next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
      也就是说:可以在 Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
      • 注意: 由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的

  2. Generator.prototype.throw():

    • 只要 Generator 函数内部部署了try…catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。
    • 一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。

  3. Generator.prototype.return():

    • 可以返回给定的值,并且终结遍历 Generator 函数。

  4. yield* 表达式:为了说明yield表达式后面跟的是一个遍历器对象时加*

  5. Generator 函数的this:

    • Generator 函数总是返回遍历器对象而不是this
    function* gen() {
      this.a = 1;
      yield this.b = 2;
      yield this.c = 3;
    }
    
    function F() {
      return gen.call(gen.prototype);//<--
    }
    
    var 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
    

  6. Generator

    • 与状态机:完美管理状态
    • 协程:既可以用单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。
    • 传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。
    • 与普通线程差异:
      • 同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。
      • 普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。



十五、Generator 函数的异步应用

  1. 传统方法

    • 回调函数 ->(callback hell)强耦合

    • 事件监听

    • 发布/订阅

    • Promise对象 ->代码冗余,原来的任务被Promise包装了一下,一堆then,原来的语义变得很不清楚。

  2. Thunk 函数:是“传名调用”的一种实现策略,用来替换某个表达式。
    /JavaScript 语言是传值调用,以下是个自执行器(thunkify、co模块、)

    //实现自执行
    

function thunkify(fn) {
return function() {
var args = new Array(arguments.length);
var ctx = this;

    for (var i = 0; i < args.length; ++i) {
      args[i] = arguments[i];
    }

    return function (done) {
      var called;
      args.push(function () {
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });

      try {
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    }
  }
}   
   
 const ft = (n,cb) =>cb(n)
 const CB = thunkify(ft)  
    
    //实现自执行
    function run(fn) {
      var gen = fn()
    
      function next(err, data) {
        var result = gen.next(data)
        console.log(result)
        if (result.done) return;
        result.value(next)
      }
    
      next()
    }
    

    const g = function* (){
      var f1 = yield CB('aaaaaa')
      var f2 = yield CB('bbbbbb')
      // ...
      var fn = yield CB('nnnnnn')
    }
    
    run(g)
```
<br/>
  1. CO模块

    1. 原理:Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。以下两种方法可以做到这一点:
      • 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
      • Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。

    2. 基于 Promise 对象的自动执行
    function run(gen){
      var g = gen();
    
      function next(data){
        var result = g.next(data)
        console.log(result)
        if (result.done) return result.value
        result.value.then(function(data){
          next(data);
        });
      }
    
      next();
    }
    
    //需要返回promise 然后链式自执行
    const CO = (data) => new Promise((resolve)=> resolve(data))
    const g = function* (){
      var f1 = yield CO('aaaaaa')
      var f2 = yield CO('bbbbbb')
      // ...
      var fn = yield CO('nnnnnn')
    }
    
    run(g)
    
    1. 处理并发的异步操作
    • co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。
    1. 实例:处理 Stream


十六、async 函数

  1. Generator 函数的语法糖

    • 改进
    • (1)async函数自带执行器。
    • (2)更广的适用性
      co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
    • (3)返回值是 Promise
      Generator 函数的返回值是 Iterator 对象

  2. 注意点

    1. async函数内部return语句返回的值,会成为then方法回调函数的参数。
    2. async函数内部抛出错误,会导致返回的Promise对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
    3. 只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。 => 想继续执行 可以吧代码放在try...catch中。
    4. await命令只能用在async函数之中,如果用在普通函数,就会报错。
    • forEach/Map中使用async也会出问题 :
    // scope B
    array.forEach(async function(item) { // scope A
     await wait(1000);
    });
    /*
    `Scope B` 部分的代码并不会等待 `Scope A` 中的 `async/await` 执行完后继续执行后面的代码,相反,他会立刻执行后面的代码
    */
    
    
    • 正确的写法是采用for循环、

  3. 并发执行

    // 写法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法二
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
    
  4. for await...of:遍历异步的 Iterator 接口。

    for await (const chunk of readStream) {
        console.log('>>> '+chunk);
    }
    
  5. 异步 Generator 函数

    • 在头部加async 标志,此时即可返回一个Promise



十七、Class 的基本语法

  1. constructor 方法
    • 通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
    • constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

  2. class Foo {}不存在声明提升

  3. 私有方法和私有属性
    • 函数名加下划线(仅为命名方法,外部仍可调用)
    • 将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
    class Widget {
      foo (baz) {
        bar.call(this, baz);
      }
      // ...
    }
    
    function bar(baz) {
      return this.snaf = baz;
    }
    
    • 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
    const bar = Symbol('bar')
    const snaf = Symbol('snaf')
    
    export default class myClass{
      foo(baz) {
        this[bar](baz);
      }
    
      // 私有方法
      [bar](baz) {
        return this[snaf] = baz;
      }
      // ...
    };
    

4. this 的指向 - 当类的方法中有this使用且方法被提取出来单独使用时this会指向方法运行环境导致意料之外的错误 1. 可以在构造函数中绑定`this.printName = this.printName.bind(this)` 2. 箭头函数: ``` constructor() { this.printName = (name = 'there') => { this.print(`Hello ${name}`) } }
```
3. 使用Proxy,获取方法的时候,自动绑定this
```
function selfish (target) {
  const cache = new WeakMap();
  const handler = {
    get (target, key) {
      const value = Reflect.get(target, key);
      if (typeof value !== 'function') {
        return value;
      }
      if (!cache.has(value)) {
        cache.set(value, value.bind(target));
      }
      return cache.get(value);
    }
  };
  const proxy = new Proxy(target, handler);
  return proxy;
}

const logger = selfish(new Logger());
```
<br/>
- Class 的取值函数(getter)和存值函数(setter)
```
class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

```

</br>
- Class 的静态方法
    在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用



十八、Class 的继承

  1. 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。 =>constructor不写默认 有super(...args)

  2. Object.getPrototypeOf:从子类上获取父类

    //可以使用这个方法判断,一个类是否继承了另一个类。
    Object.getPrototypeOf(ColorPoint) === Point
    // true
    
    
  3. super:

    • super在B的构造函数中,指向A.prototype,所以super可以拿到父类原型对象上的实例方法
    • 如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
  4. 原生构造函数的继承

    • Boolean()
      Number()
      String()
      Array()
      Date()
      Function()
      RegExp()
      Error()
      Object()

    原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。
    extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数!

  5. Mixin 模式的实现(多个对象合成一个新的对象)

    function mix(...mixins) {
      class Mix {}
    
      for (let mixin of mixins) {
        copyProperties(Mix.prototype, mixin); // 拷贝实例属性
        copyProperties(Mix.prototype, Reflect.getPrototypeOf(mixin)); // 拷贝原型属性
      }
    
      return Mix;
    }
    
    function copyProperties(target, source) {
      for (let key of Reflect.ownKeys(source)) {
        if ( key !== "constructor"
          && key !== "prototype"
          && key !== "name"
        ) {
          let desc = Object.getOwnPropertyDescriptor(source, key);
          Object.defineProperty(target, key, desc);
        }
      }
    }
    



十九、修饰器(提案)

  1. 修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升
  2. 详见>>ES6-修饰器



二十、Module 的语法

  1. 注意点
    • import在静态解析阶段执行,所以它是一个模块之中最早执行的。
    • import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
    // 报错
    import { 'f' + 'oo' } from 'my_module';
    
    // 报错
    let module = 'my_module';
    import { foo } from module;
    
    // 报错
    if (x === 1) {
      import { foo } from 'module1';
    } else {
      import { foo } from 'module2';
    }
    //在静态分析阶段,这些语法都是没法得到值的。
    
  2. export default命令其实只是输出一个叫做default的变量
  3. export 与 import 的复合写法
    //foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口
    export { foo, bar } from 'my_module'
    //>可以用来合并一些模块
    
    
  4. 提案:import()函数 => 用来动态加载模块 返回Promise



二十、Module 的加载实现

  1. 浏览器加载

    • 传统方法
    • defer :“渲染完再执行” , 多个defer脚本,会按照它们在页面出现的顺序加载。
    • async :“下载完就执行” , 多个async脚本,不能保证加载顺序。
    • 加载 ES6 模块
    • <script type="module" src="./...js"></script>:此时行为与defer相同

  2. ES6 模块与 CommonJS 模块的差异

    • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
    • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

  3. Node 加载

    • Node 有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的

    • Node 要求 ES6 模块采用.mjs后缀文件名。(新版)
    $ node --experimental-modules my-app.mjs
    
    • 内部变量差异
    • ES6 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块。
    • ES6不存在这些顶层变量:
      1. arguments
      2. require
      3. module
      4. exports
      5. __filename
      6. __dirname

    • ES6 模块加载 CommonJS 模块
      • Node 的import命令加载 CommonJS 模块,Node 会自动将module.exports属性,当作模块的默认输出,即等同于{ default: module.exports }
      • import命令加载 CommonJS 模块时,不允许采用解构的写法,改为整体输入

    • CommonJS 模块加载 ES6 模块
    • 不能使用require命令,而要使用import()函数。ES6 模块的所有输出接口,会成为输入对象的属性。

  4. 循环加载

    • CommonJS 模块加载原理
    • require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
    • CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
    • 由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。

    • ES6 模块的循环加载
    • 与CommonJS不同:变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
    • a 引用 b,b中引用a,执行到b中对a引用时会认为a已经存在,继续往下执行
  • 4
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值