ES2015(ES6)之后特性的合集(很详细)

一、ES 2016

1、Array.prototype.includes 和 String.prototype.includes

在之前我们比较一个数组中是否包含某个元素的常用做法是:

if (arr.indexOf(el) !== -1) {
    ...
}

或者

if (~arr.indexOf(el)) {
    ...
}
//此处~ 为取反,即-1取反结果为0,0取反为-1,1取反会-2,2取反为-3

indexOf 结果返回是个数字,我们必须要和其他数字进行比较,才能见到得到是否"存在",即-1 表示不存在,大于-1 表示存在。

由于 indexOf 在查找元素的比对过程中,使用的是严格相等"===",比如

[1,2,3].indexOf('1')   //就会找不到。因为1和'1'并不相等。

同样在当数组中含有 NaN 是,也找不到,即

[NaN].indexOf(NaN)   //找不到,因为NaN===NaN为false。

由此可见 indexOf 并不能完美的找出是否包含某个元素值。

所以我们便有了下面的方式

arr.includes(el);

includes 结果直接返回 boolean,true 表示存在,false 表示不存在,非常直观。includes 是通过,类型和值是否“一致”,而不是“严格相等”

[1,2,3].includes('1')   //false
[1,2,3].includes(1)   //true
[1,2,NaN].includes(NaN)   //true

所以再以后的使用中,
indexOf 应倾向于寻找 元素的位置,可能后续还要通过这个位置进行其他计算的场景,而 includes 应倾向于寻找 元素是否存在,只需要知道存在或者不存在,其他不必知道

既然数组有 indexOf,字符串也有 indexOf,那么同样
数组有 includes,字符串也有 includes

"1234567".includes("12")   //true
2、指数运算符

在以前,js 要求取 m 的 n 次方,得这么写

Math.pow(m,n)

比如 2 的 3 次方

Math.pow(2,3)  //8

而现在只需要 指数操作符"**",即两个星号即可

2 ** 3  //表示2的3次方
3 ** 2  //表示3的2次方

由于- + _ /,都有 -=,+=, _=, /=

同样 也有 "="

let a = 2;
a **= 2; //  a = a ** 2

二、ES 2017

1、Object.values / Object.keys() / Object.entries

将 object 的 key 或者 value 转成一个可以遍历的数组。

Object.values({id:1,name:2});    // [1,2]
Object.keys({id:1,name:2});    // ["id", "name"]
Object.entries({id:1,name:2});    // [["id", 1], ["name", 2]]
2、字符串“填充”
  • String.prototype.padStart(); 使用给定的字符串,填充原字符串头部,整体达到一定的长度
  • String.prototype.padEnd();使用给定的字符串,填充原字符串尾部,整体达到一定的长度
  • String.prototype.trimRight();移除尾部的空白(和 trimEnd 一样,为了兼容其他浏览器)
  • String.prototype.trimLeft();移除首部的空白(和 trimStart 一样,为了兼容其他浏览器)
  • String.prototype.trimEnd(); 移除尾部的空白
  • String.prototype.trimStart();移除首部的空白
  • String.prototype.trim(); 移除两端的空白
//padStart
"abc".padStart(10); // "       abc"  在原字符串"abc"的头部填充空白,以使整个字符串的长度变为10
"abc".padStart(10, "foo"); // "foofoofabc"  在原字符串"abc"的头部填充"foo",以使整个字符串的长度变为10
"abc".padStart(6, "123465"); // "123abc"   在原字符串"abc"的头部填充"123465",以使整个字符串的长度变为10,如果中途够了,就不进行填充了
"abc".padStart(8, "0"); // "00000abc"
"abc".padStart(1); // "abc"
//padEnd
"abc".padEnd(10); // "abc       "   同padStart,只不过填充到末尾
"abc".padEnd(10, "foo"); // "abcfoofoof"   同padStart,只不过填充到末尾
"abc".padEnd(6, "123456"); // "abc123"       同padStart,只不过填充到末尾
"abc".padEnd(1); // "abc"          同padStart,只不过填充到末尾
//trimStart
"   abc   ".trimStart(); // "abc   "
"   abc   ".trimEnd(); //  "   abc"
"   abc   ".trim(); // "abc"
3、Object.getOwnPropertyDescriptors

