ES7 到 ES12 经常使用新语法

2021年09月15日 阅读数:1
这篇文章主要向大家介绍ES7 到 ES12 经常使用新语法,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

cover

众所周知,ECMAScript 的迭代是很快的,想必做为前端开发人员对近几年 ES6 的新语法已经十分熟悉了,可是 ES7 到 ES12 中一些新增的 ECMAScript 提案,可能仍是没有普遍地被开发者所熟知。本文带着你们一块儿来了解一下 2016 到 2021 年新增的一些 ECMAScript 语法以及提案,帮助你们更好地应用于本身的项目中。javascript

TC39 规范介绍:前端

Stage 0(strawman),任何 TC39 的成员均可以提交。java

Stage 1(proposal),进入此阶段意味着这一提案被认为是正式的了,须要对此提案的场景与 API 进行详尽的描述。node

Stage 2(draft),这一阶段的提案若是能最终进入到标准,那么在以后的阶段都不会有太大的变化,由于理论上只接受增量修改。git

Stage 3(candidate),这一阶段的提案只有在遇到了重大问题才会修改,规范文档须要被全面的完成。程序员

Stage 4(finished),这一阶段的提案将会被归入到 ES 每一年发布的规范之中。es6

ES7(ES2016)

一、Array.prototype.includes

概述

includes() 方法用来判断一个数组是否包含一个指定的值,根据状况,若是包含则返回 true,不然返回 falsegithub

该提案目前处于 Stage 4 阶段。web

TC39 直通车正则表达式

动机

使用 ECMAScript 数组时,一般须要肯定数组是否包含某个元素。广泛的方法是:

if (arr.indexOf(el) !== -1) {
  ...
}
复制代码

严格意义来讲,使用 indexOf 来肯定数组中是否包含某个元素是存在问题的:

  • 语义不符,咱们的目的是想知道数组中是否包含某个元素,并非想知道数组中某个元素首次出现的索引。
  • NaN 不生效,[NaN].indexOf(NaN) === -1

语法

arr.includes(valueToFind[, fromIndex])

  • valueToFind:须要查找的元素值。
  • fromIndex:可选的。从 fromIndex 索引处开始查找 valueToFind。若是为负值,则按升序从 arr.length + fromIndex 的索引开始搜(即从末尾开始往前跳 fromIndex 的绝对值个索引,而后日后搜寻)。默认为 0

例子:

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true
复制代码

注意事项

一、若是 fromIndex 大于等于数组的长度,则会返回 false,且该数组不会被搜索。

const arr = ['a', 'b', 'c'];
arr.includes('c', 3);   // false
arr.includes('c', 100); // false
复制代码

二、若是 fromIndex 为负值,计算出的索引将做为开始搜索 valueToFind 的位置。若是计算出的索引小于 0,则整个数组都会被搜索。

// array length is 3
// fromIndex is -100
// computed index is 3 + (-100) = -97

const arr = ['a', 'b', 'c'];

arr.includes('a', -100); // true
arr.includes('b', -100); // true
arr.includes('c', -100); // true
arr.includes('a', -2);   // false
复制代码

三、includes() 方法有意设计为通用方法。它不要求 this 值是数组对象,因此它能够被用于其余类型的对象(好比类数组对象)。下面的例子展现了在函数的 arguments 对象上调用 includes() 方法。

(function() {
  console.log([].includes.call(arguments, 'a')); // true
  console.log([].includes.call(arguments, 'd')); // false
})('a', 'b', 'c');
复制代码

二、Exponentiation Operator

概述

求幂运算符(**)返回将第一个操做数加到第二个操做数的幂的结果。它等效于 Math.pow,不一样之处在于它也接受 BigInt 做为操做数。

该提案目前处于 Stage 3 阶段。

TC39 直通车

语法

Operator: var1 ** var2

求幂运算符是是右结合的: a ** b ** c 等于 a ** (b ** c)

例子:

2 ** 3   // 8
3 ** 2   // 9
3 ** 2.5 // 15.588457268119896
10 ** -1 // 0.1
NaN ** 2 // NaN

2 ** 3 ** 2   // 512
2 ** (3 ** 2) // 512
(2 ** 3) ** 2 // 64

-(2 ** 2) // -4

(-2) ** 2 // 4
复制代码

ES8(ES2017)

一、async 和 await

概述

asyncawait 关键字是基于 Generator 函数的语法糖,使异步代码更易于编写和阅读。经过使用它们,异步代码看起来更像是老式同步代码。

TC39 直通车

动机

简化异步编程写法。

使用 Generator 函数,依次读取两个文件:

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);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

复制代码

上面代码的函数 gen 能够写成 async 函数,就是下面这样:

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
复制代码

一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await

async 函数对 Generator 函数的改进,体如今如下四点:

  • 内置执行器。Generator 函数的执行必须靠执行器,因此才有了 co 模块,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数如出一辙,只要一行。
  • 更好的语义。asyncawait,比起星号(*)和 yield,语义更清楚了。async 表示函数里有异步操做,await 表示紧跟在后面的表达式须要等待结果。
  • 更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,能够是 Promise 对象和原始类型的值(数值、字符串和布尔值等,但这时会自动转成当即 resolvedPromise 对象)。
  • 返回值是 Promise。async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你能够用 then 方法指定下一步的操做。进一步说,async 函数彻底能够看做多个异步操做,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。

