关于es6-es11中新特性的一些想法

参考规范:https://tc39.es/ecma262/。

Es6(es2015)

概述

  1. 增强功能 包括模块、类声明、词法块作用域、迭代器和生成器,异步编程,解构模式以及适当的尾部调用。
  2. 内置库的扩展 支持数据抽象,包括Map, 集合Set, 以及二进制数值的数组。对象以及数组功能的扩展。

第六版的重点开发工作始于2009年,并于2015年6月通过,是经过了15年的努力的结果。作为常规版本存在,对增量语言和库增强功能提供了基础。

因为Es6已经是大家耳熟能详的,所以这里就不花篇幅了描述了,详细见:es6教程

Es7(es2016)

在es6提出后,TC-39决议认为耗时过长不利于可持续发展,于是将发布周期转换为每年发布一个版本,以确保新的语言特性能够更快的发展。

  1. Array.prototype.includes
  2. 新的指数运算符 **
  3. 新的语法错误

因为每年都会出一个版本,所以改动不会像es6一样那么大,一个个来看一下:

  1. Array.prototype.includes

    规定: 其实在es6中已经有 Array.prototype.includes 了, 只不过仍未完善,所以出现在了es7的版本中。

    两个参数:第一个要搜索的值,第二个开始搜索的索引位置(可选,默认为0)。

    返回值:找到返回true,找不到返回false。

    let nums = [7, 8, 9];
    
    // test
    console.log(nums.includes(7)); // true
    console.log(nums.includes(10)); // false
    console.log(nums.includes(7, 1)); // false
    

原因: 我们都知道在es6中存在多个类似功能的函数,比如:

findIndex(function)find(function)indexOf(value)

那么为什么还会有includes呢?

  • 首先,改名风波:

    Array.prototype.includes 的前身提案名是Array.prototype.contains ,但是没有正式推出,于是很多网站自行hack了。

    假如规范实现contains, 会出现contains 无法被for..in读出来(因为js中所有原生提供的方法属性都是不可枚举的,只能通过属性的属性描述符获取getOwnPropertyDescriptor(Array.prototype, 'indexOf')),而之前自行hack的contains的是可以被读出来的。

    会出现代码没变动,在新规范推出后产生bug的情况。因此初稿阶段,考虑新规范会使得许多现有网站出问题,所以改名includes

  • 其次,虽然我们用indexOf可以模拟includes的行为,但出于语义描述不清楚的原因,所以要改。

    includes 表达了 是否包含该项的意思。

    indexOf 表达了查找数组中第一次出现对应元素的索引是什么,在针对返回的索引进一步处理的意思。

    has 为什么不叫has呢,因为has一般表示key中是否有(Map.prototype.has),includes表示value中是否有。

  • 那么他的判断是否相等的方式是什么呢?答案是:SameValueZero ,es6中有4种相等算法

    • 松散相等比较:实现接口 ==

    • 严格相等比较:实现接口===,使用Array.prototype.indexOfArray.prototype.lastIndexOfcase-matching

    • SameValue: 实现接口Object.is()

    • SameValueZero: 实现接口借用了MapSet的方法。

      // 注意
      // SameValue 中 +0 和 -0 是false
      Object.is(-0, +0); // false
      // SameValueZero 中 +0 和 -0 是true 和Set的判断很像不是
      const s = new Set();
      s.add(0);
      s.has(-0); // true
      

思考: 从探究整个提案的流程,可以看出一个新特性的出现需要考虑很多方面的问题。

类比之下,我们的项目在修改的时候 ,也需要考虑是否会对其他的东西产生影响,以及当前的方案是否最优。

思考一下,我们现在有用到查询数组中是否包含某个元素的情况,我猜测大多数都是用的indexOf, 不过既然更优的api,官方已经提出来了 ,我们是否可以考虑换一下,毕竟可以减少码量,同时语义更加清晰不是。

let arr = [1, 2, 3];

if (arr.indexOf(1) > -1) {...}
// ==> 等价于
if (arr.includes(1)) {...}
  1. 语法变化:新的指数运算符**

    规定: 虽然Math.pow() 是已经支持了指数运算的方法,但是正式的运算符** 看起来会会让开发者更容易阅读和理解。** 的左边是基数,右边是指数。 如下:

    let result = 10 ** 2;
    
    console.log(result); // 100
    console.log(result === Math.pow(10, 2)) // true
    
    // 当然 支持 **= 即 a = a**b <==> a **= b
    
    

