四时宝库

程序员的知识宝库

快扶我起来,我还能学——ES2020与ES2021那些新特性

学到老,活到老!2020年眼看接近尾声了,ECMA在今年又搞了不少事情,让我们睁开疲惫的双眼,去回顾下ES2020顺便提前展望下ES2021吧(放心,我这只提了提案第四阶段完成的,没那么多)。

ES2020

1. String.protoype.matchAll

介绍

matchAll()方法是ES2020中新增的一个字符串方法,它返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

参数:一个正则表达式对象。如果所传参数不是一个正则表达式对象,则会隐式地使用new RegExp(obj)将其转换为一个RegExp。(该RegExp必须是设置了全局匹配模式g的形式,否则会抛出异常TypeError)

  • 返回值:一个迭代器。(我们可以通过扩展运算符 ...、Array.from() 方法将其转换为数组,或者通过 for…of 对其元素进行遍历访问)
  • 相比于match()方法,matchAll()能够获取更全面的匹配项信息让我们进行相关操作。

    使用

    在matchAll()出现之前,我们一般通过在循环中调用 RegExp.exec() 来获取所有匹配项信息。现在有了matchAll()方法,我们便可以替代while循环加exec的方式:

    
    const reg = /\b(t(\w)+)\b/g;
    const str = 'It is never too late to learn.';
    
    // before
    let match;
    while ((match = reg.exec(str)) !== null) {
      console.log(match);
    }
    // ['too', 'too', 'o', index: 12, input: 'It is never too late to learn.', groups: undefined]
    // ['to', 'to', 'o', index: 21, input: 'It is never too late to learn.', groups: undefined]
    
    // now
    const result = str.matchAll(reg);
    console.log([...result]); 
    // [['too', 'too', 'o', index: 12, input: 'It is never too late to learn.', groups: undefined], ['to', 'to', 'o', index: 21, input: 'It is never too late to learn.', groups: undefined]]
    
    

    2. dynamic import

    介绍

    动态导入通过import关键字,在运行时加载脚本或者模块,返回一个Promise对象,实现按需加载。

    动态导入功能ES2020中很实用的功能,实际上,这种功能已经被呼吁长达好几年了,在很久前我们已经可以通过webpack的配置实现,现在,我们可以直接通过javascript实现按需加载了。


    使用

    随着前端应用体积的增大,这种按需加载的需求场景已经非常多,例如图片懒加载、spa应用的组件按需加载、鼠标点击加载等等。
    以下面的小demo为例,我们在点击【展示弹窗】按钮是再引入showModal脚本,调用其show()方法显示弹窗。

    <!DOCTYPE html>
    <html lang="en">
      <body>
        <input type="button" value="展示弹窗" onclick="showModal" />
      </body>
      <script>
        function showModal() {
          import('./modal.js').then((Modal) => {
            Modal.show();
          });
        }
      </script>
    </html>
    // modal.js
    export default {
      show() {
        // do what
      }
    }

    3. BigInt

    介绍

    javascript的number类型的值超出安全范围[-2^53, 2^53 - 1]后,计算的精度就不一定保证准确了。相信很多人遇到过,服务端在数据库的ID或某个字段使用Long类型,导致传到前端后由于值超出js的number安全范围,导致拿不到正确数据的情形。

    ES2020中新增了BigInt这种基本类型,它可以超出javascript中number类型的安全范围的限制,表示任意大小的整数。

    使用

    我们可以通过把 n 加到数字后面或者调用 BigInt() 构造函数的方式,来创建BigInt类型的值:

    const bigIntNum1 = 9007199254740991n; // 9007199254740991n
    const bigIntNum2 = BigInt(9007199254740991); // 9007199254740991n
    const bigIntNum3 = BigInt('9007199254740991'); // 9007199254740991n

    我们可以像number类型一样,使用+-*/等符号对BigInt类型的值进行运算:

    console.log(bigIntNum1 + bigIntNum2); // 9007199254741657n
    console.log(bigIntNum1 * bigIntNum2); // 5998794703657500006n
    console.log(bigIntNum2 / bigIntNum3); // 95n (BigInt类型的值之间做除法除不断时,会保留BigInt类型的整数部分)
    

    与number类型比较:

    3n === 3 // false
    3n == 3 // true
    3n < 4 // true

    4. Promise.allSettled

    介绍

    Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

    使用

    Promise.allSettled()看上去和Promise.all()有些相似,Promise.all()如果其中一个Promise请求失败了,就会抛出错误,而Promise.allSettled()会等待所有的Promise完成。

    一般当我们有多个彼此不依赖的异步任务成功完成时,或者我们总是想知道每个promise的结果时,通常使用Promise.allSettled() :

    const promise1 = Promise.resolve('success');
    const promise2 = new Promise((resolve, reject) => setTimeout(reject, 500, 'fail'));
    
    Promise.allSettled([promise1, promise2]).then((results) => console.log(results));
    // [{ status: 'fulfilled', value: 'success' }, { status: 'rejected', reason: 'fail' }]
    

    5. globalThis

    介绍

    ES2020引入了globalThis,无论在什么环境下使用globalThis,它总是能成功指向全局对象。

    使用

    我们都知道,javascript可以运行于多种环境,例如浏览器、nodejs、Web Workers等等。每个环境下都拥有自己的全局对象和访问它的方法,浏览器中的全局对象是window,而在node中没有window对象,全局对象是global。

    因此,我们如果使用全局对象来设置相关变量,在不同的运行环境下想要移植是比较困难的,使用globalThis,可以有效解决这一问题。这对我们构建例如ssr应用,不确定当前环境时,是一种福音。

    
    // node环境
    console.log(globalThis === global); // true
    
    // 浏览器环境
    console.log(globalThis === window); // true
    
    // node和浏览器下都可以设置全局的setTimeout
    globalThis.setTimeout(() => {
      console.log('this is global');
    }, 1000);

    6. for…in遍历顺序

    介绍

    ES2020中,官方指定了for…in遍历顺序的规范。(之前各个浏览器引擎都有自己的for…in实现,所以对某些变量,例如对象的遍历顺序可能不一致)
    以后,各个浏览器引擎都会遵循ECMA指定的遍历顺序,保证各个引擎的遍历结果一致。

    使用

    以一个对象为例, 会先遍历出整数属性,按照升序的顺序遍历,然后其他属性按照创建时候的顺序遍历出来。

    
    let obj = {
      x2: 'x2',
      x1: 'x1',
      '3': '3',
      '1': '1',
      '2': '2',
      '3XX': '3XX',
    };
    for (let key in obj) {
      console.log(obj[key]);
    }
    // 1   
    // 2   
    // 3   
    // x2   
    // x1   
    // 3XX
    

    7. 可选链 ?.

    介绍

    如果运算符左侧的操作数是多少?.求值为undefined或null,则表达式的求值为undefined。否则,将正常触发目标属性访问,方法或函数调用。

    我们在寻找树状结构深处的属性值时,通常必须检查中间节点是否存在。同样,许多API返回一个对象或null / undefined,我们仅在结果不为null时才希望从结果中提取属性,可选链就是用来便于解决这些情景的。

    使用

    可选链的位置可能出现在三个地方:

    obj?.prop       // 可选的静态属性访问
    obj?.[expr]     // 可选的动态属性访问
    func?.(...args) // 可选函数或方法调用
    

    之前我们调用对象中间属性时,一般需要通过短路来防止空的基值,否则会抛出TypeError异常,现在我们可以使用?.防止异常抛出:

    let data;
    console.log(data.userInfo.name); // TypeError: Cannot read property 'name' of undefined
    
    // before
    console.log(data && data.userInfo && data.userInfo.name); // undefined
    
    // now
    console.log(data?.userInfo?.name); // undefined

    同样,我们可以使用可选链安全地访问深层对象属性:

    let data = {
      userInfo: {
        name: 'zlx',
        age: 22,
      },
      time: 1570000000000,
    };
    
    console.log(data?.userInfo?.name);  // zlx

    8. 空位合并运算符??

    介绍

    如果??运算符左侧的表达式求值为undefined或null,则返回其右侧的值;否则,返回其左侧的值。

    使用

    乍一看??和||很相似,没错它俩就是相似,但还是有区别的。 例如,当我们请求一个接口,接口数据获取失败时要给其赋予默认值。如果数据获取失败,那么这时候使用??或者||都可以:

    let response = null;
    const defaultGirlFriendsNum = response?.userInfo?.girl_friends_num || 1; // return 1
    const defaultGirlFriendsNum = response?.userInfo?.girl_friends_num ?? 1; // return 1

    但如果数据获取成功了,那么||会使defaultGirlFriendsNum结果变为1。也就是说,当我们期待某个变量值为undefined或null时,才为其赋予默认值,我们应该使用??。当我们期待某个变量只要符合variable == false就为其赋予默认值,我们应该使用||。

    let response = {
      userInfo: {
        name: '',
        age: 22,
        girl_friends_num: 0,
      },
    };
    const defaultGirlFriendsNum = response?.userInfo?.girl_friends_num || 1; // return 1
    const defaultGirlFriendsNum = response?.userInfo?.girl_friends_num ?? 1; // return 0

    9. import.meta

    介绍

    import.meta是一个给JavaScript模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的URL。这个对象可以扩展,并且它的属性都是可写,可配置和可枚举的。

    使用

    我们使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径),这时候就可以使用import.meta了。

    <!DOCTYPE html>
    <html lang="en">
      <body></body>
      <script type="module" src="my-module.js"></script>
    </html>
    // my-module.js
    console.log(import.meta); // { url: "file:///home/user/my-module.mjs" }

    ES2021

    天下武功,无坚不摧,唯快不破。看完了ES2020,让我们领先别人一步,看看ES2021这些已经完成了提案的新特性吧。

    1. String.prototype.replaceAll

    介绍

    replaceAll()方法返回一个新字符串,新字符串所有满足pattern的部分都已被replacement替换。pattern可以是一个字符串或一个RegExp,replacement可以是一个字符串或一个在每次匹配被调用的函数。

    • 语法:const newStr = str.replaceAll(regexp|substr, newSubstr|func)
    • 参数regexp: 一个带有全局匹配模式g的RegExp对象(如不使用g则会抛出TypeError)
    • 参数substr: 要由newSubstr替换的字符串。
    • 参数newSubstr: 用于替换regexp或substr的新字符串。
    • 参数func: 一个函数,用于创建新的子字符串,用于替换给定regexp或substr的匹配项。
    • 返回值: 一个新字符串,模式的所有pattern都被replacement替换。

    使用

    与matchAll()一样,replace也是可以替换replace搭配全局正则匹配的用法,便利了我们的操作。
    不过相比于matchAll(),我认为replaceAll()的用途更广泛一些,毕竟全局替换的场景比较常见,我们可以使用它来全局替换文本:

    const str = `I have a pen,I have an apple. Apple-pen! I have a pen,I have pineapple. Pineapple-pen!`;
    const newStr = str.replaceAll('apple', 'banana');
    console.log(newStr);
    // I have a pen,I have an banana. Apple-pen! I have a pen,I have pinebanana. Pinebanana-pen!

    2.Promise.any

    介绍

    Promise.any()接收一个Promise可迭代对象,只要其中的一个promise成功,就返回那个已经成功的promise。如果可迭代对象中没有一个promise成功(即所有的promises都失败/拒绝),就返回一个失败的promise和AggregateError类型的实例,它是Error的一个子类,用于把单一的错误集合在一起。

    它不像 Promise.all()会返回一组完成值那样resolved values,我们只能得到一个成功值(假设至少有一个 promise 完成。当我们只需要一个promise成功,而不关心是哪一个成功时此方法很有用的。

    同时, 它也不像 Promise.race()总是返回第一个结果值(resolved/reject)那样,这个方法返回的是第一个成功的值。这个方法将会忽略掉所有被拒绝的promise,直到第一个promise成功。


    使用

    Promise.any()接收的可迭代promise对象中,只要有一个promise成功,就返回第一个成功的promise:

    const promise1 = new Promise((resolve, reject) => {
      reject('我promise1失败了');
    });
    
    const promise2 = new Promise((resolve, reject) => {
      setTimeout(resolve, 500, '我promise2成功了');
    });
    
    const promise3 = new Promise((resolve, reject) => {
      setTimeout(resolve, 100, '我promise3成功了');
    });
    
    Promise.any([promise1, promise2, promise3]).then((value) => {
      console.log(value); // 我promise3成功了
    });

    如果没有fulfilled(成功的) promise,Promise.any()返回AggregateError错误:

    const promise1 = new Promise((resolve, reject) => {
      reject('我promise1失败了');
    });
    
    const promise2 = new Promise((resolve, reject) => {
      reject('我promise2失败了');
    });
    
    const promise3 = new Promise((resolve, reject) => {
      reject('我promise3失败了');
    });
    
    Promise.any([promise1, promise2, promise3]).then((value) => {
      console.log(value); // Uncaught (in promise) AggregateError: All promises were rejected
    });

    3. WeakRef

    介绍

    WeakRef对象包含对对象的弱引用,这个弱引用被称为该WeakRef对象的target或者是referent。对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为。

    而当我们使用const, let, var等关键字创建一个变量时,垃圾收集器(GC)将永远不会从内存中删除该变量,只要它的引用仍然是可访问的,这些都是强引用。

    WeakRef实例有一个方法deref,它返回引用的原始对象,如果原始对象被回收,则返回undefined。

    使用

    弱引用的主要用途是实现包含大型对象的缓存或映射,在这种情况下,不希望仅因为大型对象出现在缓存或映射中而使其保持活动状态。

    一般情况下,我们很少会有使用弱引用的场景,而且ECMA也建议我们最好尽量避免使用它(不同引擎的GC回收逻辑不同,使用不当很可能出错)。

    下面是一个示例,假设我们有许多大的二进制图像对象(用arraybuffer表示,我们可能希望将一个名称与每个图像相关联。我们可以使用一个值为WeakRef对象的映射,它指向ArrayBuffer。这样,我们就避免了将这些ArrayBuffer对象保存在内存中的时间比其他情况下长:如果图像对象仍然存在,这是一种查找图像对象的方法,但是如果它被垃圾回收,我们将重新生成它:

    function makeWeakCached(f) {
      const cache = new Map();
      return key => {
        const ref = cache.get(key);
        if (ref) {
          const cached = ref.deref();
          if (cached !== undefined) return cached;
        }
    
        const fresh = f(key);
        cache.set(key, new WeakRef(fresh));
        return fresh;
      };
    }
    
    var getImageCached = makeWeakCached(getImage);

    4. 逻辑赋值

    介绍

    新增了三个逻辑赋值,&&=、||=、??=。这个很好理解,和+=、*=这些赋值是一样的,x &&= y就相当于x = x && y,其他两个同理。

    使用

    使用上没有什么可多说的:

    let a = 1,
      b = 2,
      c = 0,
      d = undefined;
    a &&= b;
    b ||= c;
    d ??= c;
    console.log(a, b, d); // 2 2 0

    5.数值分隔符

    介绍

    此功能使开发人员可以通过在数字组之间创建可视分隔来使其数字文字更具可读性。大数字文字很难使人眼快速解析,尤其是当数字重复很长时,所以我们通过数值分隔符_来对大数字进行分隔。

    我们都知道,在银行、支付宝等app的金额显示时,都会使用,对金额进行分隔,例如 10,000,000.00元。现在我们通过_,也可以快速解析数值了!

    使用

    数值分隔符_在整数、小数指数部分通通适用,并且我们可以在任意的地方进行分隔:

    let num = 12_345_000; // 12345000
    let amount = 1_2345_000; // 12345000
    num === amount; // true
    let num2 = 0.000_001; // 0.000001
    let num3 = 1e1_0; // 10000000000(10^10)

    小编是一个有着7年工作经验的前端工程师,关于web前端,自己有做材料的整合,一个完整学习web前端的学习路线,学习材料和工具。需要的伙伴可以私信我,发送“前端”等3秒后就可以获取领取地址,免费送给大家。


    作者:梨香
    链接:https://juejin.cn/post/6896756812600016910
    来源:掘金

    发表评论:

    控制面板
    您好,欢迎到访网站!
      查看权限
    网站分类
    最新留言
      友情链接