语法

async function getData() {
  const res = await api.getTableData(); // await 异步任务
  // do something  
}
复制代码

例子:

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});
复制代码

二、Object.values 和 Object.entries

概述

Object.values() 方法返回一个给定对象自身的全部可枚举属性值的数组,值的顺序与使用 for...in 循环的顺序相同(区别在于 for...in 循环枚举原型链中的属性)。

Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for...in 循环还会枚举原型链中的属性)。

该提案目前处于 Stage 4 阶段。

TC39 直通车

语法

Object.values(obj)

  • obj:被返回可枚举属性值的对象。

Object.entries(obj)

  • obj:能够返回其可枚举属性的键值对的对象。

例子:

Object.values({a1b2c3});  // [1, 2, 3]
Object.keys({a1b2c3});    // [a, b, c]
Object.entries({a1b2c3}); // [["a", 1], ["b", 2], ["c", 3]]
复制代码

三、Object.getOwnPropertyDescriptors

概述

获取一个对象全部自身属性的描述符,若是没有任何自身属性,则返回空对象。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

ECMAScript 中没有一个方法可以简化两个对象之间的拷贝。现在,函数式编程和不可变对象是复杂应用程序的重要组成部分,每一个框架或库都在实现本身的样板文件,以便在组合对象或原型之间正确复制属性。

回到 Object.assign,它大多数状况下会出现一些不但愿出现的行为,由于它的复制方式会吞噬行为:它直接访问属性和符号,而不是它们的描述符,丢弃可能的访问器,在组合更复杂的对象或类的原型时可能会致使危险。

检索全部描述符,不管是否可枚举,也是实现类及其原型组合的关键,由于默认状况下,它们具备不可枚举的方法和访问器。

此外,装饰器能够轻松地从一个类或 mixin 中获取全部描述符,并经过 Object.defineProperties 过滤不须要的描述符。

Object.assign 只适合作对象的浅拷贝。

语法

Object.getOwnPropertyDescriptors(obj)

  • obj:任意对象

例子:

const a = { 
  b: 123,
  c: '文字内容',
  d() { return 2; }
};
Object.getOwnPropertyDescriptors(a);
// 输出结果
{
  b:{
    configurable: true,
    enumerable: true,
    value: 123,
    writable: true,
    [[Prototype]]: Object,
  }
  c:{
    configurable: true,
    enumerable: true,
    value: "文字内容",
    writable: true,
    [[Prototype]]: Object,
  }
  d:{
    configurable: true,
    enumerable: true,
    value: ƒ d(),
    writable: true,
    [[Prototype]]: Object,
  },
  [[Prototype]]: Object,
}
复制代码

四、String.prototype.padStart 和 String.prototype.padEnd

概述

padStart() 方法用另外一个字符串填充当前字符串(若是须要的话,会重复屡次),以便产生的字符串达到给定的长度。从当前字符串的开头(左侧)开始填充。