获取对象的所有自身属性的描述符。包括 configurable(可配置),enumerable(可枚举),writable(可写)

Object.getOwnPropertyDescriptors({ id: 1, name: 2 });

/*
{
  "id": {
    "value": 1,
    "writable": true,
    "enumerable": true,
    "configurable": true
  },
  "name": {
    "value": 2,
    "writable": true,
    "enumerable": true,
    "configurable": true
  }
}
*/
4、在函数末尾参数后增加逗号

在之前的函数是这样的

function clownPuppiesEverywhere(param1, param2) {}

//或者这样
function clownPuppiesEverywhere(param1, param2) {}

param2 后面如果加逗号会报错,而现在不会报错,它是被允许的

function clownPuppiesEverywhere(param1, param2) {}

//或者这样
function clownPuppiesEverywhere(param1, param2) {}

这样如果我们有新参数,只需要往后加就行了,不必往上一行末尾加逗号(真懒,加个逗号又不费事,鸡肋特性)

5、async / await

将异步变为同步神器。再也不需要像 promise 那样回调了

async function getList(){ //用async指明当前方法中有异步,但是当做同步执行

    await getId();  //如果getId是个promise,则只有状态为resolve时,才进入下一行。如果是普通函数,则默认是resolved


    let result = await getName(); //还可以有返回值。其值为resolved(xx)中的参数

}

await 的并行处理

//因为多个await都是依次往后台发送。如果这几个请求之前没有先后关联顺序,则完全没必要。可以做成并行发送
async getName() {
  let str = "";
  await Promise.all([this.getPromise(),this.getPromise(),this.getPromise()]);
  return str;
}
6、共享内存和原子

太复杂,我也没看懂

如果你想查看,这是详细
文档

三、ES 2018

1、正则表达式 点匹配符 s

在正则表达式中,“.” 点,代表 匹配除换行符之外的任何单个字符。
如下:

/.n/.test('an');   //. 匹配到了a
/.n/.test('1n');   //. 匹配到了1
/.n/.test('nn');   //. 匹配到了n
/.n/.test('\nn');   //.不会匹配 \n 返回false

假如有个需求,需要匹配任何字符。那怎么办.....

有个骚操作。

/foo[^]bar/.test('foo\nbar');  //返回true

用 [^] 替换掉. 它表示匹配包含n 在内的任意字符。

可这也太秀了,正常人都想不到这么写。这么写出来,别人也看不懂。

所以便有了下面的写法:

/foo.bar/s.test('foo\nbar');    //返回true

正则的后面加个 s 标志,你可以将 s 理解为 stop,终止的意思,即表示除了其他的,还能匹配终止符 n,换句话说现在这个. 啥都能匹配到

我们可以总结下,到此为止,正则表达式标志符有:

g      全局搜索。
i      不区分大小写搜索。
m      多行搜索。
s      允许 . 匹配换行符。
u      使用unicode码的模式进行匹配。
y      执行“粘性”搜索,匹配从目标字符串的当前位置开始,可以使用y标志。
2、扩展运算符
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }



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

或者

let [x,y,...z] = [ 1,2,3,4 ];
x; // 1
y; // 2
z; // [3, 4]



let n = [ x, y, ...z ];
n; // [1, 2, 3, 4]

...x 始终代表这是个展开式,而 x 代表原式(对象,或者数组),

类似于高数中的泰勒展开式, ...x 就是那个展开式,x 就是那个原式。

比如:

function getName({a,b,...others}){
    console.log(others)
}

getName({a:1,b:2,c:3,d:4})

由于...others 是展开式 c:3,d:4,那么 others 自然就是原式{c: 3, d: 4}

3、Promise.prototype.finally

在 promise 结束时,无论结果是 resovled 或者是 rejected,finally 都会执行指定的回调函数。

这避免了同样的语句需要在 then()和 catch()中各写一次的情况。

