ES6 - ES11

ES6

怎么理解ES6 中的Promise;使用场景
  • 介绍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
promise解决异步操作的优点:
1.链式操作减低了编码难度
2.代码可读性明显增强 */

//未使用promise
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('得到最终结果: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);

//使用promise
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);
  • 状态
1
2
3
4
promise 有3种状态
1.pending (进行中)
2.fulfilled (已成功)
3.rejected (已失败)
  • 特点
1
2
1.对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
2.一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果
  • 流程

image-20231126211227519

  • 用法
1
2
3
4
5
6
const promise = new Promise(function(resolve, reject) {});

/**
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败” */
  • 实例方法

    • Promise构建出来的实例存在以下方法:

      • then()
      1
      2
      3
      4
      5
      6
      7
      8
      /**
      then()是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数
      then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因 */
      getJSON("/posts.json").then(function(json) {
      return json.post;
      }).then(function(post) {
      // ...
      });
      • catch()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      /**
      catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数 */
      getJSON('/posts.json').then(function(posts) {
      // ...
      }).catch(function(error) {
      // 处理 getJSON 和 前一个回调函数运行时发生的错误
      console.log('发生错误!', error);
      });

      //Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止
      getJSON('/post/1.json').then(function(post) {
      return getJSON(post.commentURL);
      }).then(function(comments) {
      // some code
      }).catch(function(error) {
      // 处理前面三个Promise产生的错误
      });

      /**
      一般来说,使用catch方法代替then()第二个参数
      Promise对象抛出的错误不会传递到外层代码,即不会有任何反应 */
      • finally()
      1
      2
      3
      4
      5
      //finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
      promise
      .then(result => {···})
      .catch(error => {···})
      .finally(() => {···});
  • 构造函数方法

    • all()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例
    const p1 = new Promise((resolve, reject) => {
    resolve('hello');
    })
    .then(result => result)
    .catch(e => e);

    const p2 = new Promise((resolve, reject) => {
    throw new Error('报错了');
    })
    .then(result => result)
    .catch(e => e);

    Promise.all([p1, p2])
    .then(result => console.log(result))
    .catch(e => console.log(e));
    //如果p2没有自己的catch方法,就会调用Promise.all()的catch方法
    • race()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //将多个 Promise 实例,包装成一个新的 Promise 实例;只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
    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);
    • allSettled()
    1
    2
    //Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
    //只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束
    • resolve()
    1
    2
    3
    4
    //将现有对象转为 Promise对象
    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'))
    • reject()
    • try()
  • 使用场景

    1. 将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化
    1
    2
    3
    4
    5
    6
    7
    8
    const preloadImage = function (path) {
    return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload = resolve;
    image.onerror = reject;
    image.src = path;
    });
    };
    1. 通过链式操作,将多个渲染数据分别给个then,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    / 各司其职
    getInfo().then(res=>{
    let { bannerList } = res
    //渲染轮播图
    console.log(bannerList)
    return res
    }).then(res=>{

    let { storeList } = res
    //渲染店铺列表
    console.log(storeList)
    return res
    }).then(res=>{
    let { categoryList } = res
    console.log(categoryList)
    //渲染分类列表
    return res
    })
    1. 通过all()实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function initLoad(){
    loading.show() //加载loading
    Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
    console.log(res)
    loading.hide() //关闭loading
    }).catch(err=>{
    console.log(err)
    loading.hide()//关闭loading
    })
    }
    //数据初始化
    initLoad()
    1. 通过race可以设置图片请求超时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //请求某个图片资源
    function requestImg(){
    var p = new Promise(function(resolve, reject){
    var img = new Image();
    img.onload = function(){
    resolve(img);
    }
    //img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的
    img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";
    });
    return p;
    }

    //延时函数,用于给请求计时
    function timeout(){
    var p = new Promise(function(resolve, reject){
    setTimeout(function(){
    reject('图片请求超时');
    }, 5000);
    });
    return p;
    }

    Promise
    .race([requestImg(), timeout()])
    .then(function(results){
    console.log(results);
    })
    .catch(function(reason){
    console.log(reason);
    });
如何理解ES6中的Porxy; 使用场景
  • 介绍