padEnd() 方法会用一个字符串填充当前字符串(若是须要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。

该提案目前处于 Stage 4 阶段。

TC39 直通车

语法

str.padStart(targetLength [, padString])

  • targetLength:当前字符串须要填充到的目标长度。若是这个数值小于当前字符串的长度,则返回当前字符串自己。
  • padString:可选的。填充字符串。若是字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其余部分会被截断。此参数的默认值为 " "(U+0020)。

str.padEnd(targetLength [, padString])

  • targetLength:当前字符串须要填充到的目标长度。若是这个数值小于当前字符串的长度,则返回当前字符串自己。
  • padString:可选的。填充字符串。若是字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其余部分会被截断。此参数的缺省值为 " "(U+0020)。

例子:

'abc'.padStart(10);         // "       abc"
'abc'.padStart(10, "foo");  // "foofoofabc"
'abc'.padStart(6,"123465"); // "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"
复制代码

ES9(ES2018)

一、for await...of

概述

for await...of 语句建立一个循环,该循环遍历异步可迭代对象以及同步可迭代对象,包括: 内置的 String、Array、相似数组对象(例如 arguments 或 NodeList)、TypedArray、Map、Set 和用户定义的异步/同步迭代器。它使用对象的每一个不一样属性的值调用要执行的语句来调用自定义迭代钩子。

相似于 await 运算符同样,该语句只能在一个 async function 内部使用。

TC39 直通车

动机

迭代器接口(在 ECMAScript 2015 中引入)是一种顺序数据访问协议,它支持开发通用和可组合的数据使用者和转换器。 因为在迭代器方法返回时必须知道序列中的下一个值和数据源的“完成”状态,所以迭代器仅适用于表示同步数据源。虽然 JavaScript 程序员遇到的许多数据源是同步的(例如内存列表和其余数据结构),但许多其余数据源不是。例如,任何须要 I/O 访问的数据源一般会使用基于事件或流式异步 API 来表示。不幸的是,迭代器不能用于表示这样的数据源。

(即便是 promise 的迭代器也是不够的,由于它只容许异步肯定值,但须要同步肯定“完成”状态。)

为了为异步数据源提供通用的数据访问协议,所以引入了异步迭代语句 for await...of

语法

async function process(iterable) {   for await (variable of iterable) { // Do something. } }

  • variable:在每次迭代中,将不一样属性的值分配给变量。变量有可能以 const、let 或者 var 来声明。
  • iterable:被迭代枚举其属性的对象。与 for...of 相比,这里的对象能够返回 Promise,若是是这样,那么 variable 将是 Promise 所包含的值,不然是值自己。

例子:

function getTime(seconds){
  return new Promise(resolve=>{
    setTimeout(() => {
      resolve(seconds)
    }, seconds);
  })
}
async function test(){
  let arr = [getTime(500), getTime(300), getTime(3000)];
  for await (let x of arr){
    console.log(x); // 500  300 3000 按顺序的
  }
}

test()
复制代码

二、Promise.prototype.finally

概述

finally() 方法返回一个 Promise。在 Promise 结束时,不管结果是 fulfilled 仍是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都须要执行的代码提供了一种方式。

这避免了一样的语句须要在 then()catch() 中各写一次的状况。

TC39 直通车

动机

有些时候在 Promise 完成时,无论结果是 fulfilled 仍是 rejected,咱们想执行一些清理工做,或是设置一些变量,这个是时候,咱们不得不在 resolvecatch 中把一样的代码书写两遍,这是重复且无心义的工做。 Promise.finally() 提案,能够在 Promise 完成时,不管结果是 fulfilled 仍是 rejected,都会执行 finally 函数。

语法

Promise.resolve().then().catch(e => e).finally(onFinally);

  • onFinally:Promise 结束后调用的 Function。

例子:

let isLoading = true;
fetch(myRequest).then(function(response) {
  const contentType = response.headers.get("content-type");
  if(contentType && contentType.includes("application/json")) {
    return response.json();
  }
  throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); })
.finally(function() { isLoading = false; });
复制代码

注意事项

finally() 虽然与 .then(onFinally, onFinally) 相似,但它们不一样的是:

  • 调用内联函数时,不须要屡次声明该函数或为该函数建立一个变量保存它。
  • 因为没法知道 promise 的最终状态,因此 finally 的回调函数中不接收任何参数,它仅用于不管最终结果如何都要执行的状况。
  • Promise.resolve(2).then(() => {}, () => {})(resolved 的结果为 undefined)不一样,Promise.resolve(2).finally(() => {}) resolved 的结果为 2。
  • 一样,Promise.reject(3).then(() => {}, () => {})(fulfilled 的结果为 undefined),Promise.reject(3).finally(() => {}) rejected 的结果为 3。

三、Object Rest/Spread Properties

概述

Rest 语法添加到解构中。Rest 属性收集那些还没有被解构模式拾取的剩余可枚举属性键。

Spread 语法添加到对象字面量中。将已有对象的全部可枚举( enumerable )属性拷贝到新构造的对象中。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

浅拷贝(Shallow-cloning,不包含 prototype)和对象合并,可使用更简短的 Spread 语法。而没必要再使用 Object.assign() 方式。

const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };

const clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }

const mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }
复制代码

有些时候咱们在解构一个对象时,可能只关心它的其中一两个属性,这个时候 Rest 语法能够极大简化变量提取过程。

const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
复制代码

语法

Rest

const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }
复制代码

Spread

let n = { x, y, ...z };
n; // { x: 1, y: 2, a: 3, b: 4 }
复制代码

注意事项

Object.assign() 函数会触发 setters,而 Spread 语法则不会。

ES10(ES2019)

一、Array.prototype.flat 和 Array.prototype.flatMap

概述

flat() 方法会按照一个可指定的深度递归遍历数组,并将全部元素与遍历到的子数组中的元素合并为一个新数组返回。

flatMap() 方法首先使用映射函数映射每一个元素,而后将结果压缩成一个新数组。

flatMap 方法与 map 方法结合深度 depth1flat 几乎相同。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

若是咱们想扁平化一个多层嵌套的数组,代码以下:

// 使用 reduce、concat 和递归展开无限多层嵌套的数组
const arr = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];

function flatDeep(arr, d = 1) {
  return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), []) : arr.slice();
};

flatDeep(arr, Infinity);
// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
复制代码

flat 提案正是用来简化这个流程:

const arr = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];
arr.flat(Infinity);
// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
复制代码

若是咱们想将包含几句话的数组拆分红单个词组成的新数组,代码以下:

const arr = ["it's Sunny in", "", "California"];
const arr1 = arr.map(x => x.split(" "));
// [["it's","Sunny","in"],[""],["California"]];
arr1.flat(2);
// ["it's", "Sunny", "in", "", "California"]
复制代码

flatMap 提案能够用来简化该流程:

const arr = ["it's Sunny in", "", "California"];
arr.flatMap(x => x.split(" "));
// ["it's", "Sunny", "in", "", "California"]
复制代码

语法

arr.flat([depth])

  • depth:可选的。指定要提取嵌套数组的结构深度,默认值为 1

例子:

[12, [34]].flat(1); 
// [1, 2, 3, 4]

[12, [34, [5, 6]]].flat(2); 
// [1, 2, 3, 4, 5, 6]

[12, [34, [5, 6]]].flat(Infinity); 
// [1, 2, 3, 4, 5, 6]
复制代码