所以 finally 中最适合需要进行"清空"的操作。比如 request 请求完成或失败后,都要将 loading 设置为 false。或者关闭打开的文件流对象,或者进行日志记录等。

finally()方法和 then()一样,执行后,也返回一个 Promise。

所以一般流程是

new Promise((resolved, rejected)=>{
    //...
    //return  resolved(res); 如果执行成功
    //return  rejected(); 如果执行失败
    //...
}).then(res=>{
    //resolved的回调, res的值为resolved传入的参数
}).catch(err=>{
    //rejected的回调。err为rejected传入的失败的原因
}).finally(()=>{
    //由于不关注resolved还是rejected。所以没有参数。

})
4、for-await-of

我们经常会通过 for....of 进行迭代,如下:

let arr = [1,2,3];
for(let num of arr) {
   console.log(num);
}

输出 1,2,3

我们可以想象下,加入 arr 中的每一项不是普通类型的对象,而是 promise 呢?

//分别创建三个promise对象
let p1 = new Promise((resolved,rejected)=>{
    setTimeout(()=>{
       resolved(1)
    },1000)
});
let p2 = new Promise((resolved,rejected)=>{
    setTimeout(()=>{
       resolved(2)
    },1000)
});
let p3 = new Promise((resolved,rejected)=>{
    setTimeout(()=>{
       resolved(3)
    },1000)
});

let arr = [p1,p2,p3];
for(let num of arr) {
   console.log(num);
}

那执行结果会怎么样呢?

你将会在控制台直接看到下面三个结果。

> Promise {}
> Promise {}
> Promise {}

也就是说 arr 在迭代时,分别将 p1,p2,p3 输出控制台了,而 p1,p2,p3 是谁,当然是 promise 啊,只不过还没执行 resolved 而已,处于 pending 状态。

如果我们想在 arr 迭代时,依次输出 p1,p2,p3 的 resolved 或者 rejected 结果呢?

我们可以使用下面的方式

//分别创建三个promise对象
let p1 = new Promise((resolved,rejected)=>{
    setTimeout(()=>{
       resolved(1)
    },1000)
});
let p2 = new Promise((resolved,rejected)=>{
    setTimeout(()=>{
       resolved(2)
    },1000)
});
let p3 = new Promise((resolved,rejected)=>{
    setTimeout(()=>{
       resolved(3)
    },1000)
});

let arr = [p1,p2,p3];
for await(let num of arr) {
   console.log(num);
}

在 for 后面,左圆括号前面,加个 await。

这样在每次 arr 迭代时,等待 num 的结果为 resolved 或者 rejected 时,才进入 for 的语句块。

结果输出为: 1 2 3

我们可以想想在没有 for-await-of 之前,是怎么用的?

let num = null;
let arr = [p1,p2,p3];

num = await arr[0];
console.log(num);

let num = await arr[1];
console.log(num);

let num = await arr[2];
console.log(num);

是不是?但是如果 arr 中几个这么写还行,可如果多了呢,显然不如 for await of 方便。

四、ES 2019

1、可选的 catch 参数
try {
  throw new Error("出错了")
} catch (err) {
  console.log(err.message);
}

我们通常会在 catch 中通过 err 来获取或者打印错误信息。

但是有些场景下我们不需要 catch 中的 err,信息,只是捕获错误,然后做些其他的事。此时 err 参数被闲置,unused,未被使用。

针对这种情况,es 规范允许你省略 err 参数。

try {
  throw new Error("出错了")
} catch () {
  console.log('321');
}

==Uncaught SyntaxError: Unexpected token ')'==

报错了,纳尼?

注意不是上面这种省略,而应该是下面的省略。

try {
  throw new Error("出错了")
} catch{
  console.log('321');
}