原因: 没错 ,就是为了更容易书写和阅读。

思考: 官方都时刻关心,可见容易书写和阅读多么的重要,我以为我们的代码可以更加的简洁和易读。推荐一个代码规范地址:https://github.com/ryanmcdermott/clean-code-javascript

  1. 新的语法错误

    规定:在参数被解构或者有默认参数的函数中,禁止使用严格模式"use strict"指令。否则会抛出错误

    // 正确
    function good(first, second) { return first}
    
    // 抛出错误 原因:有默认参数并且使用了"use strict"
    function notGood(first, second = first) {
        "use strict";
        return first;
    }
    
    // 抛出错误 原因:参数被解构并且使用了"use strict"
    function notGood2({first, second}) {
        "use strict";
        return first;
    }
    

原因:实现运行在严格模式下的参数非常困难,比如:

function foo(first = this) {
 "use strict";
    return first
}

在严格模式下first被认为是undefined,可是参数的默认值也可以是函数(通过apply , call指定上下文的调用方式),这就导致大多数的JavaScript引擎均不实现此功能,而是将其等同于全局对象。

思考:当我们遇到一个不好实现或者解决的问题的时候,做个智者,未尝不是一种明智的选择(绕过它/抛异常)。

Es8(es2017)

  1. Object、String的静态方法
  2. 在函数参数列表中尾随逗号并调用
  3. 异步功能、共享内存和原子

具体如下:

  1. Object、String的静态方法

    规定: 扩展了 Object和 String的功能

    • Object.values/Object.entries/Object.getOwnPropertyDescriptors()

      • Object.values接受一个对象并返回所有值构成的数组,其顺序和for … in 的顺序相同。

      • Object.entries接受一个对象并返回数组,每个元素都是一个包含两个元素的数组。第一个是对象的key, 第二个是对象的value, 其顺序和for … in 的顺序相同。

      • Object.getOwnPropertyDescriptors 获取属性描述符,即对象所有自己的属性描述符。包括value/writable/enumerable/configurable

        let obj = {
            one: 'hh',
            second: 'dd',
            thrid: 'zz'
        }
        Object.values(obj); // ["hh", "dd", "zz"]
        Object.entries(obj); // [["one", "hh"], ["second", "dd"], ["thrid", "zz"]]
        Object.getOwnPropertyDescriptor(obj, 'one'); // {value: "one", writable: true, enumerable: true, configurable: true}
        
    • String padding 用另一个字符串填充填充当前字符串,直到结果字符串达到给定的长度为止,第二个参数默认为空格。

      • String.padStart(长度, 填充字符串|' ') 从开头填充, 填充字符从左往右填充

      • String.padEnd(长度, 填充字符串|' ') 从末尾向后填充,填充字符从左往右填充

        'abc'.padStart(10); // "       abc" 
        'abc'.padStart(10, 'foo'); // "foofoofabc"
        'abc'.padStart(6, '1234565'); // "123abc"
        'abc'.padStart(8, '0'); // '00000abc'
        'abc'.padStart(1); // 'abc'
        
        'abc'.padEnd(10); // "abc       " 
        'abc'.padEnd(10, 'foo');// "abcfoofoof"
        'abc'.padEnd(6, '123456'); // 'abc123'
        'abc'.padEnd(1); //  'abc'
        

思考:

Object.valuesObject.keys 对应使用,

在对一些私密信息进行处理的时候,需要将一些数字进行加*隐藏,之前的写法可能是进行遍历数组逐个替换,现在有了padStart,可以这样试一下:

// 对身份进行隐藏显示
const fullNumber = '320321197803069527';

// old
const hideLen = fullNumber.length - 4;
let maskedNumber = ''
for (let i = 0; i < fullNumber.length; i++) {
    if (i < hideLen) {
     maskedNumber += '*';
    }else {
        maskedNumber += fullNumber[i]
    }
}
console.log(maskedNumber); // **************9527

// new 
const last4Digits = fullNumber.slice(-4);
const maskedNumberNew = last4Digits.padStart(fullNumber.length, '*'); 
console.log(maskedNumberNew); // **************9527 很简洁,思路很清晰不是