arr.flatMap( function callback( currentValue[, index[, array]]) { // return element for new_array }[, thisArg])

  • callback:能够生成一个新数组中的元素的函数,能够传入三个参数:
    • currentValue:当前正在数组中处理的元素。
    • index:可选的。数组中正在处理的当前元素的索引。
    • array:可选的。数组中正在处理的当前元素的索引。
  • thisArg:可选的。执行 callback 函数时 使用的 this 值。

例子:

[1234].flatMap(a => [a ** 2]); 
// [1, 4, 9, 16]
复制代码

map()flatMap()

const arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]

arr1.map(x => [x * 2]).flat(1);
// [2, 4, 6, 8]

// only one level is flattened
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]
复制代码
let arr1 = ["it's Sunny in", "", "California"];

arr1.map(x => x.split(" "));
// [["it's","Sunny","in"],[""],["California"]]

arr1.flatMap(x => x.split(" "));
// ["it's","Sunny","in", "", "California"]
复制代码

二、Object.fromEntries

概述

Object.fromEntries() 方法把键值对列表转换为一个对象。

Object.fromEntries() 执行与 Object.entries 互逆的操做。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

const obj = { foo: true, bar: false };
const map = new Map(Object.entries(obj));
复制代码

若是要将相似上面代码中的 map 再转换为 obj,咱们不得不编写帮助函数:

const obj = Array.from(map).reduce((acc, [key, val]) => Object.assign(acc, { [key]: val }), {});
复制代码

Object.fromEntries 提案正是用来简化这个流程。

语法

Object.fromEntries(iterable);

  • iterable:相似 Array、Map 或者其它实现了可迭代协议的可迭代对象。

例子:

嵌套数组

const obj = Object.fromEntries([['a', 0], ['b', 1]]); 
// { a: 0, b: 1 }
复制代码

Object.entries

const obj = { abc: 1, def: 2, ghij: 3 };
const res = Object.fromEntries(
  Object.entries(obj)
  .filter(([key, val]) => key.length === 3)
  .map(([key, val]) => [key, val * 2])
);
// { abc: 2, def: 4 }
复制代码

经过 Object.fromEntries,能够将 Map 转化为 Object

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
const obj = Object.fromEntries(map);
// { a: 1, b: 2, c: 3 }
复制代码

三、String.prototype.trimStart 和 String.prototype.trimEnd

概述

去除字符串首尾的连续空白符。

trimStart() 方法从字符串的开头删除空白符。trimLeft() 是此方法的别名。

trimEnd() 方法从字符串的结尾删除空白符。trimRight() 是此方法的别名。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

ES5 标准化了 String.prototype.trim。 全部主要搜索引擎也实现了相应的 trimLefttrimRight 功能,可是无任何标准规范。 为了与 padStart/padEnd 保持一致,因此提议使用 trimStarttrimEnd。 考虑到不一样浏览器的兼容性问题,trimLeft/trimRight 将做为 trimStart/trimEnd 的别名使用。

String.prototype.trimLeft === String.prototype.trimStart;
// true
String.prototype.trimRight === String.prototype.trimEnd;
// true
复制代码

语法

const greeting = '   Hello world!   ';
console.log(greeting.trimStart());
// "Hello world!   ";
console.log(greeting.trimEnd());
// "   Hello world!";
复制代码

四、Optional Catch Binding

概述

该提案对 ECMAScript 进行了语法更改,容许在不使用 catch 绑定的状况下省略 catch 绑定。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

try {
  // try to use a web feature which may not be implemented
} catch (unused) {
  // fall back to a less desirable web feature with broader support
}
复制代码
let isTheFeatureImplemented = false;
try {
  // stress the required bits of the web API
  isTheFeatureImplemented = true;
} catch (unused) {}
复制代码

广泛认为,声明或写入从未读取的变量会引起程序错误。

语法

try {
  // ...
} catch {
  // ...
}
复制代码

五、Symbol.prototype.description

概述

Symbol 对象能够经过一个可选的描述建立,可用于调试,但不能用于访问 symbol 自己。

Symbol.prototype.description 属性能够用于读取该描述。与 Symbol.prototype.toString() 不一样的是它不会包含 "Symbol()" 的字符串。

description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

直接暴露 symboldescription 内部插槽,取代经过 symbol.prototype.toString 间接暴露。

语法

Symbol('myDescription').description;

Symbol.iterator.description;

Symbol.for('foo').description;
复制代码

例子:

Symbol('desc').toString();  // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description;     // ""
Symbol().description;       // undefined

// well-known symbols
Symbol.iterator.toString();  // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"

// global symbols
Symbol.for('foo').toString();  // "Symbol(foo)"
Symbol.for('foo').description; // "foo"
复制代码

ES11(ES2020)

一、String.prototype.matchAll

概述

matchAll() 方法返回一个包含全部匹配正则表达式的结果及分组捕获组的迭代器。

TC39 直通车

动机

以前,若是咱们要获取全部匹配项信息,只能经过循环调用 regexp.exec()( regexp 需使用 /g 标志):

const regexp = RegExp('foo[a-z]*', 'g');
const str = 'table football, foosball';
let match;

while ((match = regexp.exec(str)) !== null) {
  console.log(`Found ${match[0]} start = ${match.index} end = ${regexp.lastIndex}.`);
  // expected output: "Found football start=6 end=14."
  // expected output: "Found foosball start=16 end=24."
}
复制代码

若是使用 matchAll,就能够没必要使用 while 循环加 exec 方式(且正则表达式需使用 /g 标志)。使用 matchAll 会获得一个迭代器的返回值,配合 for...of、array spread 或者 Array.from() 能够更方便实现功能。