1
Proxy 亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
  • 用法

    1. Proxy为 构造函数,用来生成 Proxy实例
    1
    2
    3
    4
    5
    6
    var proxy = new Proxy(target, handler)
    /**
    参数
    target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
    handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
    */
    1. 参数 handler 解析
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    关于handler拦截属性,有如下:

    get(target,propKey,receiver):拦截对象属性的读取
    set(target,propKey,value,receiver):拦截对象属性的设置
    has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
    deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
    ownKeys(target):拦截Object.keys(proxy)、for...in等循环,返回一个数组
    getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
    defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
    preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
    getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
    isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
    setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
    apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
    construct(target, args):拦截 Proxy 实例作为构造函数调用的操作
    1. Reflect
    1
    2
    3
    4
    5
    6
    若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API

    基本特点:
    只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在
    修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false
    Object操作都变成函数行为
    1. 一些参数用法

      • get() get接受三个参数,依次为目标对象、属性名和 proxy 实例本身,最后一个参数可选
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      var person = {
      name: "张三"
      };
      var proxy = new Proxy(person, {
      get: function(target, propKey) {
      return Reflect.get(target,propKey)
      }
      });
      proxy.name // "张三"
      //get能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引
      function createArray(...elements) {
      let handler = {
      get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
      propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
      }
      };
      let target = [];
      target.push(...elements);
      return new Proxy(target, handler);
      }
      let arr = createArray('a', 'b', 'c');
      arr[-1] // c
      //如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错
      const target = Object.defineProperties({}, {
      foo: {
      value: 123,
      writable: false,
      configurable: false
      },
      });

      const handler = {
      get(target, propKey) {
      return 'abc';
      }
      };

      const proxy = new Proxy(target, handler);

      proxy.foo
      // TypeError: Invariant check failed
      • set() 拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      let validator = {
      set: function(obj, prop, value) {
      if (prop === 'age') {
      if (!Number.isInteger(value)) {
      throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
      throw new RangeError('The age seems invalid');
      }
      }

      // 对于满足条件的 age 属性以及其他属性,直接保存
      obj[prop] = value;
      }
      };

      let person = new Proxy({}, validator);
      person.age = 100;
      person.age // 100
      person.age = 'young' // 报错
      person.age = 300 // 报错


      //如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用
      const obj = {};
      Object.defineProperty(obj, 'foo', {
      value: 'bar',
      writable: false,
      });

      const handler = {
      set: function(obj, prop, value, receiver) {
      obj[prop] = 'baz';
      }
      };

      const proxy = new Proxy(obj, handler);
      proxy.foo = 'baz';
      proxy.foo // "bar"


      //严格模式下,set代理如果没有返回true,就会报错
      'use strict';
      const handler = {
      set: function(obj, prop, value, receiver) {
      obj[prop] = receiver;
      // 无论有没有下面这一行,都会报错
      return false;
      }
      };
      const proxy = new Proxy({}, handler);
      proxy.foo = 'bar';
      // TypeError: 'set' on proxy: trap returned falsish for property 'foo'
      • deleteProperty() deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      var handler = {
      deleteProperty (target, key) {
      invariant(key, 'delete');
      Reflect.deleteProperty(target,key)
      return true;
      }
      };
      function invariant (key, action) {
      if (key[0] === '_') {
      throw new Error(`无法删除私有属性`);
      }
      }

      var target = { _prop: 'foo' };
      var proxy = new Proxy(target, handler);
      delete proxy._prop
      // Error: 无法删除私有属性

      //目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错
      • 取消代理
      1
      Proxy.revocable(target, handler);
  • 使用场景

    Proxy其功能非常类似于设计模式中的代理模式,常用功能如下:

    • 拦截和监视外部对对象的访问
    • 降低函数或类的复杂度
    • 在复杂操作前对操作进行校验或对所需资源进行管理
    1. 使用 Proxy 保障数据类型的准确性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let numericDataStore = { count: 0, amount: 1234, total: 14 };
    numericDataStore = new Proxy(numericDataStore, {
    set(target, key, value, proxy) {
    if (typeof value !== 'number') {
    throw Error("属性只能是number类型");
    }
    return Reflect.set(target, key, value, proxy);
    }
    });

    numericDataStore.count = "foo"
    // Error: 属性只能是number类型

    numericDataStore.count = 333
    // 赋值成功
    1. 声明了一个私有的 apiKey,便于 api 这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let api = {
    _apiKey: '123abc456def',
    getUsers: function(){ },
    getUser: function(userId){ },
    setUser: function(userId, config){ }
    };
    const RESTRICTED = ['_apiKey'];
    api = new Proxy(api, {
    get(target, key, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
    throw Error(`${key} 不可访问.`);
    } return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
    throw Error(`${key} 不可修改`);
    } return Reflect.get(target, key, value, proxy);
    }
    });

    console.log(api._apiKey)
    api._apiKey = '987654321'
    // 上述都抛出错误
    1. 观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行; observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
    }
    //观察者函数都放进Set集合,当修改obj的值,在会set函数中拦截,自动执行Set所有的观察者
