• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • ECMAScript 2020(ES11)新特性

    ECMAScript 2020(ES11)新特性:

    • ??操作符
    • ?.操作符
    • 导出加强:export
    • 动态导入:await import()
    • 元信息:import.meta
    • 等待所有的 Promise 结束:Promise.allSettled()
    • 正则模式:matchAll()
    • 任意精度的整数:BigInt
    • 标准化对象:globalThis


    ??操作符

    ??操作符是一个判断是否为空然后赋值的操作,如果没有这个操作符,我们通常使用||来简单的进行这个操作,如下所示:

    const yourAge = someBody.age || 18
    

    上面的代码意思是如果someBody.age是空,那么就将yourAge设置成为 18。但是上面代码有个问题,如果 someBody.age=0 的话,上述逻辑也成立。使用??操作符可以解决这个问题。

    const yourAge = someBody.age ?? 18
    


    ?.操作符

    我们有时候在获取某个对象的属性的时候,需要进行对象的null判断,否则从null对象中取出属性就会报错,但是通常的?:操作符使用起来太复杂了,如果有多个对象和属性连写的情况下更是如此,如果使用?.操作符就会简单很多:

    const age = school?.class?.student?.age;
    

    同样?.还可以用在对象的方法上:

    const age = student.getAge?.();
    

    上面代码表示,如果studentgetAge()方法存在,则调用,否则返回undefined


    动态导入

    在 ES11 之前,我们可以使用下面的方式进行模块的导入:

    import * as TestModule from "./test-module.js";
    

    但是上面的导入方式会有一些问题,首先是效率的问题,所有的 module 都需要在首次加载的时候导入,会导致程序效率的降低。另外上面的模块名字是写死的,不可以在程序运行的时候进行动态修改。也就是说上面的模块导入方式,不能对模块进行动态导入,或者按需导入,在使用上有诸多的不便。

    为了解决这个问题,ES11 引入了新的import()方法,使用这个方法,你可以对模块进行动态导入,并且通过设置模块名为变量的形式,可以对模块名进行动态修改。动态导入返回请求模块的模块名称空间对象的 promise 。因此,可以配合使用 async/await。

    const baseModulePath = "./baseModules";
    const btnImportModule = document.getElementById("btnImportModule");
    let userList = [];
    btnImportModule.addEventListener("click", async e => {
      const userModule = await import(`${baseModulePath}/users.js`);
      userList = userModule.getUsers();
    });
    
    element.addEventListener('click', async() => {
        const module = await import(`./api-scripts/button-click.js`);
        module.clickEvent();
    })
    


    import.meta

    除了动态引入模块之外,import 还提供了一个元属性meta,它包含了当前引入的模块的信息,目前他里面有一个 url 属性,代表模块被引用的 URL。如果想使用 URL 信息,那么可以在代码中使用import.meta.url

    import.meta,返回当前模块的元信息。import.meta只能在模块内部使用,如果在模块外部使用会报错。这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说,import.meta至少会有下面两个属性。

    import.meta.url

    import.meta.url返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是https://foo.com/main.jsimport.meta.url就返回这个路径。如果模块里面还有一个数据文件data.txt,那么就可以用下面的代码,获取这个数据文件的路径。

    new URL('data.txt', import.meta.url)
    

    注意,Node.js 环境中,import.meta.url返回的总是本地路径,即是file:URL协议的字符串,比如file:///home/user/foo.js


    import.meta.scriptElement

    import.meta.scriptElement是浏览器特有的元属性,返回加载模块的那个<script>>元素,相当于document.currentScript属性。

    // HTML
    // <script type="module" src="my-module.js" data-foo="abc"></script>
    // my-module.js 内部执行下面的代码
    import.meta.scriptElement.dataset.foo
    // "abc"
    


    export 加强

    对于导出模块需要重命名的情况,我们不能直接导出,而是必须先在import的时候进行重命名,然后再使用export将重命名的模块导出:

    import * as myModule from "./test-module.js";
    export {myModule};
    

    如果使用export的增强,则不需要使用import,直接使用export导出即可:

    export * as {myModule} from "./test-module.js";
    


    Promise.allSettled()

    自从 Promise 引入之后,有两个方法可以对 Promise 进行组合,分别是Promise.all()Promise.race(),他们分别表示返回所有的 Promise 和返回最快的那个。

    对于Promise.all()来说,它会等待所有的 Promise 都运行完毕之后返回,如果其中有一个 Promise 被 rejected,那么整个Promise.all()都会被 rejected。在这种情况下,如果有一个 Promise 被 rejected,其他的 Promise 的结果也都获取不了。

    为了解决这个问题,ES11 引入了Promise.allSettled()方法,这个方法会等待所有的 Promise 结束,不管他们是否被 rejected。

    const promises = [promise1("/do1"), promise2("/do2")];
    const allResults = await Promise.allSettled(promises);
    const errors = results
      .filter(p => p.status === 'rejected')
      .map(p => p.reason);
    


    String.protype.matchAll()

    以前,带字符串参数的String.match()仅返回第一个匹配。

    let string = 'Between';
    let matches = string.match('e');
    console.log(matches[0]); // "e"
    

    添加/g模式:

    let string = 'Between';
    let ret = string.match(/e/g);  // (3) ["e","e","e"];
    

    ES11 引入了matchAll()方法。这个方法可以简单的返回一个遍历器,通过遍历这个遍历器,就可以得到所有的匹配的值,如下所示:

    const regExp = /yyds(\d+)/g;
    const text = 'yyds1 is a very good yyds2';
    let matches = [...text.matchAll(regExp)];
    for (const match of matches) {
      console.log(match);
    }
    


    使用.matchAll()的好理由:

    • 在与捕获组一起使用时,它可以更加优雅,捕获组只是使用()提取模式的正则表达式的一部分。
    • 它返回一个迭代器而不是一个数组,迭代器本身是有用的。
    • 迭代器可以使用扩展运算符()转换为数组。
    • 它避免了带有/g标志的正则表达式,当从数据库或外部源检索未知正则表达式并与陈旧的 RegEx 对象一起使用时,它非常有用。
    • 使用 RegEx 对象创建的正则表达式不能使用点(.)操作符链接。
    • 高级: RegEx 对象更改跟踪最后匹配位置的内部.lastindex属性,这在复杂的情况下会造成严重破坏。
    const string = 'black*raven lime*parrot white*seagull';
    const regex = /(?<color>.*?)\*(?<bird>[a-z0-9]+)/;
    for (const match of string.matchAll(regex)) {
      let value = match[0];
      let index = match.index;
      let input = match.input;
      console.log(`${value} at ${index} with '${input}'`);
      console.log(match.groups.color);
      console.log(match.groups.bird);
    }
    


    BigInt

    ES11 引入了新的数据类型 BigInt,在这之前,javascript 中表示数字的对象是Number,它可以表示 64-bit 的浮点类型数字。当然它也可以代表整数,但是整数表示的最大值是2^53,也可以用Number.MAX_SAFE_INTEGER来表示。

    一般来说Number已经够用了,但是如果在某些情况下需要对 64-bit 的整数进行存储或者运算,或者要表示的范围超过了 64-bit 的话,Number 就不够用了。怎么办呢?如果只是存储的话,可以存储为字符串,但是第二种字符串就不适用了。于是引入了BigInt来解决这个问题。要表示 BigInt,只需要在数字的后面加上n即可。

    BigInt 是第七种原始类型。BigInt 是一个任意精度的整数。这意味着变量现在可以表示²⁵³数字,而不仅仅是 9007199254740992。

    const bigInt = 112233445566778899n;
    

    或者使用构造函数来构造 bigInt:

    const bigInt = BigInt("112233445566778899");
    

    可以使用typeof来查看 bigInt 的类型。要注意的是虽然Number和BigInt都代表的是数字,但是两者是不能混用的,你不能将一个 Number 和一个 BigInt 相加。这会报 TypeError 异常。如果非要进行操作,那么可以使用 BigInt 构造函数将 Number 转换成为 BigInt 之后再进行。


    const b = 1n; // 追加 n 以创建 BigInt
    

    在过去,不支持大于 9007199254740992 的整数值。如果超过,该值将锁定为 MAX_SAFE_INTEGER + 1:

    const limit = Number.MAX_SAFE_INTEGER;
    // 9007199254740991
    
    limit + 1;
    // 9007199254740992
    
    limit + 2;
    // 9007199254740992 <--- MAX_SAFE_INTEGER + 1 exceeded
    
    const larger = 9007199254740991n;
    // 9007199254740991n
    
    const integer = BigInt(9007199254740991); // initialize with number
    // 9007199254740991n
    
    const same = BigInt("9007199254740991"); // initialize with "string"
    // 9007199254740991n
    


    globalThis 对象

    在 ES10 之前,globalThis还没有标准化,在平时的项目可以通过以下方式来兼容不同的平台。

    var getGlobal = function () {
      if (typeof self !== 'undefined') { return self; }
      if (typeof window !== 'undefined') { return window; }
      if (typeof global !== 'undefined') { return global; }
      throw new Error('unable to locate global object');
    };
    

    但即使这样也不总是奏效。因此,ES10 添加了globalThis对象,从现在开始,该对象用于在任何平台上访问全局作用域。