语法

str.matchAll(regexp)

  • regexp:正则表达式对象。若是所传参数不是一个正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp

RegExp 必须是设置了全局模式 /g 的形式,不然会抛出异常 TypeError

例子:

const regexp = RegExp('foo[a-z]*', 'g');
const str = 'table football, foosball';
const matches = str.matchAll(regexp);

for (const match of matches) {
  console.log(`Found ${match[0]} start = ${match.index} end = ${match.index + match[0].length}.`);
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."

// matches iterator is exhausted after the for..of iteration
// Call matchAll again to create a new iterator
Array.from(str.matchAll(regexp), m => m[0]);
// Array [ "football", "foosball" ]
复制代码

注意事项

一、若是没有 /g 标志,matchAll 会抛出异常。

const regexp = RegExp('[a-c]', '');
const str = 'abc';
Array.from(str.matchAll(regexp), m => m[0]);
// TypeError: String.prototype.matchAll called with a non-global RegExp argument
复制代码

二、matchAll 内部作了一个 regexp 的复制,因此不像 regexp.execlastIndex 在字符串扫描时不会改变。

const regexp = RegExp('[a-c]', 'g');
regexp.lastIndex = 1;
const str = 'abc';
Array.from(str.matchAll(regexp), m => `${regexp.lastIndex} ${m[0]}`);
// Array [ "1 b", "1 c" ]
复制代码

三、matchAll 的另一个亮点是更好地获取捕获组。由于当使用 match()/g 标志方式获取匹配信息时,捕获组会被忽略:

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

str.match(regexp);
// Array ['test1', 'test2']
复制代码

使用 matchAll 能够经过以下方式获取分组捕获:

let array = [...str.matchAll(regexp)];

array[0];
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
array[1];
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
复制代码

二、Dynamic Import

概述

动态导入模块

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

标准用法的 import 导入的模块是静态的,会使全部被导入的模块,在加载时就被编译(没法作到按需编译,下降首页加载速度)。有些场景中,你可能但愿根据条件导入模块或者按需导入模块,这时你可使用动态导入代替静态导入。下面的是你可能会须要动态导入的场景:

  • 当静态导入的模块很明显的下降了代码的加载速度且被使用的可能性很低,或者并不须要立刻使用它。
  • 当静态导入的模块很明显的占用了大量系统内存且被使用的可能性很低。
  • 当被导入的模块,在加载时并不存在,须要异步获取。
  • 当导入模块的说明符,须要动态构建。(静态导入只能使用静态说明符)。
  • 当被导入的模块有反作用(这里说的反作用,能够理解为模块中会直接运行的代码),这些反作用只有在触发了某些条件才被须要时。(原则上来讲,模块不能有反作用,可是不少时候,你没法控制你所依赖的模块的内容)。

语法

import()

关键字 import 能够像调用函数同样来动态的导入模块。以这种方式调用,将返回一个 promise

例子:

import('/modules/my-module.js')
  .then((module) => {
    // Do something with the module.
  });
复制代码

也支持 await 关键字

const module = await import('/modules/my-module.js');
复制代码

模板字符串形式

const main = document.querySelector("main");
  for (const link of document.querySelectorAll("nav > a")) {
    link.addEventListener("click", e => {
      e.preventDefault();
      import(`./section-modules/${link.dataset.entryModule}.js`)
        .then(module => {
          module.loadPageInto(main);
        })
        .catch(err => {
          main.textContent = err.message;
        });
    });
  }
复制代码

注意事项

请不要滥用动态导入(只有在必要状况下采用)。 静态框架能更好地初始化依赖,并且更有利于静态分析工具和 tree shaking 发挥做用。

与静态 import 差别:

  • import() 能够从脚本中使用,而不只仅是从模块中使用。
  • 若是 import() 在模块中使用,它能够出如今任何级别的任何地方,而且不会被提高。
  • import() 接受任意字符串(此处显示了运行时肯定的模板字符串),而不只仅是静态字符串文字。
  • 模块中存在 import() 并不会创建依赖关系,在对包含的模块求值以前,必须先获取并求值依赖关系。
  • import() 不创建能够静态分析的依赖关系(可是,在诸如 import('./foo.js') 这样的简单状况下,实现可能仍然可以执行推测性抓取)。

三、import.meta

概述

import.meta 是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,好比说这个模块的 URL

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

一般状况下,主机环境能够提供有用的模块特定信息,以便在模块内进行代码评估。如下是几个例子。

模块的 URL 或 文件名

这是在 Node.js 的 CommonJS 模块系统中经过做用域内 __filename 变量(及其对应的 __dirname )提供的。这容许经过代码轻松解析相对于模块文件的资源,例如:

const fs = require("fs");
const path = require("path");
const bytes = fs.readFileSync(path.resolve(__dirname, "data.bin"));
复制代码

若是没有可用的 _dirname,像 fs.readFileSync('data.bin') 这样的代码将解析相对于当前工做目录的 data.bin。这对于库做者来讲一般是没有用的,由于他们将本身的资源与模块捆绑在一块儿,这些模块能够位于与 CWD 相关的任何位置。

浏览器中也存在类似的行为,将文件名替换为 URL。

scrpit 标签引入资源

<script data-option="value" src="library.js"></script>

// 获取 data-option 的值以下
const theOption = document.currentScript.dataset.option;
复制代码

为此使用(有效的)全局变量而不是词法范围的值的机制是有问题的,由于这意味着该值仅设置在顶级,而且必须保存在那里才能用于任何异步代码。

mainModule

在 Node.js 中,一般的作法是经过如下代码来肯定您是程序的 main 模块仍是 entry 模块:

if (module === process.mainModule) {
  // run tests for this library, or provide a CLI interface, or similar
}
复制代码

也就是说,它容许单个文件在导入时用做库,或者在直接运行时用做反作用程序。

请注意,此特定公式如何依赖于将主机提供的做用域值模块与主机提供的全局值 process.mainModule 进行比较。

其余可设想的状况

  • 其余 Node.js 方法,如 module.childrenrequire.resolve()
  • 关于模块所属 package 的信息,或者经过 Node.jspackage.jsonweb package
  • 指向嵌入在 HTML 模块中的 JavaScript 模块的包含 DocumentFragment 的指针

语法

import.meta

import.meta 对象由一个关键字 import,一个点符号和一个 meta 属性名组成。一般状况下 import. 是做为一个属性访问的上下文,可是在这里 import 不是一个真正的对象。

import.meta 对象带有一个 null 的原型对象。这个对象能够扩展,而且它的属性都是可写,可配置和可枚举的。

例子:

如下代码使用了咱们添加到 import.meta 中的两个属性:

(async () => {
  const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
  const blob = await response.blob();

  const size = import.meta.scriptElement.dataset.size || 300;

  const image = new Image();
  image.src = URL.createObjectURL(blob);
  image.width = image.height = size;

  document.body.appendChild(image);
})();
复制代码

当这个模块被加载时,不管它的位置如何,它都会加载 hamsters.jpg 文件,并显示图像。可使用用于导入它的脚本元素来配置图像的大小,例如:

<script type="module" src="path/to/hamster-displayer.mjs" data-size="500"></script>
复制代码

四、BigInt

概述

在 JavaScript 中,BigInt 是一种数字类型的数据,它能够表示任意精度格式的整数。(新的基本数据类型)

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

在 JavaScript 中,当 number 大于 Number.MAX_SAFE_INTEGER 时,就会发生精度丢失。

const x = Number.MAX_SAFE_INTEGER;
// 9007199254740991, this is 1 less than 2^53

const y = x + 1;
// 9007199254740992, ok, checks out

const z = x + 2;
// 9007199254740992, wait, that’s the same as above!
复制代码

BigInt 的提案正是要解决这个问题,BigInt 使 Number.MAX_SAFE_INTEGER 再也不是 JavaScript 的限制。它能够表示任意精度的整数。

语法

经过 BigInt 方法,或是在一个数值后添加 n 后缀,来将一个 Number 转换为 BigInt 类型。

例子:

const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
// 9007199254740991n
const hugeButString = BigInt('9007199254740991');
// 9007199254740991n
复制代码
Number.MAX_SAFE_INTEGER;
// 9007199254740991
Number.MAX_SAFE_INTEGER + 10 - 10;
// 9007199254740990   (精度丢失)
BigInt(Number.MAX_SAFE_INTEGER) + 10n - 10n;
// 9007199254740991n  (计算结果为 bigint 类型)

typeof 9007199254740991n === "bigint";
// true
复制代码

你可使用 +*-**% 运算符,就像在 Number 中使用同样。

const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// 9007199254740991

const maxPlusOne = previousMaxSafe + 1n;
// 9007199254740992n
 
const theFuture = previousMaxSafe + 2n;
// 9007199254740993n, this works now!

const multi = previousMaxSafe * 2n;
// 18014398509481982n

const subtr = multi – 10n;
// 18014398509481972n

const mod = multi % 10n;
// 2n

const bigN = 2n ** 54n;
// 18014398509481984n

bigN * -1n
// –18014398509481984n
复制代码

注意事项

一、/ 除法运算符也可使用,可是会向下取整。

由于 BigInt 不是 BigDecimal

const expected = 4n / 2n; // 2n
const rounded = 5n / 2n;  // 2n, not 2.5n
复制代码

二、BigInt 并不严格等于 Number

2n === 2; // false
2n == 2;  // true
复制代码

三、比较运算符能够正常使用。

1n < 2;  // true 
2n > 1;  // true 
2 > 2;   // false 
2n > 2;  // false 
2n >= 2; // true
复制代码

四、数组中混合 BigIntNumber 能够正常排序。

const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
// [4n, 6, -12n, 10, 4, 0, 0n]

mixed.sort();
// [-12n, 0, 0n, 10, 4n, 4, 6]
复制代码

五、BigInt 不能够与 Number 混合运算。

1n + 2;
// TypeError: Cannot mix BigInt and other types, use explicit conversions

1n * 2;
// TypeError: Cannot mix BigInt and other types, use explicit conversions
复制代码

五、Promise.allSettled

概述

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

当您有多个彼此不依赖的异步任务成功完成时,或者您老是想知道每一个 promise 的结果时,一般使用它。

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个 reject 时当即结束。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

api 描述  
Promise.allSettled 不会短路 本提案 🆕
Promise.all 当存在输入值 rejected 时短路 在 ES2015 中添加 ✅
Promise.race 当存在输入值 稳定 时短路 在 ES2015 中添加 ✅
Promise.any 当存在输入值 fulfilled 时短路 在 ES2021 中添加 ✅

能够看出,Promise.allPromise.racePromise.any 都存在短路行为。

Promise.allSettled 不存在短路行为,它确保每一个 promise 均可以执行完毕,不管状态是 fulfilled 仍是 rejected

语法

Promise.allSettled(iterable)

  • iterable:一个可迭代的对象,例如 Array,其中每一个成员都是 Promise

一旦所指定的 promises 集合中每个 promise 已经完成,不管是成功的达成或被拒绝,未决议的 promise 将被异步完成。那时,所返回的 promise 的处理器将传入一个数组做为输入,该数组包含原始 promises 集中每一个 promise 的结果。

对于每一个结果对象,都有一个 status 字符串。若是它的值为 fulfilled,则结果对象上存在一个 value。若是值为 rejected,则存在一个 reasonvalue(或 reason)反映了每一个 promise 决议(或拒绝)的值。

例子:

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises)
  .then((results) => results.forEach((result) => console.log(result.status)));