怎么理解ES6中Generator;使用场景
  • 介绍

    • 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

      形式上,Generator函数是一个普通函数,但是有两个特征:

      • function关键字与函数名之间有一个星号
      • 函数体内部使用yield表达式,定义不同的内部状态
    1
    2
    3
    4
    5
    function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
    }
  • 使用

    • Generator 函数会返回一个遍历器对象,即具有Symbol.iterator属性,并且返回给自己
    1
    2
    3
    4
    5
    6
    7
    8
    function* gen(){
    // some code
    }

    var g = gen();

    g[Symbol.iterator]() === g
    // true
    • 通过yield关键字可以暂停generator函数返回的遍历器对象的状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
    }
    var hw = helloWorldGenerator();

    /**
    上述存在三个状态:hello、world、return
    通过next方法才会遍历到下一个内部状态,其运行逻辑如下:
    1.遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    2.下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
    3.如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    4.如果该函数没有return语句,则返回的对象的value属性值为undefined
    */
    hw.next()
    // { value: 'hello', done: false }
    hw.next()
    // { value: 'world', done: false }
    hw.next()
    // { value: 'ending', done: true }
    hw.next()
    // { value: undefined, done: true }

    /**
    done用来判断是否存在下个状态,value对应状态值
    yield表达式本身没有返回值,或者说总是返回undefined
    通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
    */
    function* foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
    }

    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}

    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }

    //正因为Generator函数返回Iterator对象,因此我们还可以通过for...of进行遍历
    function* foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6;
    }

    for (let v of foo()) {
    console.log(v);
    }
    // 1 2 3 4 5

    //原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for...of进行遍历了
    function* objectEntries(obj) {
    let propKeys = Reflect.ownKeys(obj);

    for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
    }
    }

    let jane = { first: 'Jane', last: 'Doe' };

    for (let [key, value] of objectEntries(jane)) {
    console.log(`${key}: ${value}`);
    }
    // first: Jane
    // last: Doe
  • 异步解决方案

    • 回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数
    fs.readFile('/etc/fstab', function (err, data) {
    if (err) throw err;
    console.log(data);
    fs.readFile('/etc/shells', function (err, data) {
    if (err) throw err;
    console.log(data);
    });
    });
    //readFile函数的第三个参数,就是回调函数,等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行
    • Promise 对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用
    const fs = require('fs');

    const readFile = function (fileName) {
    return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
    if (error) return reject(error);
    resolve(data);
    });
    });
    };
    readFile('/etc/fstab').then(data =>{
    console.log(data)
    return readFile('/etc/shells')
    }).then(data => {
    console.log(data)
    })
    //这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强
    • generator 函数
    1
    2
    3
    4
    5
    6
    7
    //yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化
    const gen = function* () {
    const f1 = yield readFile('/etc/fstab');
    const f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
    • async/await
    1
    2
    3
    4
    5
    6
    7
    //将上面Generator函数改成async/await形式,更为简洁,语义化更强了
    const asyncReadFile = async function () {
    const f1 = await readFile('/etc/fstab');
    const f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
  • 区别:

1
2
3
4
5
6
7
通过上述代码进行分析,将promise、Generator、async/await进行比较:
1.promise和async/await是专门用于处理异步操作的
2.Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口...)
3.promise编写代码相比Generator、async更为复杂化,且可读性也稍差
4.Generator、async需要与promise对象搭配处理异步情况
5.async实质是Generator的语法糖,相当于会自动执行Generator函数
6.async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
  • 使用场景

    1. Generator是异步解决的一种方案,最大特点则是将异步操作同步化表达出来
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function* loadUI() {
    showLoadingScreen();
    yield loadUIDataAsynchronously();
    hideLoadingScreen();
    }
    var loader = loadUI();
    // 加载UI
    loader.next()

    // 卸载UI
    loader.next()
    1. 包括redux-saga中间件也充分利用了Generator特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
    import Api from '...'

    function* fetchUser(action) {
    try {
    const user = yield call(Api.fetchUser, action.payload.userId);
    yield put({type: "USER_FETCH_SUCCEEDED", user: user});
    } catch (e) {
    yield put({type: "USER_FETCH_FAILED", message: e.message});
    }
    }

    function* mySaga() {
    yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
    }

    function* mySaga() {
    yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
    }

    export default mySaga;
    1. 还能利用Generator函数,在对象上实现Iterator接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function* iterEntries(obj) {
    let keys = Object.keys(obj);
    for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
    }
    }

    let myObj = { foo: 3, bar: 7 };

    for (let [key, value] of iterEntries(myObj)) {
    console.log(key, value);
    }

    // foo 3
    // bar 7