还有一种情形就是 给定字符串长度,超出的以省略号表示 (这里不考虑超出指定长度省略号表示),试试看?

const str = 'Tom Jackson';
// 期望输出: 'Tom...'
  1. 在函数参数列表中尾随逗号并调用

    规定:在函数的参数列表中,最后一个参数以逗号结尾,如:

    function myFun(
     p1, 
        p2,
    ) {}
    myFun(
        'param1',
        'param2',
    )
    

    原因:

    1. 如果最后一项改变了位置,则不必添加和删除逗号。
    2. 它可以帮助版本控制系统跟踪实际更改的内容。
  1. 异步功能

    规定: 异步功能,指的是定义一个异步函数,也就是一个返回异步对象的函数,返回Promise或者函数上加async标记。

原因:当异步功能有多个的时候,在一个函数中就会难以维护,违背了单一职责原则, 就需要拆分出不同的方法,因此就有了异步功能。

思考: 异步功能很好的将业务封装在了功能函数之中,调用者只需要关心成功和失败两种状态(resolve和reject),这样会使得代码更具有可读性。

这种场景目前用的最多的是在接口函数的编写中,它让我们不用太过关心接口的发送以及对返回数据的处理,只需要使用即可。

  1. 共享内存和原子

    规定: 引入新的构造函数 SharedArrayBuffer 和 具有辅助函数的命名空间对象 Atomics, 用原子操作进行通信的程序,即使在并行CPU上也能确保定义良好的执行顺序。全局变量 Atomics,它的方法具有三个主要用例。

    • 同步化
    • 等待通知
    • 原子操作
    Atomics.store() // 写入
    Atomics.load() // 读取
    Atomics.wait() // 等待
    Atomics.wake() // 唤醒
    

    原因: 虽然用到的地方很少,但是作为一门语言,对于并行操作还是要支持的。Web workers 将任务并行引入了 JavaScript 。还记得PWA吗?它的核心就在与本地开启了一个service worker,并对数据进行了缓存以及代理。

    思考: 要实现并行,需要满足两点:一个是数据的并行,一个是代码的并行。这里SharedArrayBuffer作为共享阵列缓冲区是更高抽象的基本构建块。 它允许多个workers和主线程之间共享SharedArrayBuffer对象的字节。同时Atomics的 方法可以用来与其他 workers 进行同步。

    详情可以了解https://www.html.cn/archives/7724

Es9(es2018)

  1. 解除模板字面量限制
  2. 正则表达式相关
  3. 对象解构中的rest操作符(...)/对象字面量中的spread操作符(...)
  4. Promise.prototype.finally 和 异步的迭代

