【本文共4000字左右,阅读时间约10分钟】
截止目前为止,以下的17个特性(按时间顺序排列)都已经处于finished阶段,接下来就让我们一睹新特性的风采吧!
一. 8个ES2019新特性
1. Optional catch binding
catch参数可选, 我们在使用try catch捕获错误的时候,即使并不需要错误信息,也必须给catch传参数的,否则就会报错:
const isValidJSON = json => {
try {
JSON.parse(json);
} catch (err) {
console.log(err)
}
};
在es2019中,我们可以这么写,是不是感觉舒服了很多:
const isValidJSON = json => {
try {
JSON.parse(json);
} catch {
}
};
2. JSON superset
JSON超集:ES2019 规范要求,字符串字面量支持完整的 JSON 字符集,即在支持 ES2019 的环境中,对于双引号/单引号中的U+2028和U+2029字符。
我们知道JavaScript 规定以下5个字符,不能在字符串里面直接使用,只能使用转义形式。
U+005C:反斜杠(reverse solidus)
U+000D:回车(carriage return)
U+2028:行分隔符(line separator)
U+2029:段分隔符(paragraph separator)
U+000A:换行符(line feed)
如下:执行解析分隔符都会报语法错误:
eval(" '\u2029' "); // Uncaught SyntaxError: Invalid or unexpected token
JSON.parse('"\u2029"'); // Uncaught SyntaxError: Invalid or unexpected token
ES2019支持了这个新特性之后,可以直接执行解析:
eval(" '\u2029' "); //""
JSON.parse('"\u2029"'); // ""
3. Symbol.prototype.description
Symbol的描述属性(只读):用于获取符号对象的描述,更好了解该符号的作用。
新特性之前,假如我们想要知道定义的Symbol的作用,只能通过Symbol.prototype.toString获取,如:
let sym = Symbol('foo');
sym.toString(); // => foo
Es2019中这个新增的description特性,允许我们直接的获取到描述,如:
let sym = Symbol('foo');
sym.description; // => foo
4. Function-prototype-toString-revision
ES2019中重新修订Function.prototype.toString,toString方法会原样返回源代码中的实际文本片段。即返回注释、空格和语法详细信息。
看个例子,以前的写法中,toStringf()只返回了函数主体:
function /* zhushi*/ foo /*zhushi*/ (){}**
foo.toString(); // function foo(){}
在新特性中,函数的toString方法输出的内容与编写时的一致:
function /* zhushi*/ foo /*zhushi*/ (){}**
foo.toString(); // function /* zhushi*/ foo /*zhushi*/ (){}
// 注意:这里不支持箭头函数js
const bar /* zhushi*/ = /*注释*/ () => {}
bar.toString(); // () => {}
5. Object.fromEntries
Object.fromEntries函数,用于把键值对还原成对象结构,可以理解为是entries的逆方法。
在ES6时候,如果我们想把一个键值对还原成对象,写起来稍微复杂一点:
const getObj = (myArray) => Array.from(myArray).reduce((acc, [key, val]) => Object.assign(acc, {[key]: val}), {});
getObj([['one', 1], ['two', 2], ['three', 3]]) // {one: 1, two: 2, three: 3}
有了新特性之后,我们可以一步到位:
const myArray1 = [['one', 1], ['two', 2], ['three', 3]];
Object.fromEntries(myArray1) //{one: 1, two: 2, three: 3}
//在只有一个数值的情况下,会默认这个数值为key,值为undefined
const myArray2 = [['one'], ['two'], ['three']];
Object.fromEntries(myArray2) //{one: undefined, two: undefined, three: undefined}
//在有多个数值的情况下,会默认截取第一个值为key,第二个值为value
const myArray3 = [['one',1,'yi'], ['two',2,'er'], ['three',3,'san']];
Object.fromEntries(myArray3) //{one: 1, two: 2, three: 3}
6. JSON.stringify
加强格式化,主要是修复了一些unicode字符显示的问题。
由于JSON数据必须是UTF-8编码,UTF-8 标准规定,0xD800到0xDFFF之间的码点,不能单独使用,对于不存在的码点或者该区间的码点,JSON会认为这是一个无效的文本字符串。
es2019之前,这些字符将替换为特殊字符:
JSON.stringify('\uD83D') // '"�"'
现在, 针对以上字符串,JSON.stringify()会返回转义字符串。
JSON.stringify('\uD83d') // ""\ud83d""
7. String.prototype.{trimStart,trimEnd}
字符串原型上新增trimStart()和trimEnd()方法,用于去掉字符串前后的空格
想要单独去掉字符串前后的空格,截止到es6,我们可以这样写:
const str = " string ";
str.trimLeft(); // => "string "
str.trimRight(); // => " string"
也可以自己写个函数:
String.prototype.TrimStart = function (c) {
if (c == null || c == "") {
var str = this.replace(/^s*/, '');
return str;
}
else {
var rg = new RegExp("^" + c + "*");
var str = this.replace(rg, '');
return str;
}
}
新特性中,trimStart()和trimEnd()这两个函数的作用等同于trimLeft()和trimRight(),可以理解为后者是前者的别名。
const str = " string ";
str.trimStart(); // => "string "
str.trimEnd(); // => " string"
String.prototype.trimLeft.name === 'trimStart' //true
String.prototype.trimRight.name === 'trimEnd' //true
8. Array.prototype.{flat,flatMap}
flat函数,返回一组新的展开的数组。在新特性之前,我们要展开一个二维数组,可能会这么写:
const iterFun = (arr) => {
 while(arr.some(item=> Array.isArray(item))){
arr=[].concat(...arr)
}
return arr
}
const arr1 = [1, 2, [3, 4]];
iterFun(arr1); //[1,2,3,4]
const arr2 = [1, 2, [3, 4, [5, 6, [7, 8]]]];
iterFun(arr2); // [1, 2, 3, 4, 5, 6, 7, 8]
有了flat函数之后,展开函数可就简单多了。ps:flat函数默认展开一层,若想完全展开,传入Infinity即可:
const arr1 = [1, 2, [3, 4]];
arr1.flat(); // [1, 2, 3, 4]
const arr2 = [1, 2, [3, 4, [5, 6, [7, 8]]]];
arr2.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8]
flatMap函数,可以看做是flat函数和map函数的结合体,在展开数组后,可对数组的各项进行操作,如:
const arr = [[4.25,7.88],[ 19.99], 25.5];
arr.flatMap(value => Math.round(value)) //[4,8, 20, 26]
arr.flat().map(value => Math.round(value)) //[4,8, 20, 26]
再看一个关于flatMap函数的例子,这次的效果就相当于对数组的各项操作之后,再进行展开:
const sentence = ["This is a", "regular", "sentence"];
sentence.flatMap(x => x.split(" ")); // ["This","is","a","regular", "sentence"]
arr.map(x => x.split(" ")).flat() // ["This","is","a","regular", "sentence"]
arr.flat().map(x => x.split(" ")) //[["This","is","a"], ["regular"], ["sentence"]
二. 9个ES2020新特性
1. String.prototype.matchAll
matchAll方法接收正则表达式,返回一个迭代对象
我们先用match方法举个例子:
const str = 'Dr. Smith and Dr. Anderson';
const re1 = /(Dr\. )\w+/g;
const res1 = str.match(re1); //["Dr. Smith", "Dr. Anderson"]
const re2= /(Dr\. )\w+/;
const res2 =str.match(re2); //["Dr. Smith", "Dr. ", index: 0, input: "Dr. Smith and Dr. Anderson", groups: undefined]
从res1和res2的结果中可以看出,res1没有匹配到子项(即Dr. ),res2中返回了第一个匹配项。
新特性matchAll,兼顾了以上两种返回结果,既可以获取全部匹配项,同时返回所有子项。
const re = /(Dr\. )\w+/g;
const str = 'Dr. Smith and Dr. Anderson';
const matches = str.matchAll(re);
for (const match of matches) {
console.log(match);
}
// ["Dr. Smith", "Dr. ", index: 0, input: "Dr. Smith and Dr. Anderson", groups: undefined]
// ["Dr. Anderson", "Dr. ", index: 14, input: "Dr. Smith and Dr. Anderson", groups: undefined]
2. Dynamic-import
import(module) 按需加载,用于动态加载由应用合并(Bundling)中代码切分阶段所生成的模块
通过import方法异步导入(支持关键字await)的模块返回一个promise对象,我们可以在任何地方(不同于只在模块头部定义导入,而是可在代码任何位置)调用,比如某个点击事件中,示例代码如下:
onClick = () => {
import('/modules/my-module.js')
.then(module => {
// Do something **with** the module.
})
}
let module = await import('/modules/my-module.js');
3. BigInt
BigInt 是一种内置对象,它提供了一种表示大于2^53-1的整数的方法。BigInt类型作为一种新的数据类型,它可以表示任意大的整数。
typeof 9007199254740993n; // 'bigint'
ES2019之前,Js 中 Number类型只能安全的表示-(2^53-1)至 2^53-1 范的值,超出这个范围的整数计算或者表示会丢失精度,如
var num = Number.MAX_SAFE_INTEGER; // 9007199254740991
num = num + 1; // 9007199254740992
num = num + 1; // 9007199254740992,无法正常运算
9007199254740992 === 9007199254740993 //true,无法正常比教
ES2020中为了解决大整数计算的问题,引入了BigInt 类型,它有两种定义方法
1.字面量表示,在数字的末尾带有一个n const bigIntNum = 9007199254740993n; 2.通过BigInt函数创造,参数(必传)可为整型、十六进制、二进制的字符串形式
const bigNum = BigInt(1); //1n
const bigHex = BigInt("0x1f"); //31n
const bigBin= BigInit('0b110011') //51n
const bigDeci= BigInit(1.5) // The number 1.5 cannot be converted to a BigInt because it is not an integer
算数运算,如 _ * / **,注意不能和Number实例进行混合运算
1.操作数必须都是bigints类型
add = 9n + 12 //Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
2.bigints不储存整数
division = -9n/2n //-4n
比较运算,如 > < == ===
1n === 1 //false,两数的类型不一样
1n == 1 // true, 仅比较两数值
2n > 1 // true
23243454698789789789n < 86786758575675646456456n //true
逻辑运算,如 || && !
or = 1n || 2n // 1n
and = 0 && 2n // 0
位移运算,如 >> << ,注意不能和Number实例进行混合运算,也不能用于无符号右移
left = 2n << 5n // 64n
right = 2n >> 5n // 0n
noSymbolRight = 2n >>> 5n; // Uncaught TypeError: BigInts have no unsigned right shift, use >> instead
mixRight = 2n >> 1 // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
注意:bigInt类型不能用于math方法中
Math.abs(-120n) //Uncaught TypeError: Cannot convert a BigInt value to a number at Math.abs
4. Promise.allSettled
Promise.allSettled(iterable),iterable中每个成员都是promise。该方法保证了在并发任务中,无论一个任务正常或者异常,都会返回对应的的状态(fulfilled 或者 rejected)与结果(value 或者 reason)。
我们都知道 Promise.all 和promise.race,前者具有并发执行异步任务的能力,但是只要某个任务被拒绝,所有任务都会挂掉,Promise直接进入reject状态;后者一旦迭代器中的某个任务被解决或拒绝,返回的promise就会解决或拒绝。
Promise.all([
Promise.resolve('a'),
Promise.reject('b'),
])
.then(arr => console.log(arr))
.catch(error => console.log(error) )// b
Promise.race([
Promise.resolve('a'),
Promise.reject('b'),
])
.then(arr => console.log(arr)) // a
.catch(error => console.log(error) )
那有没有一个方法可以输出所有任务的执行结果呢?Promise.allSettled()就是解决这个问题的。该方法返回一个promise,将给定的所有promise执行后的结果(无论是解决还是拒绝)解析称一个对象,放在数组里返回:
Promise.allSettled([
Promise.resolve('a'),
Promise.reject('b'),
])
.then(arr => console.log(arr)); // { status: 'fulfilled', value: 'a' }, { status: 'rejected', reason: 'b' }
5. GlobalThis
globalThis 提供一种标准化方式访问全局对象,不需要考虑环境的问题 之前,我们要获取全局对象,需要考虑到环境的问题,比如node 中通过 global, web中通过 window, self ,webwork中通过self获取。所以常常是写个函数,通过判断环境来返回全局对象
const getGlobal = () => {
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 objec');
};
es2020中,通过globalThis来访问全局对象,不用担心环境问题啦
//浏览器:
globalThis === window //true
globalThis === self //true
//node:
globalThis === global //true
//webwork:
globalThis === self //true
6. For-in-order
ECMA-262 几乎全部未指明 for (a in b) 顺序 ,由于不同的引擎实现方式不同,所以for in顺序也会存在差异。es2020中明确只要保证for in迭代过程中保持以下几个特点,就能保证各大引擎的输出的顺序有一定的规律性。
-
迭代对象或原型的任何内容都不是代理,类数组,模块名称空间对象或主机对象
-
对象及其原型链没有更改,即不将属性添加到原型链上,可以添加到对象本身
-
属性的可枚性没有变化
-
没有属性被删除,然后重新添加
-
没有从原型链中的任何内容删除属性
//迭代对象为类数组,postMessage发送消息后,a的所有权不再属于发送方。 let a = new Uint8Array(3); let done = false; for (let key in a) { console.log(key); if (!done) { postMessage(null, '*', [a.buffer]); done = true; } } // 0 //迭代时修改了可枚举属性 let a = { x: 0, y: 0, z: 0 }; for (let key in a) { console.log(key); Object.defineProperty(a, 'y', { enumerable: false, configurable: true, writable: true, value: 0 }); } //{ x: 0, z: 0,y: 0, }; // 迭代时修改了原型 let derived = { x: 0 }; let baseOne = { y: 0 }; let baseTwo = { z: 0 }; Object.setPrototypeOf(derived, baseOne); for (let key in derived) { Object.setPrototypeOf(derived, baseTwo); console.log(key); } //{'x':0}
输出的规律也有以下几个特点:
- 在返回之前被删除的属性,且不再其进行重新添加,该属性不返回
- 对于非整数属性,按插入顺序返回属性
- 在迭代后再添加到要迭代对象上的属性,不返回。
let x = [];
x.a = 0;
x['2'] = 0;
x.c = 0;
x.b = 0;
x[0] = 0;
x[-1] = 0;
x[3] = 0;
for (let key in x) {
console.log(key.toString());
delete x[3];
delete x.a;
Object.defineProperty(x, 3, { enumerable: true, configurable: true, writable: true, value: 0 });
x.a= 0;
x.d = 0;
} // 0 2 3 c b -1
7. Optional Chaining
可选链:通过操作符?.让我们在查询多层级的对象时,不再需要进行冗余的各种前置校验。? 表示如果问号左边表达式有值, 就会继续查询问号后面的字段,否则返回undefined.
之前在查询多层级对象时,我们一般会这么写:
const obj2 = {
prop1: {
prop2: {
prop3: 1
}
}
};
const val = obj2 && obj2.props1 && obj2.props1.props2 && obj2.props1.props2.props3; //1
支持了可选链之后,写起来就轻松多了
const val = obj2?.prop1?.prop2?.prop3); //1
8. Nullish coalescing Operator
空值合并运算符(??):解决了在赋值时,空字符串、 flase、0在逻辑运算符被视为false无法取值只能取默认值的问题
举个例子,如下的fun方法,如果num为null或未定义时取默认值,但是传参为0情况下输出的却是默认值,当然这不是我们想要的结果:
const fun = (num) => {
return num || '默认值'
}
fun(0) // '默认值'
采用es2020新特性,就完美解决,??表示在只有null或者undefined才会取默认值
const fun = (num) => {
return num ?? '默认值'
}
fun(0) // 0
fun(null) // '默认值'
9. Import-meta
import.meta:模块的元数据对象,包含了依赖于当前宿主环境的模块元数据。 当前其中最受开发人员关注的是 url 属性,url 给出了当前模块文件的 URL 字符串。开发人员可使用该 URL 字符串去导入相关模块,或是根据模块当前信息有条件地执行一些操作。ES2020 提案中给出了如下示例代码,获取当前模块对应的 hamster.jpg 文件
在之前的用法中,假如要获取某个脚本上的数据,我们需要这么写:
<script data-option="value" src="library.js"></script>
const value = document.currentScript.dataset.option;
在ES2020中,提出两点 1.不再通过全局变量的形式获取数据,而是通过Import.meta获取元数据,2.提供通用性拓展机制,而不是对每个元数据进行标准化。
这两点怎么理解呢,举个例子就明白了了。拿ES2020提案中给出了官方示例,针对上面第一点,通过import.meta.url获取当前模块文件的URL字符串,然后根据该字符串去导入相关模块,第二点通过import.meta.scriptElement.dataset.size获取元数据size的值。
//当加载a模块时,将加载同级图片hamster.jpg,并根据脚本数据size显示图像的大小
<script type="module" src="a.mjs" data-size="500"></script>
(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);
})();
具体项目中使用的话,需要安装依赖npm install babel-preset-env --save-dev,然后在babelrc文件中加入"presets": ['env']即可,就是让babel根据运行环境自动将ES2015+的代码转换为es5。
注意:
1.目前babel是不支持bigint的字面量语法的转换,所以想要使用bigint类型,还是采用函数BigInt()的方式吧。
2.由于规范未说明该import.meta对象内部应包含什么内容,所以babel未正式提供用于语法转换的插件,目前该特性暂不可用。tips:在vscode中会对js文件默认开启ts校验,在代码里使用新特性语法,会报错ts错误,所以需要在首选项-设置-扩展-TypeScript: 关闭Javascript › Validate: Enable选项。
结束语:感谢大家看到这里,以上就是我要介绍的17个ES新特性,希望能带给大家一点收获!