怎么理解ES6中Module; 使用场景
  • 介绍

    模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)

    两个基本的特征:外部特征和内部特征

    • 外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能
    • 内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码

    为什么需要模块化

    • 代码抽象
    • 代码封装
    • 代码复用
    • 依赖管理
  • 使用

    ES6模块内部自动采用了严格模式,这里就不展开严格模式的限制,毕竟这是ES5之前就已经规定好

    模块功能主要由两个命令构成:

    • export:用于规定模块的对外接口
    • import:用于输入其他模块提供的功能
    1. export
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // profile.js
    export var firstName = 'Michael';
    export var lastName = 'Jackson';
    export var year = 1958;
    //或
    // 建议使用下面写法,这样能瞬间确定输出了哪些变量
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    export { firstName, lastName, year };

    //输出函数或类
    export function multiply(x, y) {
    return x * y;
    };

    //通过as可以进行输出变量的重命名
    function v1() { ... }
    function v2() { ... }
    export {
    v1 as streamV1,
    v2 as streamV2,
    v2 as streamLatestVersion
    };
    1. import
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
    // main.js
    import { firstName, lastName, year } from './profile.js';

    function setName(element) {
    element.textContent = firstName + ' ' + lastName;
    }


    //同样如果想要输入变量起别名,通过as关键字
    import { lastName as surname } from './profile.js';


    //当加载整个模块的时候,需要用到星号*
    // circle.js
    export function area(radius) {
    return Math.PI * radius * radius;
    }
    export function circumference(radius) {
    return 2 * Math.PI * radius;
    }

    // main.js
    import * as circle from './circle';
    console.log(circle) // {area:area,circumference:circumference}

    //输入的变量都是只读的,不允许修改,但是如果是对象,允许修改属性

    //如果不需要知道变量名或函数就完成加载,就要用到export default命令,为模块指定默认输出
    //加载该模块的时候,import命令可以为该函数指定任意名字
    // export-default.js
    export default function () {
    console.log('foo');
    }
    // import-default.js
    import customName from './export-default';
    customName(); // 'foo'
  • 动态加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
允许您仅在需要时动态加载模块,而不必预先加载所有模块,这存在明显的性能优势
这个新功能允许您将import()作为函数调用,将其作为参数传递给模块的路径。 它返回一个 promise,它用一个模块对象来实现,让你可以访问该对象的导出
*/
import('/modules/myModule.mjs')
.then((module) => {
// Do something with the module.
});

