ES2021
1.replaceAll
replaceAll
返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉
const str = 'hello world';str.replaceAll('l', ''); // "heo word"
2. Promise.any
Promise.any()
接收一个Promise可迭代对象,只要其中的一个promise
成功,就返回那个已经成功的promise
。如果可迭代对象中没有一个 promise
成功(即所有的 promises
都失败/拒绝),就返回一个失败的 promise
const promise1 = new Promise((resolve, reject) => reject('我是失败的Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失败的Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList).then(values=>{
console.log(values);
})
.catch(e=>{
console.log(e);
});
3.WeakRefs(待补充)
使用WeakRefs
的Class类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)
4. 逻辑运算符和赋值表达式
逻辑运算符和赋值表达式,新特性结合了逻辑运算符(&&,||,??)
和赋值表达式而JavaScript已存在的 复合赋值运算符有:
a ||= b
//等价于
a = a || (a = b)
a &&= b
//等价于
a = a && (a = b)
a ??= b
//等价于
a = a ?? (a = b)
5. 数字分隔符
数字分隔符,可以在数字之间创建可视化分隔符,通过_下划线来分割数字,使数字更具可读性
const money = 1_000_000_000;
//等价于
const money = 1000000000;
1_000_000_000 === 1000000000; // true
ES2020
1. 类的私有变量
类的主要目的之一是将代码包含到可重用的模块中。
你创建一个在许多不同地方使用的类,你可能不希望它内部的所有内容都是全局可用的。
现在,通过在变量或函数前面添加一个简单的哈希符号,我们可以将它们完全保留为类内部使用。
class Message {
// 定义一个私有变量
#message = "Howdy"
greet() { console.log(this.#message) }
}
const greeting = new Message()
// 通过实例方法调用访问
greeting.greet() // Howdy
// 直接访问类变量
console.log(greeting.#message) // Private name #message is not defined
2.Promise.allSettled
当我们在使用多实例promises
的时候,特别是当他们互相依赖的时候,记录每次调试发生的错误是非常有用的,通过使用Promise.allSettled
,我们可以创建一个仅仅当全部的promises
完成才返回的promise
,他将返回一个包含每个promise
执行结果的数组。
const p1 = new Promise((res, rej) => setTimeout(res, 1000));
const p2 = new Promise((res, rej) => setTimeout(rej, 1000));
Promise.allSettled([p1, p2]).then(data => console.log(data));
// [
// Object { status: "fulfilled", value: undefined},
// Object { status: "rejected", reason: undefined}
// ]
3.空值合并运算符(??)
空值合并运算符(??)
是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
与逻辑或(||)
操作符不同,逻辑或会在左操作数为 假值 时返回右侧操作数。也就是说,如果你使用 || 来为某些变量设置默认的值时,你可能会遇到意料之外的行为。比如为假值(例如,''
或 0
)时。见下面的例子。
const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// expected output: 0
4.可选链式操作符(?.)
与空值合并操作符类似,JavaScript
在处理错误值时可能无法按我们希望的方式工作。如果他是未定义的,我们可以返回一个值,但是如果到它的路径是未定义的呢?
通过在点表示法之前添加一个问号,我们可以使值的路径的任何部分成为可选的,这样我们仍然可以与它交互。
如果访问对象上不存在的属性上的属性,使用.操作符会直接报错。
let person = {};
// 如果person对象不包含profile会报错
console.log(person.profile.name ?? "Anonymous"); // person.profile is undefined
// 下面的路径是可选的,如果person对象不包含profile属性直接返回"Anonymous"
console.log(person?.profile?.name ?? "Anonymous");
console.log(person?.profile?.age ?? 18);
5.BigInt
我们不会深入讨论技术细节,但是由于JavaScript
处理数字的方式,当您达到足够高的级别时,就会出现一些意料之外的事情。JavaScript
能处理的最大数字是2的53次方
,我们可以使用Number的MAX_SAFE_INTEGER
属性得到这个值。
const max = Number.MAX_SAFE_INTEGER;
console.log(max); // 9007199254740991
如果超过了这个范围,事情就会变得有点奇怪……
console.log(max + 1); // 9007199254740992
console.log(max + 2); // 9007199254740992
console.log(max + 3); // 9007199254740994
console.log(Math.pow(2, 53) == Math.pow(2, 53) + 1); // true
我们可以使用新的BigInt
数据类型来解决这个问题。通过把字母n放在末尾,我们可以开始使用并与大得离谱的数字进行交互。我们无法将标准数字与BigInt
数字混合在一起,因此任何数学运算都需要使用BigInt
来完成。
const bigNum = 100000000000000000000000000000n;
console.log(bigNum * 2n); // 200000000000000000000000000000n
6.动态引入(Dynamic Import)
如果你有一个工具函数文件,其中一些函数可能很少被使用,将他们完整导入可能只是浪费资源。现在我们可以使用async/await
来动态地导入我们需要的依赖项。
这与我们当前Parcel
的设置不兼容,因为我们使用的导入只能在Node.js
环境中工作。
math.js
const add = (num1, num2) => num1 + num2;
export { add };
index.js
const doMath = async (num1, num2) => {
if (num1 && num2) {
const math = await import('./math.js');
console.log(math.add(5, 10));
};
};
doMath(4, 2);
ES2019
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)
,那么在解析过程中会报错。
JSON.parse('"\u2028"');
// SyntaxError
现在ES2019
对它们提供了支持。
JSON.parse('"\u2028"');
// ''
7.JSON.stringify() 加强格式转化
我们看一下熟知的emoji
表现:
'😎'.length;
// 2
JavaScript
将emoji
解释为两个字符的原因是UTF-16
将emojis
(以及其他不寻常的字符)表示为两个代理项的组合。我们的emoji
用字符'\uD83D'
和'\uDE0E'
编码。但是如果试图单独编写这样一个字符,例如'\uD83D'
,则会认为这是一个无效的文本字符串。在早期版本中,这些字符将替换为特殊字符:
JSON.stringify('\uD83D');
// '"�"'
现在在字符代码之前插入转义字符,结果仍是可读且有效的UTF-8/UTF-16
代码:
JSON.stringify('\uD83D');
// '"\\ud83d"'
8.Array.prototype.sort() 更加稳定
之前,规范允许不稳定的排序算法,如快速排序。
let array = [
{a: 1, b: 2},
{a: 2, b: 2},
{a: 1, b: 3},
{a: 2, b: 4},
{a: 5, b: 3}
];
array.sort((a, b) => a.a - b.a);
// [{a: 1, b: 2}, {a: 1, b: 3}...] / [{a: 1, b: 3}, {a: 1, b: 2}...]
在之前的排序中,可能出现[{a: 1, b: 2}, {a: 1, b: 3}...]、[{a: 1, b: 3}, {a: 1, b: 2}...]
等多种情况。
现在所有主流浏览器都使用稳定的排序算法。实际上,这意味着如果我们有一个对象数组,并在给定的键上对它们进行排序,那么列表中的元素将保持相对于具有相同键的其他对象的位置。
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 () {}'
ES2018
1.async/await 异步迭代
ES8
为我们带来async/await
,使我们能在同步的写法中执行异步函数,但是在循环中:
async function foo(array) {
for (let i of array) {
await doSomething(i);
}
}
上面代码执行不符合预期,循环本身依旧保持同步,并在在内部异步函数之前全部调用完成。
ES2018
引入异步迭代器(asynchronous iterators)
,使得await
可以和for...of
循环一起使用,以串行的方式运行异步操作。
async function foo(array) {
for await (let i of array) {
doSomething(i);
}
}
2.Promise.finally()
ES6
为我们带来了Promise
,但是它的结果要么成功then
要么失败catch
,使得我们的一些逻辑,如执行状态修改,结束回调都得在两边写一遍。
选择有了finally()
,逻辑只可以放在一个地方了,这有点像以前jQuery
ajax
的complete
。
return new Promise((reslove, reject) => {
// ...
}).then((res) => {
// reslove
}).catch((err) => {
// reject
}).finally(() => {
// complete
});
finally()
没有参数传入。
3.Rest/Spread 属性
ES2015
引入了Rest
参数和扩展运算符。当时三个点...
仅用于数组。
Rest
参数语法允许我们将一个剩余参数表示为一个数组。
function foo(a, b, ...rest) {
// a = 1
// b = 2
// rest = [3, 4, 5]
}
foo(1, 2, 3, 4, 5);
展开操作符则是将数组转换成可传递给函数的单独参数。
const nums = [1, 2, 3, 4, 5];
Math.max(...nums));
// 5
现在对象也可以使用它们了。
function foo({ a, b, ...rest }) {
// a = 1
// b = 2
// rest = { c: 3, d: 4, e: 5 }
foo({
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
});
const object = {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
};
const { a, ...rest } = object;
// a = 1
// b = 2
// rest = { c: 3, d: 4, e: 5 }
跟数组一样,Rest
参数只能在声明的结尾
处使用。
4.正则表达式 s(dotAll模式)标记
在正则中,.可以匹配任意字符,除了换行符。
/hello.es9/.test('hello\nes9');
// false
ES2018
引入了dotAll
模式,通过使用标记s
选项,.
就可以匹配换行符。
/hello.es9/s.test('hello\nes9');
// true
5.正则表达式 Unicode 转义
目前在正则中,可以通过字符集的名称来匹配字符。如s代表空白
/^\s+$/u.test(' ');
// true
在ES2018
添加了Unicode
属性转义,形式为\p{...}
和\P{...}
,在正则表达式中使用标记u(unicode)
选项。
/^\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
6.lookbehind 反向断言
目前JavaScript
在正则表达式中支持先行断言(lookahead)
。这意味着匹配会发生,但是断言没有包含在整个匹配字段中。
如匹配字符串“10 hours”
中紧跟着是”hours”
的数字:
const reg = /\d+(?= hours)/u;
const matched = reg.exec('10 hours');
matched[0];
// 42
匹配字符串“10 minutes”
中紧跟着不是”hours”
的数字:
const reg = /\d+(?! hours)/u;
const matched = reg.exec('10 minutes');
matched[0];
// 42
ES2018
引入以相同方式工作但是匹配前面的反向断言(lookbehind)
。
匹配字符串“hours10”
中”hours”
后面的数字:
const reg = /(?<=hours)\d+/u;
const matched = reg.exec('hours10');
matched[0];
// 10
匹配字符串“minutes10”
中数字前面不是“hours”
:
const reg = /(?<!hours)\d+/u;
const matched = reg.exec('minutes10');
matched[0];
// 10
7.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
允许命名捕获组使用符号?<name>
, 可以指定小括号中匹配内容的名称放在groups
里,这样可以提高代码的可读性。
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\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 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2018-12-31'.replace(reg, '$<year>年$<month>月$<day>日');
// 2018年12月31日
8.非转义序列的模板字符串
ES2018
移除对 ECMAScript
在带标签的模版字符串中转义序列的语法限制。
之前,\u
开始一个 unicode
转义,\x
开始一个十六进制
转义,\
后跟一个数字开始一个八进制
转义。这使得创建特定的字符串变得不可能,例如Windows文件路径C:\uuu\xxx\111
。
ES2017
1.String.prototype.padStart / String.prototype.padEnd(字符串填充)
正如其名,这俩函数的作用就是在字符串的头部和尾部增加新的字符串,并且返回一个具有指定长度的新的字符串。你可以使用指定的字符、字符串或者使用函数提供的默认值-空格来填充源字符串。
// 语法
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])
padStart
从字符串开端左边开始填充,并且返回给定长度的字符串,如果不给定填充字符串,将使用默认字符空格,具体示例如下:
'es8'.padStart(2); // 'es8',如果长度小于原字符串,返回原字符串
'es8'.padStart(5); // ' es8'
'es8'.padStart(6, 'woof'); // 'wooes8',如果填充字符串+原字符串长度大于
给定长度,则从填充字符串左边开始截取(这是由于与其他语言PHP
、Ruby
保持一致)
'es8'.padStart(14, 'wow'); // 'wowwowwowwoes8'
'es8'.padStart(7, '0'); // '0000es8',如果填充字符串+原字符串长度小于给定长度,则从填充字符串重复填充
padEnd
从字符串尾端右边开始填充,并且返回给定长度的字符串,如果不给定填充字符串,将使用默认字符空格,具体示例如下:
'es8'.padEnd(2); // 'es8',如果长度小于原字符串,返回原字符串
'es8'.padEnd(5); // 'es8 '
'es8'.padEnd(6, 'woof'); // 'es8woo',如果填充字符串+原字符串长度大于给定长度,则从填充字符串左边开始截取(这是由于与其他语言PHP、Ruby保持一致)
'es8'.padEnd(14, 'wow'); // 'es8wowwowwowwo'
'es8'.padEnd(7, '6'); // 'es86666',如果填充字符串+原字符串长度小于给定长度,则从填充字符串重复填充
2.Object.values / Object.entries
Object.values
函数将会返回一个数组,该数组的内容是函数参数(一个对象)可遍历属性的属性值。
// 语法
Object.values(obj)
// 示例
const obj = { x: 'xxx', y: 1 };
Object.values(obj); // ['xxx', 1]
const obj = ['e', 's', '8']; // same as { 0: 'e', 1: 's', 2: '8' };
Object.values(obj); // ['e', 's', '8']
// when we use numeric keys, the values returned in a numerical
// order according to the keys
const obj = { 10: 'xxx', 1: 'yyy', 3: 'zzz' };
Object.values(obj); // ['yyy', 'zzz', 'xxx']
Object.values('es8'); // ['e', 's', '8']
Object.entries
函数与Object.values
函数类似,也是返回一个数组,只不过这个数组是对象自身可迭代属性 [key-value]
的对数组。
// 语法
Object.entries(obj)
// 示例
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']]
3.Object.getOwnPropertyDescriptors
该函数返回指定对象(参数)的所有自身属性描述符。所谓自身属性描述符就是在对象自身内定义,不是通过原型链继承来的属性。
Object.getOwnPropertyDescriptors(obj)
obj
参数即为源对象,该函数返回的每个描述符对象可能会有的 key 值分别是:configurable
、enumerable
、writable
、get
、set
和value
。示例代码如下:
const obj = {
get es7() { return 777; },
get es8() { return 888; }
};
Object.getOwnPropertyDescriptor(obj);
// {
// es7: {
// configurable: true,
// enumerable: true,
// get: function es7(){}, //the getter function
// set: undefined
// },
// es8: {
// configurable: true,
// enumerable: true,
// get: function es8(){}, //the getter function
// set: undefined
// }
// }
4.函数参数列表和调用中的尾逗号
此处结尾逗号指的是在函数参数列表中最后一个参数之后的逗号以及函数调用时最后一个参数之后的逗号。ES8
允许在函数定义或者函数调用时,最后一个参数之后存在一个结尾逗号而不报 SyntaxError
的错误。示例代码如下:
// 函数声明时
function es8(var1, var2, var3,) {
// ...
}
// 函数调用时
es8(10, 20, 30,);
ES8的这项新特性受启发于对象或者数组中最后一项内容之后的逗号,如 [10, 20, 30,] 和 { x: 1, } 。
5.异步函数
由 async
关键字定义的函数声明定义了一个可以异步执行的函数,它返回一个 AsyncFunction
类型的对象。异步函数的内在运行机制和 Generator
函数非常类似,但是不能转化为 Generator
函数。
示例代码如下:
function fetchTextByPromise() {
return new Promise(resolve => {
setTimeout(() => {
resolve("es8");
}, 2000);
});
}
async function sayHello() {
const externalFetchedText = await fetchTextByPromise();
console.log(`Hello, ${externalFetchedText}`); // Hello, es8
}
sayHello();
有了 async/await
,我们的代码执行异步看起来像执行同步一样。可以从头到尾读起来非常简单和易懂,因为出现结果顺序和函数题中从头到尾顺序一样啊!
6.共享内存与原子操作
当内存被共享时,多个线程可以并发读、写内存中相同的数据。原子操作可以确保那些被读、写的值都是可预期的,即新的事务是在旧的事务结束之后启动的,旧的事务在结束之前并不会被中断。这部分主要介绍了 ES8 中新的构造函数 SharedArrayBuffer
以及拥有许多静态方法的命名空间对象 Atomic
。
Atomic
对象类似于 Math
对象,拥有许多静态方法,所以我们不能把它当做构造函数。 Atomic
对象有如下常用的静态方法:
add/sub
- 为某个指定的value值在某个特定的位置增加或者减去某个值
and/or/xor
- 进行位操作
load
- 获取特定位置的值
ES2016
1.Array.prototype.includes(数组元素判断)
// 语法
Array.prototype.includes(value:任意值): return boolean
// 例子
['a', 'b', 'c'].includes('a')
> true
['a', 'b', 'c'].includes('d')
> false
includes
方法与 indexOf
方法很相似——下面两个表达式是等价的:
arr.includes(x)
arr.indexOf(x) >= 0
唯一的区别是 includes()
方法能找到 NaN
,而 indexOf()
不行:
[NaN].includes(NaN)
> true
[NaN].indexOf(NaN)
> -1
另外 includes
不会区分 +0
和-0
(这也与其他 JavaScript
特性表现一致):
[-0].includes(+0)
> true
2.Exponentiation Operator(求冥运算)
// 语法
x ** y
// 例子
let a = 2 ** 3 // 2*2*2 = 2^3
let b = 3 ** 2 // 3*3 = 3^2
其实与以下表达式Math.pow(x, y)
结果相同:
let a = 7 ** 12
let b = 2 ** 7
console.log(a === Math.pow(7,12)) // true
console.log(b === Math.pow(2,7)) // true
另外还可以简写:
let a = 7
a **= 12
let b = 2
b **= 7
console.log(a === Math.pow(7,12)) // true
console.log(b === Math.pow(2,7)) // true
原文地址:
作者:白茶_别事
原文:ES12(2021)新增的5个新特性
作者:SunSeekerX
原文:ECMAScript 2020(ES2020)的新增语法
作者:小吉很低调
原文:ES2019(ES10)带来的9个新特性
作者:小吉很低调
原文:ES2018(ES9)新特性
作者:hlemon
原文:ES2016与ES2017特性
参考资料:MDN官网