具体如下:

  1. 解除模版字面量的限制

    规定: 对于模版字面量中,如果存在非法转义字符 ,则将模版字面量设置为undefined。不过可以通过.raw访问原始值。

    function tag(strs) {
        console.log(strs[0] === undefined); 
        console.log(strs.raw[0] === '\\unicode and \\u{55}')
    }
    tag`\unicode and \u{55}`; // true true
    
    const badSequence = `bad escape sequence: \unicode`; // Uncaught SyntaxError: Invalid Unicode escape sequence
    

    原因:解决模版字面量中存在非法转义字符报错的问题。

    思考:在处理字符串的时候,我们多是需要注意处理需要进行转义的字符,此时有了这个特性 ,使的我们可以封装个公共的方法,放心的使用模版字面量,没必要进行而外的处理了。

  1. 正则表达式相关

    • . 的使用

      规定: es9支持了/s模式下,. 可以匹配换行符。

      console.log(/./s.test("\n")); //  true
      console.log(/./s.test("\r")); //  true
      

      原因:正则中.看似可以匹配任何字符,但换行符除外,当时大家用[\w\W]替代了.。这个终究是个缺陷。于是增加了在 /s 模式下,. => [\w\W]

  • 对捕获组进行命名:

    规定: 使用捕获组需要通过指定捕获组名称来进行引用,每个名称是唯一的,避免冲突。语法:?

    const regExp = /(?\d{4})-(?\d{2})-(?\d{2})/u;
    const result = regExp.exec('2020-10-21');
    console.log(result.groups.year === '2020'); //true => result[1] === '2020'
    console.log(result.groups.month === '10'); //true => result[2] === '10'
    console.log(result.groups.day === '21');//true => result[3] === '21'
    // 其中 result[0] === '2020-10-21' 
    
    // 命名捕获组这块 建议结合解构字面量一起使用 会更加清晰
    const { groups: { year, month, day } } = regExp.exec('2020-10-21');
    

    原因:对捕获组命名可以让调用更加清晰。

  • 支持了后行断言:

    规定: es9之前只支持先行断言,现在支持了后行断言。

    正向后行断言:(?<=...) 表示之前的字符串能匹配

    const re = /(?<=\d{3}) meters/; // 表示 meters 之前 匹配三个数字
    console.log(re.test('10 meters')); // false
    console.log(re.test('100 meters')); // true
    

    负向后行断言:(?之前的字符串不能匹配

    const re = /(?

    原因: 弥补之前只能支持先行断言的缺陷。

    思考: 正则中完整的断言定义:正/负向断言 与 先/后行断言 的笛卡尔积组合(互相组合得到4种结果)。

    补充一下正向先行断言负向先行断言

    // 正向先行断言
    const re = /meters(?= 10)/;
    console.log(re.test('meters 10')); // true
    console.log(re.test('meters 5')); // false
    
    // 负向先行断言
    const re2 = /meters(?! 10)/; 
    console.log(re.test('meters 10')); // false
    console.log(re.test('meters 5')); // true
    
  • Unicode 属性转义

    规定:正则支持了更加强大的unicode匹配方式。在/u(修饰符u指unicode)模式下,通过\p{}大括号内提及unicode字符属性来匹配字符。

    可以用\p{Number}匹配所有数字

    可以用\p{Alphabetic}匹配所有Alphabetic元素,包括汉字,字母等

    可以用\p{White_Space}匹配空格、制表符、换行符。

    等等。

    const regex = /^\p{Number}+$/u;
    regex.test("²³¹¼½¾"); // true
    regex.test("㉛㉜㉝"); // true
    regex.test("ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ"); // true
    
    const str = "漢";
    console.log(/\p{Alphabetic}/u.test(str)); //  true
    // the \w shorthand cannot match 漢
    console.log(/\w/u.test(str)); // false
    
    /^\p{White_Space}+$/u.test('\n \r \t'); // true
    

    原因: 创建支持unicode的正则表达式不再麻烦,更加易读。使用Unicode属性转义符的代码将保持“最新”状态:每当Unicode标准得到更新时,ECMAScript引擎就会更新其数据。

  1. 对象解构中的rest操作符(...)/对象字面量中的spread操作符(...)

    // 对象解构中的rest操作符(...)
    const {one, two, ...others} = { one: 'one', two: 'two', three: 'three', four: 'four'};
    console.log(others); // { three: 'three', four: 'four' }
     
    // 对象字面量中的spread操作符(...)
    const nums = {one, two, ...others};
    console.log(nums);// { one: 'one', two: 'two', three: 'three', four: 'four'};
    

    **思考: **需要注意的是 在使用对象字面量的时候 如果有key重复,那么对象中后面的key的值会覆盖前面的。

  2. Promise.prototype.finally 和 异步的迭代

    • Promise.prototype.finally

      规定:一旦执行了Promise的resolve回调或者reject回调,最终都会执行finally回调

      思考:总有一些相同的处理需要在resolve回调或者reject回调后处理,比如:资源的回收或者网络请求时loading的状态修改等。 平时我们会在resolve回调或者reject回调中都添加或者提取一个公共方法。现在会让我们更省力和美观。

    • 异步的迭代

      规定:这个是for...of的一种变体,他允许在由异步组成的可迭代对象上进行迭代。

      async function asyncFun() {
          const promises = [
              fetch('f1.json'),
             fetch('f2.json'),
              fetch('f3.json'),
          ];
          // 正常的迭代
          for (const promiseItem of promises) {
              console.log(item); // =>  输出 promise
         }
          
          // 正常的迭代
          for await (const promiseItem of promises) {
              console.log(item); // =>  输出 resolved response
         }
      }
      

      思考: 适合的场景会是 多个异步同步执行,并对每个结果有统一的处理方式。比如:对文件的处理。

Es10(es2019)

  1. Array.prototype.flat 和 Array.prototype.flatMap

  2. Object.fromEntries

  3. String.prototype.trimStart, String.prototype.trimEnd

  4. Symbol的描述访问器

  5. try { } catch {} 可省略catch参数的写法

  6. 对行分隔符\u2028和段落分隔符\u2029的支持,

  7. JSON.stringify对特殊字符的转义处理

  8. Array.prototype.sort

  9. Function.prototype.toString

具体如下:

  1. Array.prototype.flat 和 Array.prototype.flatMap

    规定:

    Array.prototype.flat(Number) ,降纬度数组,具体下降多少维度视参数而定,知道无法继续降维为止。默认为1

    Array.prototype.flatMap(Function) ,flat和map功能的组合,接受一个函数, 返回一个处理后的降维了1个的数组。

    const fruits = [['apple', 'banana'], ['苹果', '香蕉']];
    console.log(fruits.flat()); // ['apple', 'banana', '苹果', '香蕉']
    
    const fruits2 = [['apple'], ['banana'], ['苹果'], ['香蕉']];
    const mappedAndFlatten = fruits2.flatMap((fruit, index) => [fruit + '_fruit']);
    console.log(mappedAndFlatten); // ["apple_fruit", "banana_fruit", "苹果_fruit", "香蕉_fruit"]
    

    思考:

    对比我们平常使用的解构方法, 需要递归遍历:

    function reduceDimension(arr){
        let ret = [];
        let targetArr = function(arr){
            arr.forEach(function(item){
                item instanceof Array ? targetArr(item) : ret.push(item);
            });
        }
        targetArr(arr);
        return ret;
    }
    
    // 等同于 arr.flat(Infinity)
    

    发现 flat(Infinity) 无疑更简洁(Infinity 作为数值中的无限大,可以保证数组能够完全解构)。

  1. Object.fromEntries

    规定: 将 由键值对组成的数组转换成一个对象, 与 Object.entries功能相反。

    const fruits = { apple: '苹果', banana: '香蕉', orange: '橘子'};
    const entries = Object.entries(fruits);// [["apple", "苹果"],["banana", "香蕉"] ,["orange", "橘子"]]
    const fromEntries = Object.fromEntries(entries); // { apple: '苹果', banana: '香蕉', orange: '橘子'}
    

    思考: 这是针对这种特定数据结构的格式化 函数。对于一个对象如果需要同时修改对象的key-value, 直接修改不方便,我们可以先将一个对象通过Object.entries转换成数组,进行修改,完成后通过Object.fromEntries转换回来。

  1. String.prototype.trimStart, String.prototype.trimEnd

    • trimStart :去除字符串前面的空格

    • trimEnd: 去除字符串后面的空格

  1. Symbol的描述访问器

    规定: 通过 Symbol.prototype.description可以获取定义Symbol时的具体数值

    const OPEN = Symbol('STATUS_OPEN');
    // old 可以通过 toString() 来获取描述信息
    console.log(OPEN.toString()); // "Symbol('STATUS_OPEN')"
    
    // new 通过 description属性获取
    console.log(OPEN.description); // "STATUS_OPEN"
    

    思考: Symbol的出现 让我们在常量的使用中大量使用,不过有时侯需要获取它的描述值来做一些判断。

    const BANNER = {
        LEFT: Symbol('left'),
        RIGHT: Symbol('right'),
    }
    
    // 使用时候
    if (BANNER.LEFT.description === 'left') {
        // do something
    }
    
  1. try { } catch {} 可省略catch参数的写法

    规定: catch后面的括号以及参数在不用的时候可以省略

    try { } catch(err) {}
    try { } catch {}  // 不会报错
    

    思考:有些时候我们不需要对catch 的err 数据进行操作,这个时候如果传递了,但是未使用,那么eslint 可能会警告我们代码中存在可优化。本着不用不写的原则,省略无疑是很好的一种方式。

  1. 对行分隔符\u2028和段落分隔符\u2029的支持

    规定: es10之前 '\u2028''\u2020'这种写法会报错,现在不会啦。

  1. JSON.stringify对特殊字符的转义处理

    规定: JSON.stringify 在处理\uD800\DFFF之间的字符,如果找不到对应的,直接返回转义后的字符。

    JSON.stringify('\uDF06\uD834'); // '"\\udf06\\ud834"'
    
  1. Array.prototype.sort

    规定: es10之前 V8对于包含10个以上的数组排序时采用了不稳定的快速排序算法,这个算法可能会改变原来数组里面相同数值的元素的先后关系。 通过采用原地排序保证数组中相同数值元素的先后关系。避免了顺序被改变的问题。

  1. Function.prototype.toString

    规定:返回一个表示函数源代码的字符串,包括格式和注释,比较精确。

    function add(num1, num2) {
        /* add */
        return num1 + num2;
    }
    
    add.toString(); 
    // "function add(num1, num2) {
    //    /* add */
    //    return num1 + num2;
    // }" 
    

Es11(es2020)

  1. String.prototype.matchAll

  2. Promise.allSettled

  3. export * as ns from 'module'

  4. for-in 对于迭代对象顺序的统一

  5. 链合并运算符 ?. 以及 Null判断运算符 ??

  6. 新增原始类型 BigInt ,可以表示任意精度的整数

  7. import 动态导入

  8. import.meta

  9. 标准化的全局对象

具体如下:

  1. String.prototype.matchAll

    规定: 返回所有与正则表达式相匹配字符串的结果的迭代器,包括捕获组。相比于match, 它提供的信息更全。

    const regexp = /t(e)(st(\d?))/g;
    const str = 'test1test2';
    
    // 曾经 我们通过 while 和 exec配合来获取比较全的匹配信息
    let result = null;
    while((result = regexp.exec(str)) !== null) {
     console.log(result);
    }
    // >  ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]   
    // >  ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
    
    // 现在 我们用matchAll 就好了
    const array = [...str.matchAll(regexp)];
    for (result of array) {
        console.log(result);
    }
    // >  ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]   
    // >  ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
    
  1. Promise.allSettled

    规定:对比Promise.all 方法,Promise.allSettled 当每个Promise 无论失败还是成功都会走回调函数。

    const promiseArr = [
        Promise.resolve(100),
        Promise.reject(null),
        Promise.reject(new Error('Oh No')),
    ]
    
    Promise.all(promiseArr).then(results => {
        // 不会执行
        console.log('All Promise Settled', results);
    });
    
    Promise.allSettled(promiseArr).then(results => {
        console.log('All Promise Settled', results);
    });
    // [{status: "fulfilled", value: 100},
    // {status: "rejected", reason: null},
    // {status: "rejected", reason: Error: Oh No at :4:20}]
    

    思考:当我们在进行多个接口请求的时候,如果通过Promise.all ,不能保证回调一定会执行,这时候如果有loading状态的,无疑会出现问题,无法在回调中更新。如果我们并不关心接口是否调用成功,这个时候Promise.allSettled 无疑是更合适的。

  1. export * as ns from 'module'

    规定: 在一个文件中引用另外一个文件并导出他的一种缩写。

    export * as utils from './utils.mjs'
    
    // 等价于 ==> 
    import * as utils from './utils.mjs'
    export { utils }
    

    思考: 在react中使用组件的时候,我们会对一个目录下的多个文件 通过index.js导出。好处在于当前文件中并不会真的导入文件对象的引用,比如以上的utils为undefined。

    // index.js old
    import * as Header from './header.jsx'
    import * as Footer from './footer.jsx'
    // ...
    export { Header }
    export { Footer }
    // ...
    
    // index.js new 
    export * as Header from './header.jsx';
    export * as Footer from './footer.jsx';
    export * as Body from './body.jsx';
    export * as Nav from './nav.jsx';
    
  1. for-in 对于迭代对象顺序的统一

    规定:虽然浏览器自己保持了 for in的顺序,但是在es11中进行了标准化。

  1. 链合并运算符 ?. 以及 Null判断运算符 ??

    规定:链合并运算符 避免了调用时左侧的对象是否为null或undefined的判断,Null判断运算符 为属性值为null或undefined时,指定默认值

    const userName = (list && list.info 
                      && list.info.base 
                      && list.info.base.userName
                     ) || 'userName';
    // => 等价于
    const userName = list?.info?.base?.userName ?? 'userName';
    

    思考: ?? 类似于 || ,区别在于:

    • ?? 只当左侧为 null/undefined的时候才会指定右侧默认值

    • || 当左侧所有转换后boolean值为false 的时候会指定右侧默认值(包括 0 )

      在某些情况下 我们并不需要这种结果,因此结合业务考虑使用哪一种。

  1. 新增原始类型 BigInt ,可以表示任意精度的整数

    规定: 顾名思义,就是用来存放Number放不下的数字

    let maxNum = 9007199254740992; // Number的最大值 pow(2, 53) - 1
    console.log(maxNum + 1); // 9007199254740992
    
    maxNum = 9007199254740992n;
    console.log(maxNum + 1n); // 9007199254740993n
    console.log(typeof maxNum === 'bigint'); // true
    console.log(maxNum + 1); // Uncaught TypeError: Cannot mix BigInt and other types 
    

思考: 在进行大数计算的时候,之前需要通过竖式运算,一种从个位往前一个一个相加求和的方式,比较麻烦。现在直接用BigInt就可以了

  1. import 动态导入

    规定: 返回所请求模块的模块名称空间对象的Promise,所以也可以使用async/await

    const modulePath = './file.js';
    import(modulePath)
     .then((module) => {
         // do something
     });
    // or
    (async function() {
        const modulePath = './file.js';
        const module = await import(modulePath);
        // do somthing
    })();
    

    思考:import 的动态导入支持 一方面可以让我们按需加载,另一方面的可能会出现滥用的可能,对静态文件分析造成一定程度上的阻碍(比如:tree-shaking 就是依赖于 import的静态导入,如果都做成了动态导入,那么该功能的能力会收到很大的影响)。

  1. import.meta

    规定: 可以通过import.meta获取module的信息

    
    
    // module.js
    console.log(import.meta) // 得到一个对象 包含模块的信息{ url: "file:///home/user/module.js" }
    

    思考:在使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。这是import.meta的用处。

  1. 标准化的全局对象

    规定:全局变量之前在各个平台未统一,需要自行标准化。

    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');
    }
    
    // getGlobal() 等同于 globalThis
    

其他可能的提案

  1. do表达式

  2. 双冒号运算符

  3. 管道运算符

  4. #!命令

具体如下:

  1. do表达式

    规定: 通过在块级作用域前添加do , 让块级作用域可以变为表达式。也就是说可以返回值。do 也提供了单独的作用域。do表达式的逻辑:封装什么就会返回什么

    let x = do {
      let t = f();
      t * t + 1;
    };
    // x的值等于 t*t+1的结果
    
    // 等同于 <表达式>
    do { <表达式>; }
    // 等同于 <语句>
    do { <语句> }
    

    思考:我们在写jsx的时候,do很好用。以下避免了三元运算符的使用,同时代码意图比较清晰。

    return (
      
    )
    
  1. 双冒号运算符

    规定:即“函数绑定”(function bind)运算符,用来取代callapplybind调用。左边是一个对象,右边是一个函数

    foo::bar;
    // 等同于
    bar.bind(foo);
    
    foo::bar(...arguments);
    // 等同于
    bar.apply(foo, arguments)    
    
  2. 管道运算符

    规定:JavaScript 的管道是一个运算符,写作|>。它的左边是一个表达式,右边是一个函数。支持await。

    x |> f
    // 等同于
    f(x)
    
    x |> await f
    // 等同于
    await f(x)
    

    思考:管道运算符最大的好处,就是可以把嵌套的函数,写成从左到右的链式表达式。

    function doubleSay (str) {
      return str + ", " + str;
    }
    function capitalize (str) {
      return str[0].toUpperCase() + str.substring(1);
    }
    function exclaim (str) {
      return str + '!';
    }
    
    let msg = 'hello';
    const result = msg |> doubleSay |> capitalize |> exclaim;
    // 等同于
    const resultNew = exclaim(capitalize(doubleSay(msg)));
    
  1. #!命令

    规定: JavaScript 脚本引入了#!命令,写在脚本文件或者模块文件的第一行。Unix 命令行就可以直接执行脚本。同时对于JavaScript 引擎来说,会把#!理解成注释,忽略掉这一行。

    // 写在脚本文件第一行 hello.js
    #!/usr/bin/env node
    'use strict';
    console.log(1);
    
    # 以前执行脚本的方式
    $ node hello.js
    # 现在执行 的方式
    $ ./hello.js
    

你可能感兴趣的:(关于es6-es11中新特性的一些想法)