catch 后面直接跟{即可。

坑我给你们填上了,别在掉进去了。

2、json 超集

由于 json 可以支持 "u2028"(行分隔符)和 "u2029"(段落分隔符)两个特殊字符。

我们可以看看这两个字符长什么样
ES2015(ES6)之后特性的合集(很详细)_第1张图片
方方正正的,一个写着 LSEP,一个写着 PSEP.

创建个 json,包含 u2028.

ES2015(ES6)之后特性的合集(很详细)_第2张图片

没问题,可以解析。

但是这两个字符之前在 js 中是不能写的。所以 ECMA 在标准中实现了他们,以便在 js 中也能正常使用。

ES2015(ES6)之后特性的合集(很详细)_第3张图片

3、Symbol.prototype.description

symbol 是一种基本数据类型。 每个从 Symbol()返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。

我们可以通过下面方式获取 symbol 的描述。

Symbol('desc').description    //desc
4、Function.prototype.toString 的修订

以前由于 function 继承了从 object 来的 toString 方法;所以 toString 为"[object Object]"

function getName(){
    return "hello";
};
getName.toString()    // "[object Object]"

而修订后的 function toString 覆盖了从 object 继承来的 toString 方法。返回一个包含定义函数的源文本段。

ES2015(ES6)之后特性的合集(很详细)_第4张图片

5、Object.fromEntries
let obj = {
  id: 1,
  name: "hello",
};
let entries = Object.entries(obj); //   [["id",1],["name","hello"]]

同样我们可以使用 fromEntries 还原 obj

let obj = Object.fromEntries([
  ["id", 1],
  ["name", "hello"],
]); // {id: 1, name: "hello"}
6、Array.prototype.flat / Array.prototype.flatMap

具体可参考 mdn 链接

flat 文档

flatMap 文档

五、ES 2020

1、String.prototype.matchAll
let regexp = /te/;
let str = 'test1test2';

str.match(regexp)   // ['te']  匹配到第一个位置就停止,并返回匹配到的字符串

加个 g 标志

let regexp = /te/g;
let str = 'test1test2';

str.match(regexp)   //["te", "te"]   返回所有匹配到的字符串

matchAll 会返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。且正则表达式必须含有标志 g,否则报错

let regexp = /te/g;
let str = 'test1test2';

str.matchAll(regexp)   //  RegExpStringIterator{}  但会迭代器。

可以通过解构方式取出迭代器。

let regexp = /te/g;
let str = 'test1test2';

let arr = [...str.matchAll(regexp) ]   //
arr[0]  //  {0: "te", groups: undefined, index: 0, input: "test1test2", length: 1}

arr[1]  //  {0: "te", groups: undefined, index: 5, input: "test1test2", length: 1}

这样我们不仅能获取到 匹配的字符串,还是通过 index 知道匹配的位置。

2、import()

在 js 中,引入模块的方式通常是:

import _ from "lodash"

在代码运行之前,在编译阶段,就已经绑定引入本地作用域。

但是如果我们想在运行阶段动态的引入呢?

答案是没有办法。

如果你使用的是 webpack,则可以使用 webpack 的 require("xxx") 动态的将模块引入。

所以 ECMA 直接在 js 核心库中,实现了这个 api。即使不适用 webpack,也能直接在浏览器上运行 import().

在 chrome 控制台我们可以直接使用。

ES2015(ES6)之后特性的合集(很详细)_第5张图片

同时发现,import()返回的是个 promise 对象,

所以我们可以这么用

import("/static/js/app.js").then(res=>{
    //xxx
})

或者
await import("/static/js/app.js");
//后续代码
3、BigInt

在 javascript 中,interget 的最大数为 2^53

Math.pow(2,53)  // 9007199254740992

如果加 1,发现没变。

Math.pow(2,53)+1  // 9007199254740992

假如要表示 比 Math.pow(2,53) 还要大的数值怎么办?

可以使用 BigInt 类型来表示

const theBiggestInt = 9007199254740991n;   //后面追加 n,表示这是个bigInt类型

const alsoHuge = BigInt(9007199254740991);  //直接通过构造器创建。

在 bigInt 中,最小的 1,用 1n 来表示,如下面的运算

1n+1n==2n
2n-1n == 1n
2n * 2n == 4n
4n / 2n == 2n

9007199254740992n +1n==9007199254740993n   //  //2^53加1  结果为

特殊情况

1n+1n==2  //true
0n == 0   //true

//但是下面为false
1n+1n==2    //false
0n === 0    //false

特别的

1n+1    会直接报错

Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
3、Promise.allSettled

在 promise 中的组合器有下面几种:

  • Promise.all 参数中的所有 promise 完成,才执行回调。
  • Promise.race 只要有一个完成,或者一个失败,就马上执行回调。
  • Promise.all 只要有个完成,或者所有的失败,才执行回调。
  • Promise.allSettled 永远不会中途结束,会全部完成,返回所有给定的 promise 已被完成或失败后的数组。
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, "1oo");
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 1000, "foo");
});
const promises = [promise1, promise2];