//复合写法
//如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
怎么理解Decorator (装饰器); 使用场景
  • 介绍

    即装饰器,在不改变原类和使用继承的情况下,动态地扩展对象功能

    优点:

    1. 代码可读性变强了,装饰器命名相当于一个注释
    2. 在不改变原有代码情况下,对原来功能进行扩展
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //定义一个类,什么属性值,方法也没有
    class soldier{
    }

    //定义一个方法,作为装饰器
    function strong(target){
    target.AK = true
    }

    //将装饰器装饰在类上
    @strong
    class soldier{
    }

    //调用类的AK属性
    soldier.AK // true
  • 用法

    Docorator修饰对象为下面两种:

    • 类的装饰
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    /**
    类的装饰
    当对类本身进行装饰的时候,能够接受一个参数,即类本身
    将装饰器行为进行分解,大家能够有个更深入的了解
    */
    @decorator
    class A {}

    // 等同于

    class A {}
    A = decorator(A) || A;


    //下面@testable就是一个装饰器,target就是传入的类,即MyTestableClass,实现了为类添加静态属性
    @testable
    class MyTestableClass {
    // ...
    }

    function testable(target) {
    target.isTestable = true;
    }

    MyTestableClass.isTestable // true

    //如果想要传递参数,可以在装饰器外层再封装一层函数
    function testable(isTestable) {
    return function(target) {
    target.isTestable = isTestable;
    }
    }

    @testable(true)
    class MyTestableClass {}
    MyTestableClass.isTestable // true

    @testable(false)
    class MyClass {}
    MyClass.isTestable // false
    • 类属性的装饰
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    /**
    类属性的装饰
    当对类属性进行装饰的时候,能够接受三个参数:
    1.类的原型对象
    2.需要装饰的属性名
    3.装饰属性名的描述对象
    */

    //首先定义一个readonly装饰器
    function readonly(target, name, descriptor){
    descriptor.writable = false; // 将可写属性设为false
    return descriptor;
    }
    //使用readonly装饰类的name方法
    class Person {
    @readonly
    name() { return `${this.first} ${this.last}` }
    }
    //相当于以下调用
    readonly(Person.prototype, 'name', descriptor);

    //如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行
    function dec(id){
    console.log('evaluated', id);
    return (target, property, descriptor) =>console.log('executed', id);
    }

    class Example {
    @dec(1)
    @dec(2)
    method(){}
    }
    // evaluated 1
    // evaluated 2
    // executed 2
    // executed 1
    //外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行
  • 注意项

    装饰器不能用于修饰函数,因为函数存在变量声明情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    var counter = 0;

    var add = function () {
    counter++;
    };

    @add
    function foo() {
    }
    //编译阶段,变成下面
    var counter;
    var add;

    @add
    function foo() {
    }

    counter = 0;

    add = function () {
    counter++;
    };
    //意图是执行后counter等于 1,但是实际上结果是counter等于 0
  • 使用场景

    基于Decorator强大的作用,我们能够完成各种场景的需求,下面简单列举几种:

    1. 使用react-redux的时候,如果写成下面这种形式,既不雅观也很麻烦
    1
    2
    3
    4
    5
    class MyReactComponent extends React.Component {}
    export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
    //通过装饰器就变得简洁多了
    @connect(mapStateToProps, mapDispatchToProps)
    export default class MyReactComponent extends React.Component {}
    1. mixins,也可以写成装饰器,让使用更为简洁了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function mixins(...list) {
    return function (target) {
    Object.assign(target.prototype, ...list);
    };
    }

    // 使用
    const Foo = {
    foo() { console.log('foo') }
    };

    @mixins(Foo)
    class MyClass {}

    let obj = new MyClass();
    obj.foo() // "foo"
    1. 讲讲core-decorators.js几个常见的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//@antobind
//autobind装饰器使得方法中的this对象,绑定原始对象
import { autobind } from 'core-decorators';

class Person {
@autobind
getPerson() {
return this;
}
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person;
// true



//@readonly
//readonly装饰器使得属性或方法不可写
import { readonly } from 'core-decorators';

class Meal {
@readonly
entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]



//@deprecate
//deprecate或deprecated装饰器在控制台显示一条警告,表示该方法将废除
import { deprecate } from 'core-decorators';

class Person {
@deprecate
facepalm() {}

@deprecate('功能废除了')
facepalmHard() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: 功能废除了
ES6 class

​ class 没有声明提升

类方法 constructor

​ 这个方法类似于其他语言,类的init方法,每次new实例时都会调用

关键字 extends (继承)
1
class Son_of_Bullshit extends Bullshit{}
关键字 super
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 当我们获取父类属性时,需要用到super进行获取
// 例如子类写了constructor , 父类的属性就需要super进行获取
class Bullshit{
constructor (text, color){
this.text = text;
this.color = color;
}
}
class Son_of_Bullshit extends Bullshit{
constructor (text, color, fontSize) {
super(text, color)
this.fontSize = fontSize
}
}
关键字 static

​ 在类里,静态属性、方法是属于类自身的,当调用时直接使用类调用即可

​ 静态方法里的this是类而非实例对象,所以静态方法里面可以调用另外一个静态方法

关键字setter getter (设置属性和获取属性)
class实现单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
class SingletonApple {
constructor(name, creator, products) {
this.name = name;
this.creator = creator;
this.products = prodcts;
}
// 静态方法
static getInstance(name, creator, products) {
if(!this.instance){
this.instance = new SingletonApple(name, creator,products);
}
return this.instance;
}
}
let appleComany = SingletonApple()
let copyApple = SingletonApple()
console.log(appleComany === copyApple) //true