自EcmaScript2015(es6)推出很多新的js特性后,后续每年都会有一些特性更新,但是后面几年更新的体量远不如es6。本文对es7~es12(2016~2021)版本更新的特性做一些整合。
EcmaScript2016(es7)新特性:
1.Array.prototype.includes var arr = ["a","b","c","d"] arr.includes("c")//true 从开始位置找 arr.includes("c",2)//true 从位置2开始找 arr.includes("c",3)//false 从位置3开始找 arr.includes("c",-2)//true 从倒数第2开始找 2.幂运算符**(简化版Math.pow(x, y)) 5**2 //25
EcmaScript2017(es8)新特性:
1.String padding(字符串填充),分为padStart从前方填充和padEnd从后方填充:
//有两个参数('填满至什么长度','用什么字符填') 'es8'.padStart(2); //'es8',自身已经满2位 'es8'.padStart(5); //' es8',填至5位,没有第二参数,默认用空字符填 'es8'.padStart(6, 'woof'); // 'wooes8',用第二个参数的字符依次填够长度 'es8'.padStart(14, 'wow'); // 'wowwowwowwoes8',第二个参数循环利用来填够长度 //padEnd同理,从字符最后开始填充
2.Object.values(obj)方法,返回一个给定对象自己的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于for-in循环枚举原型链中的属性 ):
const obj = { x: 'xxx', y: 1 }; Object.values(obj); // ['xxx', 1] const obj = ['e', 's', '8']; // 相当于 { 0: 'e', 1: 's', 2: '8' }; Object.values(obj); // ['e', 's', '8'] // 当我们使用数字键值时,返回的是数字排序 // 根据键值排序 const obj = { 10: 'xxx', 1: 'yyy', 3: 'zzz' }; Object.values(obj); // ['yyy', 'zzz', 'xxx'] Object.values('es8'); // ['e', 's', '8']
3.Object.entries(obj)方法,返回一个给定对象自身可遍历属性 [key, value] 的数组, 排序规则和 Object.values 一样。
const obj = { x: 'xxx', y: 1 }; Object.entries(obj); // [['x', 'xxx'], ['y', 1]] const obj = ['e', 's', '8']; Object.entries(obj); // [['0', 'e'], ['1', 's'], ['2', '8']] const obj = { 10: 'xxx', 1: 'yyy', 3: 'zzz' }; Object.entries(obj); // [['1', 'yyy'], ['3', 'zzz'], ['10': 'xxx']] Object.entries('es8'); // [['0', 'e'], ['1', 's'], ['2', '8']]
4.Object.getOwnPropertyDescriptors()方法,用来获取一个对象的所有自身属性的描述符
const obj = { get es8() { return 888; } }; Object.getOwnPropertyDescriptors(obj, 'es8'); //{ //configurable: true //enumerable: true //get: ƒ es8() //set: undefined //} const obj2 = { a:"aa" }; Object.getOwnPropertyDescriptors(obj, "a"); //{ //configurable: true //enumerable: true //value: "aa" //writable: true //}
5.函数参数和函数调用中的尾逗号.当我们在函数参数列表后面加上不必要的逗号,函数参数中的尾逗号可以让编辑器不再抛出错误.
//以下写法不再报错 function es8(var1, var2, var3,) { // ... } es8(10, 20, 30,);
6.Async函数.声明一个异步的函数
function fetchTextByPromise() { return new Promise(resolve => { setTimeout(() => { resolve("es8"); }, 2000); }); } //呈现更清晰的Promise语法.异步函数调用不会阻塞主进程 //await关键字只能用于标有async函数的内部,await一般是等待Promise async function sayHello() { const externalFetchedText = await fetchTextByPromise(); console.log(Hello, ${externalFetchedText}); // Hello, es8 } console.log(1); sayHello(); console.log(2); //1 //2 //Hello, es8
7.共享内存和原子
当共享内存时,多个进程可以在内存中读写数据。原子操作保证了可预测的数据被读写,在下一个操作开始之前,之前所有操作都结束了 及操作不会被打断。这部分介绍一个新的构造函数SharedArrayBuffer和一个有静态方法的命名空间对象Atomics。ShareArrayBuffer和Atomics用于从共享内存位置读取和写入.
ShareArrayBuffer对象用来表示一个泛型的,固定长度的原生二进制数据缓冲区,类似于ArrayBuffer对象。但在某种程度上,它们能被用于在共享内存上创建视图。与ArrayBuffer不同的是,ShareArrayBuffer不能被移除。
EcmaScript2018(es9)新特性:
1.异步迭代器(asynchronous iterators)使得await可以和for...of循环一起使用,以串行的方式运行异步操作。
async function foo(array) { for await (let i of array) { doSomething(i); } }
2.Promise.finally().选择有了finally(),逻辑只可以放在一个地方了,这有点像以前jQuery ajax的complete。
//finally()没有参数传入 return new Promise((reslove, reject) => { // ... }).then((res) => { // reslove }).catch((err) => { // reject }).finally(() => { // complete });
3.Rest/Spread属性. 为对象解构提供了和数组一样的Rest参数()和展开操作符
const myObject = {a: 1,b: 2,c: 3}; const { a, ...x } = myObject; // a = 1 // x = { b: 2, c: 3 } //或者你可以使用它给函数传递参数: restParam({a: 1,b: 2,c: 3}); function restParam({ a, ...x }) { // a = 1 // x = { b: 2, c: 3 } }
4.Named capture groups 正则表达式命名捕获组
目前,正则表达式中小括号匹配的分组是通过索引编号的:
const reg = /(\d{4})-(\d{2})-(\d{2})/u; const matched = reg.exec('2018-12-31'); matched[0]; // 2018-12-12 matched[1]; // 2018 matched[2]; // 12 matched[3]; // 31 //代码可读性较差,并且改变正则表达式的结构有可能改变匹配对象的索引。
ES2018允许命名捕获组使用符号?
const reg = /(?\d{4})-(? \d{2})-(? \d{2})/u; const matched = reg.exec('2018-12-31'); matched.groups.year; // 2018 matched.groups.month; // 12 matched.groups.day; // 31 //命名捕获组也可以使用在replace()方法中。例如将日期转换为“年月日”格式: const reg = /(? \d{4})-(? \d{2})-(? \d{2})/u; '2018-12-31'.replace(reg, '$ 年$ 月$ 日'); // 2018年12月31日
5.正则表达式反向断言(lookbehind)和反向断言(lookbehind)
目前JavaScript在正则表达式中支持先行断言(lookahead)。这意味着匹配会发生,但不会有任何捕获,并且断言没有包含在整个匹配字段中。例如从价格中捕获货币符号:
const reLookahead = /\D(?=\d+)/, match = reLookahead.exec('$123.89'); console.log( match[0] ); // $
ES2018引入以相同方式工作但是匹配前面的反向断言(lookbehind),这样我就可以忽略货币符号,单纯的捕获价格的数字:
const reLookbehind = /(?<=\D)\d+/, match = reLookbehind.exec('$123.89'); console.log( match[0] ); // 123.89
以上是肯定反向断言,非数字\D必须存在。同样的,还存在否定反向断言,表示一个值必须不存在,例如:
const reLookbehindNeg = /(?<!\D)\d+/, match = reLookbehind.exec('$123.89'); console.log( match[0] ); // null
6.正则表达式s(dotAll模式)标记. 在正则中,. 可以匹配任意字符,除了换行符
/hello.es9/.test('hello\nes9'); // false /hello.es9/s.test('hello\nes9'); // true
7.正则表达式Unicode转义
目前在正则中,可以通过字符集的名称来匹配字符。如s代表空白
/^\s+$/u.test(' '); // true
在正则表达式中本地访问 Unicode 字符属性是不被允许的。ES2018添加了 Unicode 属性转义——形式为\p{...}和\P{...},在正则表达式中使用标记 u (unicode) 设置,在\p块儿内,可以以键值对的方式设置需要匹配的属性而非具体内容。例如:
const reGreekSymbol = /\p{Script=Greek}/u; reGreekSymbol.test('π'); // true /^\p{White_Space}+$/u.test(' ') // true 空格 /^\p{Script=Greek}+$/u.test('μετά') // true 希腊字母 /^\p{Script=Latin}+$/u.test('Grüße') // true 匹配拉丁字母 /^\p{Surrogate}+$/u.test('\u{D83D}') // true 匹配单独的替代字符 //此特性可以避免使用特定 Unicode 区间来进行内容类型判断,提升可读性和可维护性。
8.非转义序列的模板字符串.ES2018 移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。
之前,\u开始一个 unicode 转义,\x开始一个十六进制转义,\后跟一个数字开始一个八进制转义。这使得创建特定的字符串变得不可能,例如Windows文件路径 C:\uuu\xxx\111。
EcmaScript2019(es10)新特性:
1.String.prototype.trimStart() / String.prototype.trimEnd()
在接收用户输入的文本,我们经常会把头尾的空格文本去掉,来规避展示的不受控情况。自ES5来,String.prototype.trim() 被用于去除头尾上的空格、换行符等,现在通过 trimStart(),trimEnd() 来头和尾进行单独控制。trimLeft()、trimRight() 是他们的别名。
const string = ' Hello ES2019! '; string.trimStart(); // 'Hello ES2019! ' string.trimEnd(); // ' Hello ES2019!'
2.Object.fromEntries()
ES8为我们引入了Object.entries把一个对象转为[key, value]键值对的形式,可以运用于像Map这种结构中。凡事有来有回,Object.fromEntries()用于把键值对还原成对象结构。
const entries = [ ['foo', 'bar'] ]; const object = Object.fromEntries(entries); // { foo: 'bar' }
3.Array.prototype.flat() / Array.prototype.flatMap()
把数组展平是Array原型给我们带来的新特性,通过传入层级深度参数(默认为1),来为下层数组提升层级。如果想提升所有层级可以写一个比较大的数字甚至是Infinity,当然不推荐这么做。
[1, 2, [3, 4]].flat(); // [ 1, 2, 3, 4 ] [1, 2, [3, 4, [5, 6]]].flat(2); // [ 1, 2, 3, 4, 5, 6 ]
Array.prototype.flatMap() 它是 Array.prototype.map() 和Array.prototype.flat()的组合,通过对map调整后的数据尝试展平操作。
[1, 2, [3, 4]].flatMap(v => { if (typeof v === 'number') { return v * 2 } else { return v.map(v => v * 2) } }) // [2, 4, 6, 8]
4.catch 的参数改为可选.在进行try...catch错误处理过程中,如果没有给catch传参数的话,代码就会报错。有时候我们并不关心错误情况,如:
const isValidJSON = json => { try { JSON.parse(json); return true; } catch (unusedError) { // Unused error parameter return false; } };
在新规范中,我们可以省略catch绑定的参数和括号。
const isValidJSON = json => { try { JSON.parse(json); return true; } catch { return false; } };
5.Symbol.description
Symbol 是ES6中引入的基本数据类型,可以用作对象属性的标识符。描述属性是只读的,可用于获取符号对象的描述,更好了解它的作用。
const symbol = Symbol('This is a Symbol'); symbol; // Symbol(This is a Symbol) Symbol.description; // 'This is a Symbol'
6.JSON Superset超集
之前如果JSON字符串中包含有行分隔符(\u2028) 和段落分隔符(\u2029),那么在解析过程中会报错。现在ES2019对它们提供了支持。
JSON.parse('"\u2028"'); // ''
7.JSON.stringify() 加强格式转化
JSON.stringify() 可能返回U+D800和U+DFFF之间的字符,来作为没有等效UTF-8字符的值。但是,JSON格式需要UTF-8编码。解决方案是,将未配对的替代代码点表示为JSON转义序列,而不是将其作为单个UTF-16代码单元返回。
JSON.stringify('\uDf06\uD834') //'"\\udf06\\ud834"' JSON.stringify('\uDEAD') '"\\udead"'
8.稳定的 Array.prototype.sort()
V8之前的实现对包含10个以上项的数组使用了一种不稳定的快速排序算法。
一个稳定的排序算法是当两个键值相等的对象在排序后的输出中出现的顺序与在未排序的输入中出现的顺序相同时。
但情况不再是这样了,ES10 提供了一个稳定的数组排序:
var fruit = [ { name: "Apple", count: 13, }, { name: "Pear", count: 12, }, { name: "Banana", count: 12, }, { name: "Strawberry", count: 11, }, { name: "Cherry", count: 11, }, { name: "Blackberry", count: 10, }, { name: "Pineapple", count: 10, } ]; // 创建排序函数: let my_sort = (a, b) => a.count - b.count; // 执行稳定的ES10排序: let sorted = fruit.sort(my_sort); console.log(sorted); // //{name:"Blackberry",count:10} //count=10的Blackberry输入顺序排在第一 //{name:"Pineapple",count:10} //...
9.Function.prototype.toString()重新修订
从ES2019开始,Function.prototype.toString()将从头到尾返回源代码中的实际文本片段。这意味着还将返回注释、空格和语法详细信息。
function /* a comment */ foo() {} //之前,Function.prototype.toString()只会返回了函数的主体,但没有注释和空格。 foo.toString(); // 'function foo() {}' //但现在,函数返回的结果与编写的一致。 foo.toString(); // 'function /* a comment */ foo () {}'
10.Hashbang语法.也就是unix用户熟悉的shebang它指定一个解释器(什么将执行JavaScript文件?)。
ES10标准化,我不会对此进行详细介绍,因为从技术上讲,这并不是一个真正的语言特性,但它基本上统一了 JavaScript 在服务器端的执行方式。
$ ./index.js //代替 $ node index.js
11.private、static和公共成员
现在,新的语法字符#(哈希标签)可用于直接在类中定义变量,函数,getter和setter,以及构造函数和类方法。
class Raven extends Bird { #state = { eggs: 10}; // getter get #eggs() { return state.eggs; } // setter set #eggs(value) { this.#state.eggs = value; } #lay(){ this.#eggs++; } constructor() { super(); this.#lay.bind(this); } #render() { /* paint UI */ } }
EcmaScript2020(es11)新特性:
1.BigInt — 任意精度的整数
感谢 Daniel Ehrenberg 让 Number.MAX_SAFE_INTEGER 不再是 JavaScript 的限制。BigInt 是一个新的原语,它可以表示任意精度的整数。你可以通过 BigInt 方法,或是在一个数值后添加 n 后缀,来将一个 number 转换为 bigint 类型。
Number.MAX_SAFE_INTEGER // 9007199254740991 Number.MAX_SAFE_INTEGER + 10 -10 // 9007199254740990 (译者注:精度丢失) BigInt(Number.MAX_SAFE_INTEGER) + 10n -10n // 9007199254740991n (译者注:计算结果为 bigint 类型)
2.String.protype.matchAll()
String.prototype的match() 方法仅返回完整的匹配结果,却不会返回特定正则表达式组(Regex groups)的信息。感谢 Jordan Harband 提出的 String.prototype.matchAll 协议,它返回了远比 match() 多得多的信息——它返回的迭代器不仅包括精确的匹配结果,还有全部的正则模式捕获结果。你还记得由 Gorkem Yakin 和 Daniel Ehrenberg 在 ECMAScript 2018 中新增的命名捕获组吗? matchAll() 方法完美实现了它。我们举个例子说明。
// match() 方法 const text = "From 2019.01.29 to 2019.01.30"; const regexp = /(?\d{4}).(? \d{2}).(? \d{2})/gu; const results = text.match(regexp); console.log(results); // [ '2019.01.29', '2019.01.30' ] const results2 = Array.from(text.matchAll(regexp)); console.log(results2); // [ // [ // '2019.01.29', // '2019', // '01', // '29', // index: 5, // input: 'From 2019.01.29 to 2019.01.30', // groups: [Object: null prototype] { year: '2019', month: '01', day: '29' } // ], // [ // '2019.01.30', // '2019', // '01', // '30', // index: 19, // input: 'From 2019.01.29 to 2019.01.30', // groups: [Object: null prototype] { year: '2019', month: '01', day: '30' } // ] // ]
3.标准化globalThis对象. 这在ES10之前,globalThis还没有标准化。
在产品代码中,你可以自己编写这个怪物,在多个平台上“标准化”它:
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'); };
但即使这样也不总是奏效。因此,ES10 添加了 **globalThis** 对象,从现在开始,该对象用于在任何平台上访问全局作用域:
// 访问全局数组构造函数 globalThis.Array(0, 1, 2); ⇨ [0, 1, 2] // 类似于 ES5 之前的 window.v = { flag: true } globalThis.v = { flag: true }; console.log(globalThis.v); ⇨ { flag: true }
4.动态导入
ECMAScript 2015 引入了静态模块,与之相对应的是, 由 Domenic Denicola 提出的可以按需获取的动态 import. 该类函数格式(它并非继承自 Function.prototype)返回了一个强大的 promise 函数,使得诸如按需引入、可计算模块名以及脚本内计算均成为可能。
const modulePage = 'page.js'; import(modulePage) .then((module) => { module.default(); }); // (async () => { const helpersModule = 'helpers.js'; const module = await import(helpersModule) const total = module.sum(2, 2); })();
5.Promise.allSettled
自从ECMAScriptES2015支持了仅有的两个promise连接符:Promise.all()和Promise.race()以来,我们终于迎来了Promise.allSettled(),感谢Jason Williams,Robert Pamely和Mathias Bynens.它可以用在处理所有的promise都 settled的情况,无论结果是fulfilled还是rejected.你看,无需catch!
Promise.allSettled([ fetch("https://api.github.com/users/pawelgrzybek").then(data => data.json()), fetch("https://api.github.com/users/danjordan").then(data => data.json()) ]) .then(result => console.log(`All profile settled`));
6.Optional chaining可选链
// 之前 const title = data && data.article && data.article.title // 之后 const title = data?.article?.title
7.Nullish coalescing Operator 空值合并运算符
//设置默认值 const vip = userInfo.vip || '非vip' //以上写法userInfo.vip=0时被判断为false了,而使用空值合并运算符,能获得简洁的写法: const vip = userInfo.vip ?? '非vip'
8.import.meta. import.meta 提议为当前运行的模块添加了一个特定 host 元数据对象。
console.log(import.meta.url) // file:///Users/pawelgrzybek/main.js
9.export * as ns from “mod”
这是对ES规范的有力补充,它允许开发者以新名称导出另一模块的命名空间外部对象。
export * as ns from "mod" // file:///Users/pawelgrzybek/main.js
EcmaScript2021(es12)新特性:
1.String.prototype.replaceAll
replace方法只能是替换字符串中匹配到的第一个实例字符,而不能进行全局多项匹配替换,唯一的办法是通过正则表达式进行相关规则匹配替换.而replaceAll则是返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。
2.Promise.any & AggregateError. 继all,race,allSettled这些批处理方法后,又迎来了一个新的any
race接收promise数组中第一个reslove的值作为自身的reslove,也就是说只要有一个fulfilled,它也会是fulfilled状态。举个例子:
const a = Promise.resolve("ok"); const b = Promise.reject("err1"); const c = Promise.reject("err2"); Promise.any([a, b, c]).then(ret => { console.log(ret); // ok });
而当所有promise都rejected时,那么它将抛出一个AggregateError汇总错误消息:
const a = Promise.reject("err1"); const b = Promise.reject("err2"); const c = Promise.reject("err3"); Promise.any([a, b, c]).catch(err => { console.log(err.errors); // ['err1', 'err2', 'err3'] });
3.Logical assignment operators(逻辑赋值运算符)
??, &&, || 这三也加入了赋值运算符中,举个例子比较形象:
// 原有 a = a ?? b; a = a && b; a = a || b; // 新的 a ??= b; a &&= b; a ||= b;
4.Numeric literal separator(数字分隔符)
//为了提高大数字的可读性,现在可以用 _ 作为数字的分隔符: const num = 1_000_000_000 // 1000000000
5.WeakRefs & FinalizationRegistry
使用WeakRefs的Class类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)注意:要尽量避免使用 WeakRef 和 FinalizationRegistry,垃圾回收机制依赖于 JavaScript 引擎的实现,不同的引擎或是不同版本的引擎可能会有所不同。
这个提案主要包括两个主要的新功能:
1)使用 WeakRef 类创建对象的弱引用.
2)使用 FinalizationRegistry 类对对象进行垃圾回收后,运行用户定义的终结器
它们可以分开使用也可以一起使用。WeakRef 实例不会阻止 GC 回收,但是 GC 会在两次 EventLoop 之间回收 WeakRef 实例。GC 回收后的 WeakRef 实例的 deref() 方法将会返回undefined。
let ref = new WeakRef(obj) let isLive = ref.deref() // 如果 obj 被垃圾回收了,那么 isLive 就是 undefined
FinalizationRegistry 注册 Callback,某个对象被 GC 回收后调用。
const registry = new FinalizationRegistry(heldValue => { // .... }); // 通过 register 注册任何你想要清理回调的对象,传入该对象和所含的值 registry.register(theObject, "some value");