// expected output:
// "fulfilled"
// "rejected"
复制代码
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => reject('我是失败的Promise_1'));
const promise4 = new Promise((resolve, reject) => reject('我是失败的Promise_2'));
const promiseList = [promise1, promise2, promise3, promise4];
Promise.allSettled(promiseList)
.then(values => {
  console.log(values);
});
复制代码

六、globalThis

概述

全局属性 globalThis 包含全局的 this 值,相似于全局对象(global object)。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

编写访问全局对象的可移植 ECMAScript 代码是很困难的。在 web 端,能够是 window 或者 self 或者 this 或者 frames。在 node.js 中,是 global 或者 this

其中,只有 this 在 V8 的 d8 或者 JavaScriptCore 的 jsc 这样的 shell 中可用。 在 sloppy 模式下的独立函数调用中,this 也能够工做,可是在模块中或在函数内的严格模式下它是 undefined

在这样的上下文中,全局对象仍然能够经过 Function('return this')() 访问,但在某些 CSP 设置中,例如在 Chrome 应用程序中是不可访问的。

下面是一些从外部获取全局对象的代码,做为单个参数传递给 IIFE,它在大多数状况下都有效,但实际上不适用于 d8 当在模块中或在函数内的严格模式下(可使用函数技巧修复):

function foo() {
  // If we're in a browser, the global namespace is named 'window'. If we're
  // in node, it's named 'global'. If we're in a shell, 'this' might work.
  (typeof window !== "undefined"
    ? window
    : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
      ? global
      : this);
}
复制代码