Promise.allSettled(promises).then((results) => {
  //results  [{"status":"fulfilled","value":"1oo"},{"status":"rejected","reason":"foo"}]
});
3、globalThis

在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 window、self 或者 frames 取到全局对象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它们都无法获取,必须使用 global。

globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)

这样在浏览器中, globalThis 就是 window 或者 self,
在 Web Workers 中, globalThis 就是 self,
在 nodejs 中,globalThis 就是 global。

快去把你项目中的 window,self,global 等替换成 globalThis 吧

4、for-in

for...in 语句以任意顺序遍历一个对象的除 Symbol 以外的可枚举属性。

在对象中使用 for....in 并没有什么问题

for(let item in {id:1,name:'123'}){
   console.log(item)  //依次输出 id, name
}

不建议 for....in 和数组搭配一起使用。为什么呢,请看下面的例子:

for(let item in ['a','b','c']){
   console.log("-===",item) //依次输出 '0' '1' '2',

   //注意  '0' '1' '2'并非下标,而是枚举位置
}

我们再给数组添对象添加个可枚举属性

let arr = [1,2,3];
Object.defineProperty(arr, "getName", {
    value: "hello",
    enumerable: true
});

//等同于

let arr = [1,2,3];
Array.prototype.getName = "hello";


//紧接着我们执行下面
for(let item in arr){
   console.log(item)  //依次输出 '0' '1'  '2'  'getName'
}

发现没?? 是不出错了,如果我们项目中,在 util 的模块中,给 Array.prototype 上增加了一些 polyfill,那么我们使用 for....in 遍历数组,就必然出错。

当然我们可以通过其他方式避免。
比如

for(let i=0;i{
  //
})
5、可选链操作符 '?.'

在以前我们进行条件判断时:

let obj = { "circle": { "x": 0, "y": 0 }, "radius": 50 };

if(obj && obj.circle && obj.circle.x){

    //然后才敢拿着  obj.circle.x 进行操作。否则假如obj为null,obj.circle 就会直接报错。

}

而现在不用这么麻烦

let obj = { "radius": 50 };

if(obj?.circle?.x){
    // obj.circle.x 进行操作
}

?.表示是否有这个属性,如果没有(undifined),或者有,但值为 null。则直接短路不会再继续向下。从而保护了代码不被出错。

let obj = { "circle": { "y": 0 }, "radius": 50 };

if(obj?.circle?.x){

}else{
    //不会报错。在执行到 ?.circle时得到undefiend,直接短路。返回undefiend, 进入 else
}

同样,数组也可以

let arr = ['a','b'];
arr?.[1]  //  'b'

最后注意,可选链不能用于赋值操作

let arr = ['a','b'];
arr?.[1]  = 'c'  //报错   Uncaught SyntaxError: Invalid left-hand side in assignment
6、空值合并操作符 '??'

它是个逻辑操作符,类似于 ||

|| 操作符,只有当左侧为假值(0, '', NaN, false, null,undefined)是才返回右侧的值.

0 || 'A' //A
'' || 'A' //A
NaN || 'A' //A
false || 'A' //A
null || 'A' //A
undefined || 'A' //A

而 ??只有左侧为空值(只有 null 和 undefined)时才

0 ?? 'A';     // 0
'' ?? 'A'      // ''
NaN ?? 'A'     //NaN
false ?? 'A'    //false
null ?? 'A'     //A
undefined ?? 'A' //A
6、import.meta

具体查看
文档

参考 ECMA 规范:
https://github.com/tc39/proposals/blob/master/finished-proposals.md

你可能感兴趣的:(javascript,前端,ecmascript)