此外,因为 CSP 问题,es6-shim 必须从 Function('return this')() 转换, 当前处理 browsersnodeweb workersframes 方式以下:

const getGlobal = function () {
  // the only reliable means to get the global object is
  // `Function('return this')()`
  // However, this causes CSP violations in Chrome apps.
  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');
};
复制代码

语法

在浏览器中, windowglobalThisthis 三者全等:

window === this;       // true
window === globalThis; // true
this === globalThis;   // true
复制代码

在 node.js 中,globalglobalThisthis 三者全等:

> global === this;       // true
> global === globalThis; // true
> this === globalThis;   // true
复制代码

七、Nullish Coalescing

概述

空值合并操做符(??)是一个逻辑操做符,当左侧的操做数为 null 或者 undefined 时,返回其右侧操做数,不然返回左侧操做数。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

在执行属性访问时,若是属性访问的结果为 nullundefined,则一般须要提供默认值。目前,在 JavaScript 中表达此意图的一种典型方式是使用 || 运算符。

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false,
  },
};
const undefinedValue = response.settings.undefinedValue || 'some other default';
// result: 'some other default'
const nullValue = response.settings.nullValue || 'some other default';
// result: 'some other default'
复制代码

这对于 nullundefined 的值常见状况很是有效,但有许多 falsy 值可能会产生使人惊讶的结果:

const headerText = response.settings.headerText || 'Hello, world!';
// Potentially unintended. '' is falsy, result: 'Hello, world!'
const animationDuration = response.settings.animationDuration || 300;
// Potentially unintended. 0 is falsy, result: 300
const showSplashScreen = response.settings.showSplashScreen || true;
// Potentially unintended. false is falsy, result: true
复制代码

语法

leftExpr ?? rightExpr

例子:

let user = {
  u10,
  u2false,
  u3null,
  u4undefined,
  u5'',
}
let u2 = user.u2 ?? '用户2'// false
let u3 = user.u3 ?? '用户3'// 用户3
let u4 = user.u4 ?? '用户4'// 用户4
let u5 = user.u5 ?? '用户5'// ''
复制代码

八、Optional Chaining

概述

可选链操做符(?.)容许读取位于链接对象链深处的属性的值,而没必要明确验证链中的每一个引用是否有效。?. 操做符的功能相似于 . 链式操做符,不一样之处在于,在引用(null 或者 undefined)的状况下不会引发错误,该表达式短路返回值是 undefined。与函数调用一块儿使用时,若是给定的函数不存在,则返回 undefined

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

在寻找对象结构深处的属性值时,一般必须检查中间节点是否存在:

const street = user.address && user.address.street;
复制代码

此外,许多 API 返回对象或 null/undefined,而且可能但愿仅在结果不为 null 时从结果中提取属性:

const fooInput = myForm.querySelector('input[name=foo]');
const fooValue = fooInput ? fooInput.value : undefined;
复制代码

语法

obj?.prop

obj?.[expr]

arr?.[index]

func?.(args)

例子:

使用可选链访问对象属性:

let user = {};
let u1 = user.childer.name;
// TypeError: Cannot read property 'name' of undefined
let u1 = user?.childer?.name;
// undefined
复制代码

使用可选链进行函数调用

function doSomething(onContent, onError) {
  try {
   // ... do something with the data
  }
  catch (err) {
    onError?.(err.message); // 若是onError是undefined也不会有异常
  }
}
复制代码

当使用方括号与属性名的形式来访问属性时,你也可使用可选链操做符:

const nestedProp = obj?.['prop' + 'Name'];
复制代码

可选链访问数组元素:

let arrayItem = arr?.[42];
复制代码

ES12(ES2021)

一、String.prototype.replaceAll

概述

replaceAll() 方法返回一个新字符串,新字符串全部知足 pattern 的部分都已被 replacement 替换。pattern 能够是一个字符串或一个 RegExpreplacement 能够是一个字符串或一个在每次匹配被调用的函数。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

目前没有办法在不使用全局正则表达式的状况下替换字符串中子字符串的全部实例。 String.prototype.replace 仅在与字符串参数一块儿使用时替换掉第一次出现的子字符串。

目前实现这一目标的最多见方法是使用全局正则表达式:

const queryString = 'q=query+string+parameters';
const withSpaces = queryString.replace(/+/g, ' ');
复制代码

这种方法的缺点是须要对特殊的 RegExp 字符进行转义——注意转义的 +

另外一种解决方案是结合 String.splitArray.join

const queryString = 'q=query+string+parameters';
const withSpaces = queryString.split('+').join(' ');
复制代码

这种方法避免了任何转义,但带来了将字符串拆分为数组,而后再将数组各项从新粘合在一块儿的开销。

语法

String.prototype.replaceAll(searchValue, replaceValue);

返回一个全新的字符串,全部符合匹配规则的字符都将被替换掉。

除了如下两种状况外,在其余全部状况下 String.prototype.replaceAll 行为都与 String.prototype.replace 相同:

  • 若是 searchValue 是一个字符串,String.prototype.replace 只替换一次出现的 searchValue,而 String.prototype.replaceAll 替换全部出现的 searchValue
  • 若是 searchValue 是非全局正则表达式,则 String.prototype.replace 替换单个匹配项,而 String.prototype.replaceAll 抛出异常。这样作是为了不缺乏全局标志(这意味着“不要所有替换”)和被调用方法的名称(强烈建议“所有替换”)之间的固有混淆。

例子:

const queryString = 'q=query+string+parameters'; 
const withSpaces = queryString.replaceAll('+', ' ');
复制代码

二、Promise.any

概述

Promise.any() 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise。若是可迭代对象中没有一个 promise 成功(即全部的 promises 都失败/拒绝),就返回一个失败的 promise

该提案目前处于 Stage 4 阶段。

TC39 直通车

语法

Promise.any(iterable)

  • iterable:一个可迭代的对象,例如 Array。

例子:

const promise1 = new Promise((resolve, reject) => reject('失败的Promise1'));
const promise2 = new Promise((resolve, reject) => reject('失败的Promise2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList)
.then(values=>{
  console.log(values);
})
.catch(error=>{
  console.log(error);
});
复制代码

在上面的示例中,errorAggregateError,它是一个新的 error 子类,将各个错误组合在一块儿。每一个 AggregateError 实例都包含一个指向异常数组的指针。

三、Logical Assignment Operators

概述

逻辑赋值运算符

该提案目前处于 Stage 4 阶段。

TC39 直通车

语法

||=

&&=

??=

例子:

a ||= b;
//等价于
a = a || (a = b);

a &&= b;
//等价于
a = a && (a = b);

a ??= b;
//等价于
a = a ?? (a = b);
复制代码

四、Numeric Separators

概述

数字分隔符

它是将其早期草案与 Christophe Porteneuve 的提案 numeric underscores 合并的结果,以扩展示有的 NumericLiteral 以容许数字之间的分隔符。

该提案目前处于 Stage 4 阶段。

TC39 直通车

动机

此功能使开发人员可以经过在数字之间建立视觉分隔来使他们的数字文字更具可读性。

语法

使用下划线 _(U+005F)在数字间进行分隔。

例子:

十进制

const price1 = 1_000_000_000;
const price2 = 1000000000;
price1 === price2; // true

const float1 = 0.000_001;
const float2 = 0.000001;
float1 === float2; // true;
复制代码

二进制

const nibbles1 = 0b1010_0001_1000_0101;
const nibbles2 = 0b1010000110000101;
nibbles1 === nibbles2; // true
复制代码

十六进制

const message1 = 0xA1_B1_C1;
const message2 = 0xA1B1C1;
message1 === message2; // true
复制代码

BigInt

const big1 = 1_000_000_000n;
const big2 = 1000000000n;
big1 === big2; // true
复制代码
若是你以为这篇文章对你有点用的话,麻烦请给咱们的开源项目点点star:   http://github.crmeb.net/u/defu   不胜感激 !