目录
- 一、ES6(ES2015)
- 二、ES7(ES2016)
- 1、Array.prototype.includes()(🌟🌟🌟)
- 2、幂运算符**
- 3、模板字符串扩展
- 三、ES8(ES2017)
- 1、async/await(🌟🌟🌟)
- 2、对象(🌟🌟🌟)
- (1)、Object.values()
- (2)、Object.entries()
- (3)、Object.getOwnPropertyDescriptors()
- 3、padStart() 和 padEnd()(🌟)
- 4、ShareArrayBuffer
- 四、ES9(ES2018)
- 1、for await…of(🌟🌟🌟)
- 2、模板字符串扩展
- 3、正则表达式
- (1)、正则表达式反向断言(🌟🌟🌟)
- (2)、正则表达式 Unicode 转义(🌟🌟🌟)
- (3)、正则表达式新匹配索引 `/s`(🌟🌟🌟)
- (4)、正则表达式命名捕获组(🌟🌟🌟)
- 4、对象扩展操作符(🌟🌟🌟)
- 5、 Promise.prototype.finally()(🌟🌟🌟)
- 五、ES10(ES2019)
- 1、数组
- (1)、Array.prototype.flat() / flatMap()(🌟🌟🌟)
- (2)、Array.prototype.sort() 更加稳定了
- 2、字符串
- (1)、String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight()
- (2)、String.prototype.matchAll(🌟🌟🌟)
- 3、Object.fromEntries()(🌟🌟🌟)
- 4、Symbol.prototype.description
- 5、Function.prototype.toString() 返回注释与空格(🌟)
- 6、try-catch 可以不带异常参数了(🌟🌟🌟)
- 7、JSON 扩展
- (1)、Superset 超集
- (2)、JSON.stringify() 增强 (🌟🌟🌟)
- 8、用 `#` 前缀来定义类的私有变量和方法(🌟🌟🌟)
- 六、ES11(ES2020)
- 1、可选链运算符?.(🌟🌟🌟)
- 2、空合并运算符??(🌟🌟🌟)
- 3、BigInt
- 4、import 和 export(🌟🌟🌟)
- (1)、动态加载模块
- (2)、import.meta
- (3)、export * as ns from 'module'
- 5、GlobalThis(🌟🌟)
- 6、Promise.allSettled(🌟🌟🌟)
- 七、ES12(ES2021)
- 1、逻辑赋值运算符 和 赋值表达式(🌟🌟🌟)
- 2、Promise.any(🌟🌟🌟)
- 3、数字分隔符(🌟🌟🌟)
- 4、WeakRefs
- 5、String.prototype.replaceAll(🌟🌟🌟)
- 八、ES13(ES2022)
- 1、声明类的字段(🌟)
- 2、检测私有字段(🌟)
- 4、类的静态公共方法和字段(🌟)
- 5、类静态初始化块(🌟)
- 6、新的正则匹配索引`/d`(🌟🌟🌟)
- 7、Top-level await(🌟🌟🌟)
- 8、在所有内置的可索引数据上新增`.at()`方法(🌟🌟🌟)
- 9、Object.hasOwn(object, property)(🌟🌟🌟)
一、ES6(ES2015)
- class
- 箭头函数
- 函数参数默认值
- 模板字符串
- 解构赋值
- 模块化(Module)
- 扩展运算符
- Promise
- for…of…
- Symbol
- 迭代器(Iterator)/ 生成器(Generator)
- Set/WeakSet
- Map/WeakMap
- Proxy/Reflect
- Regex对象的扩展
- Array对象的扩展
详见:js ES6新特性js ES6 引用类型的新特性
二、ES7(ES2016)
1、Array.prototype.includes()(🌟🌟🌟)
includes() 方法用来判断一个数组是否包含一个指定的值。包含则返回 true,不包含则返回 false。
2、幂运算符**
幂运算符**,具有与Math.pow()一样的功能,代码如下:
console.log(2**10) // 1024
console.log(Math.pow(2, 10)) // 1024
3、模板字符串扩展
自ES7起,带标签的模版字面量遵守以下转义序列的规则:
- Unicode字符以"\u"开头,例如:\u00A9
- Unicode码位用"\u{}"表示,例如:\u{2F804}
- 十六进制以"\x"开头,例如:\xA9
- 八进制以""和数字开头,例如:\251
这表示类似下面这种带标签的模版是有问题的,因为对于每一个ECMAScript语法,解析器都会去查找有效的转义序列,但是只能得到这是一个形式错误的语法:
latex`\unicode`
// 在较老的ECMAScript版本中报错(ES2016及更早)
// SyntaxError: malformed Unicode character escape sequence
三、ES8(ES2017)
1、async/await(🌟🌟🌟)
虽然Promise可以解决回调地狱的问题,但是链式调用太多,则会变成另一种形式的回调地狱 —— 面条地狱,所以在ES8里则出现了Promise的语法糖async/await,专门解决这个问题。
我们先看一下下面的Promise代码:
fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message)
})
然后再看看async/await版的,这样看起来是不是更清晰了。
async function myFetch() {
let response = await fetch('coffee.jpg')
let myBlob = await response.blob()
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
}
myFetch()
当然,如果你喜欢,你甚至可以两者混用
async function myFetch() {
let response = await fetch('coffee.jpg')
return await response.blob()
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob)
let image = document.createElement('img')
image.src = objectURL
do
2、对象(🌟🌟🌟)
(1)、Object.values()
Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
(2)、Object.entries()
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
(3)、Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
3、padStart() 和 padEnd()(🌟)
- padStart():padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。
- padEnd():padEnd() 方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。
4、ShareArrayBuffer
SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。
代码如下:
let sab = new SharedArrayBuffer(1024) // 必须实例化
worker.postMessage(sab)
Atomics对象 提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操作。方法如下:
- Atomics.add() :将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。
- Atomics.and():将指定位置上的数组元素与给定的值相与,并返回与操作前该元素的值。
- Atomics.compareExchange():如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。
- Atomics.exchange():将数组中指定的元素更新为给定的值,并返回该元素更新前的值。
- Atomics.load():返回数组中指定元素的值。
- Atomics.or():将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。
- Atomics.store():将数组中指定的元素设置为给定的值,并返回该值。
- Atomics.sub():将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。
- Atomics.xor():将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。
- Atomics.wait():检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 “ok”、“not-equal” 或 “time-out”。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用 wait())。
- Atomics.wake():唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。
- Atomics.isLockFree(size):可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回 true;否则就意味着对于该数组,Atomics 对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。
四、ES9(ES2018)
1、for await…of(🌟🌟🌟)
for await…of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,Array-like 对象(比如arguments 或者NodeList),TypedArray,Map, Set和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每个不同属性的值执行语句。
配合迭代异步生成器,例子如下:
async function* asyncGenerator() {
var i = 0
while (i < 3) {
yield i++
}
}
(async function() {
for await (num of asyncGenerator()) {
console.log(num)
}
})()
// 0
// 1
// 2
2、模板字符串扩展
ES9 开始,模板字符串允许嵌套支持常见转义序列,移除对ECMAScript在带标签的模版字符串中转义序列的语法限制。
不过,非法转义序列在"cooked"当中仍然会体现出来。它们将以undefined元素的形式存在于"cooked"之中,代码如下:
function latex(str) {
return { "cooked": str[0], "raw": str.raw[0] }
}
latex`\unicode` // { cooked: undefined, raw: "\\unicode" }
3、正则表达式
(1)、正则表达式反向断言(🌟🌟🌟)
断言是一个对当前匹配位置之前或之后的字符的测试, 它不会实际消耗任何字符,所以断言也被称为“非消耗性匹配”或“非获取匹配”。
正则表达式的断言一共有 4 种形式:
- (?=pattern) 零宽正向肯定断言(zero-width positive lookahead assertion)
- (?!pattern) 零宽正向否定断言(zero-width negative lookahead assertion)
- (?<=pattern) 零宽反向肯定断言(zero-width positive lookbehind assertion)
- (?<!pattern) 零宽反向否定断言(zero-width negative lookbehind assertion)
在ES9之前,JavaScript 正则表达式,只支持正向断言。正向断言的意思是:当前位置后面的字符串应该满足断言,但是并不捕获。例子如下:
'fishHeadfishTail'.match(/fish(?=Head)/g) // ["fish"]
反向断言和正向断言的行为一样,只是方向相反。例子如下:
'abc123'.match(/(?<=(\d+)(\d+))$/) // ["", "1", "23", index: 6, input: "abc123", groups: undefined]
(2)、正则表达式 Unicode 转义(🌟🌟🌟)
正则表达式中的Unicode转义符允许根据Unicode字符属性匹配Unicode字符。 它允许区分字符类型,例如大写和小写字母,数学符号和标点符号。 部分例子代码如下:
// 匹配所有数字
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
// 匹配所有空格
\p{White_Space}
// 匹配各种文字的所有字母,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配各种文字的所有非字母的字符,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu
// 匹配所有的箭头字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true
具体的属性列表可查看:Unicode property escapes
(3)、正则表达式新匹配索引 /s(🌟🌟🌟)
在以往的版本里,JS的正则的.只能匹配 emoji 和 行终结符 以外的所有文本,例如:
let regex = /./;
regex.test('\n'); // false
regex.test('\r'); // false
regex.test('\u{2028}'); // false
regex.test('\u{2029}'); // false
regex.test('\v'); // true
regex.test('\f'); // true
regex.test('\u{0085}'); // true
/foo.bar/.test('foo\nbar'); // false
/foo[^]bar/.test('foo\nbar'); // true
/foo.bar/.test('foo\nbar'); // false
/foo[\s]bar/.test('foo\nbar'); // true
但是在ES9之后,JS正则增加了一个新的标志**/sflags用来表示 dotAll**,这可以匹配任意字符。代码如下:
/foo.bar/s.test('foo\nbar'); // true
const re = /foo.bar/s; // 等价于 const re = new RegExp('foo.bar', 's');
re.test('foo\nbar'); // true
re.dotAll; // true
re.flags; // "s"
(4)、正则表达式命名捕获组(🌟🌟🌟)
在以往的版本里,JS的正则分组是无法命名的,所以容易混淆。例如下面获取年月日的例子,很容易让人搞不清哪个是月份,哪个是年份:
const matched = /(\d{4})-(\d{2})-(\d{2})/.exec('2019-01-01')
console.log(matched[0]); // 2019-01-01
console.log(matched[1]); // 2019
console.log(matched[2]); // 01
console.log(matched[3]); // 01
ES9引入了命名捕获组,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。代码如下:
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31
const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');
matchObj.groups.as // undefined
'as' in matchObj.groups // true
4、对象扩展操作符(🌟🌟🌟)
ES6中添加了数组的扩展操作符,让我们在操作数组时更加简便,美中不足的是并不支持对象扩展操作符,但是在ES9开始,这一功能也得到了支持,例如:
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }
上面便是一个简便的浅拷贝。这里有一点小提示,就是Object.assign() 函数会触发 setters,而展开语法则不会。所以不能替换也不能模拟Object.assign() 。
如果存在相同的属性名,只有最后一个会生效。
5、 Promise.prototype.finally()(🌟🌟🌟)
finally()方法会返回一个Promise,当promise的状态变更,不管是变成rejected或者fulfilled,最终都会执行finally()的回调。
例子如下:
fetch(url)
.then((res) => {
console.log(res)
})
.catch((error) => {
console.log(error)
})
.finally(() => {
console.log('结束')
})
五、ES10(ES2019)
1、数组
(1)、Array.prototype.flat() / flatMap()(🌟🌟🌟)
flat() 方法用来将数组扁平化。通过传入层级深度参数(默认为1),来为下层数组提升层级。
[1, 2, [3, 4]].flat();
// [ 1, 2, 3, 4 ]
[1, 2, [3, 4, [5, 6]]].flat(2);
// [ 1, 2, 3, 4, 5, 6 ]
flatMap()它是 map() 和 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]
(2)、Array.prototype.sort() 更加稳定了
之前,规范允许不稳定的排序算法,现在所有主流浏览器都使用稳定的排序算法。实际上,这意味着如果我们有一个对象数组,并在给定的键上对它们进行排序,那么列表中的元素将保持相对于具有相同键的其他对象的位置。
2、字符串
(1)、String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight()
在ES5中,我们可以通过trim()来去掉字符首尾的空格,但是却无法只去掉单边的,但是在ES10之后,我们可以实现这个功能。
如果我们要去掉开头的空格,可以使用trimStart()或者它的别名trimLeft(),同样的,如果我们要去掉结尾的空格,我们可以使用trimEnd()或者它的别名trimRight()。
(2)、String.prototype.matchAll(🌟🌟🌟)
matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。并且返回一个不可重启的迭代器。
var regexp = /t(e)(st(\d?))/g
var str = 'test1test2'
str.match(regexp) // ['test1', 'test2']
str.matchAll(regexp) // RegExpStringIterator {}
[...str.matchAll(regexp)] // [['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4], ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]]
3、Object.fromEntries()(🌟🌟🌟)
Object.fromEntries() 方法把键值对列表转换为一个对象,它是Object.entries()的反函数。
// Object.entries()
const object1 = {
a: 'somestring',
b: 42
}
for (let [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`)
}
// "a: somestring"
// "b: 42"
// Object.fromEntries()
const entries = new Map([
['foo', 'bar'],
['baz', 42]
])
const obj = Object.fromEntries(entries)
console.log(obj) // Object { foo: "bar", baz: 42 }
4、Symbol.prototype.description
description 是一个只读属性,它会返回Symbol对象的可选描述的字符串。与 Symbol.prototype.toString() 不同的是它不会包含Symbol()的字符串。
Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description; // ""
Symbol().description; // undefined
// 具名 symbols
Symbol.iterator.toString(); // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"
//全局 symbols
Symbol.for('foo').toString(); // "Symbol(foo)"
Symbol.for('foo').description; // "foo"
5、Function.prototype.toString() 返回注释与空格(🌟)
在以往的版本中,Function.prototype.toString()得到的字符串是去掉空白符号的函数的主体,但是从ES10开始会保留这些空格,函数返回的结果与编写的一致。
6、try-catch 可以不带异常参数了(🌟🌟🌟)
在以往的版本中,try-catch里catch后面必须带异常参数,但是在ES10之后,这个参数却不是必须的,如果用不到,我们可以不用传,例如:
try {
console.log('111')
} catch {
console.error('222')
}
7、JSON 扩展
(1)、Superset 超集
之前如果JSON字符串中包含有行分隔符(\u2028) 和段落分隔符(\u2029),那么在解析过程中会报错。现在 ES10 对它们提供了支持。
// 以前
JSON.parse('"\u2028"');// SyntaxError
// ES10
JSON.parse('"\u2028"');// ''
(2)、JSON.stringify() 增强 (🌟🌟🌟)
JSON.stringify在 ES10 修复了对于一些超出范围的 Unicode展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
// \uD83D\uDE0E emoji 多字节的一个字符
console.log(JSON.stringify('\uD83D\uDE0E')) // "😎"
// 如果我们只去其中的一部分 \uD83D 这其实是个无效的字符串
// 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列
console.log(JSON.stringify('\uD83D')) // "\ud83d"
8、用 # 前缀来定义类的私有变量和方法(🌟🌟🌟)
ES10 定义了,用 # 前缀来定义类的私有方法和字段。
class User {
name;
#password;
get #password () {
return #password;
}
#clear () {
this.name = null;
this.#password = null;
}
}
六、ES11(ES2020)
1、可选链运算符?.(🌟🌟🌟)
可选链操作符 ?.允许读取位于连接对象链深处的属性的值,判断该值是否存在,若存在则继续执行,若不存在,则返回短路运算的值undefined。
const obj = {a:1};
obj?.a && console.log(obj.a);// 1
obj?.b && console.log(obj.b);// undefined
2、空合并运算符??(🌟🌟🌟)
ES11引入了新的Null判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
于是,判断一个变量的值为null或undefined时,给定默认值,可以这样写了:
const userName = userinfo?.userName ?? 'userName';
3、BigInt
在BigInt出来以后,JS的原始类型便增加到了7个,如下:
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (ES6)
- BigInt (ES10)
Bigint 类型用来表示大于 253-1 的整数。
创建一个 Bigint 类型的值,可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,也可以调用函数BigInt():
const b = 10n; // 追加 n
const c = BigInt(10);
b === c;// true
判断 Bigint 类型:
typeof 10; // 'number'
typeof 10n; // 'bigint'
在以往的版本中,我们有以下的弊端:
// 大于2的53次方的整数,无法保持精度
2 ** 53 === (2 ** 53 + 1)
// 超过2的1024次方的数值,无法表示
2 ** 1024 // Infinity
在ES10引入BigInt之后,这个问题便得到了解决。
【注意】
- BigInt 和 Number不是严格相等的,但是宽松相等的。
- +、*、-、**、% 操作符可以和 BigInt 一起使用。
- 除 >>> (无符号右移)之外的位操作也可以支持。因为 BigInt 都是有符号的,>>> (无符号右移)不能用于 BigInt。
- BigInt 不支持单目 + 运算符。
- / 操作符对于整数的运算也没问题。可是因为这些变量是 BigInt 而不是 BigDecimal ,该操作符结果会向零取整,也就是说不会返回小数部分。
4、import 和 export(🌟🌟🌟)
(1)、动态加载模块
静态的import 语句用于导入由另一个模块导出的绑定。无论是否声明了 严格模式,导入的模块都运行在严格模式下。这就要求:“在浏览器中,import 语句只能在声明了 type=“module” 的 script 的标签中使用”。但是在ES10之后,我们有动态 import(),它不需要依赖 type=“module” 的script标签。
const main = document.querySelector("main")
for (const link of document.querySelectorAll("nav > a")) {
link.addEventListener("click", e => {
e.preventDefault()
import('/modules/my-module.js')
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
})
})
}
另外,在以前我们根据条件导入模块时,不得不使用 require():
if(XXX) {
const menu = require('./menu');
}
如今可以替换为:
if(XXX) {
const menu = import('./menu');
}
(2)、import.meta
import.meta 会返回一个对象,有一个 url 属性,返回当前模块的 url 路径,只能在模块内部使用。
<script src='./main.js' type="module"></script>
//main.js
console.log(import.meta);
// {url: "http://localhost:8080/main.js"}
(3)、export * as ns from ‘module’
ES2020新增了 export * as XX from 'module',和 import * as XX from 'module' 语法。
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
//menu.js
export * as ns from './info';
可以理解为是将下面两条语句合并为一句:
import * as ns from './info';
export { ns };
【注意】export * as ns from './info'实际上并没有将 ns 导入进当前模块,只是相当于对外转发了这个接口,导致当前模块不能直接使用 ns。
模块的接口改名和整体输出,也可以采用这种写法。
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
5、GlobalThis(🌟🌟)
globalThis属性包含类似于全局对象 this值。所以在全局环境下,我们有:
globalThis === this // true
6、Promise.allSettled(🌟🌟🌟)
先看一看 Promise.all 或者 Promise.race。
Promise.all 示例:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('成功了'), 1000);
})
.then(res => res)
.catch(e => e => console.log('p1:' + e));
const p2 = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('request timeout')), 2000);
})
.then(res => res);
.catch(e => console.log('p2:' + e));
const p = Promise.all([p1, p2]);
p.then((results) => {
console.log(results);
}).catch(err => {
console.log('settled:' + err);
});
由上述代码可知:
- p1和p2实例状态都为fulfilled时,p的状态才会变成fulfilled,此时p1、p2的返回值组成一个数组,传递给p的回调函数
- 只要p1、p2之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race 示例:
const p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('成功了'), 2000);
});
const p4 = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('request timeout')), 1000);
});
const race = Promise.race([p3, p4]);
race.then(data => {
console.log('success:', data);
}).catch(error => {
console.log('error:', error);
});
由上述代码可知:p3和p4其中任意一个实例返回状态后,race的状态就会发生改变,并且不会再变。
Promise.all 或者 Promise.race 有的时候并不能满足我们的需求。比如,我们需要在所有的 promise 都结束的时候做一些操作,而并不在乎它们是成功还是失败。
Promise.allSettled 方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise ,并带有一个对象数组,每个对象表示对应的 promise 结果。
const promise1 = Promise.resolve(100);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'info'));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 200, 'name'))
Promise.allSettled([promise1, promise2, promise3]).
then((results) => console.log(result));
/*
[
{ status: 'fulfilled', value: 100 },
{ status: 'rejected', reason: 'info' },
{ status: 'fulfilled', value: 'name' }
]
*/
可以看到,Promise.allSettled() 的成功的结果是一个数组,该数组的每一项是一个对象,每个对象都有一个 status 属性,值为 fulfilled 或 rejected,如果status 的值是 fulfilled,那么该对象还有一个 value 属性,其属性值是对应的 promise 成功的结果;如果 status 的值是 rejected,那么该对象有一个 reason 属性,其属性值是对应的 promise 失败的原因。
应用场景 Promise.race:针对第一个执行的 Promise 实例。
- 只要有一个Promise实例(s)执行了,无论该 s 执行的是 resolve 还是 reject,执行完之后立即停止,返回一个新的 Promise 对象 p。
- 若 s 成功(执行 resolve),p 走.then里的代码。
- p 的[[PromiseState]]永远为 fulfilled。
- p 的返回值是该 s 成功(执行 resolve)的结果。
- 若 s 失败(执行 reject),p 走.catch里的代码。
- p 的[[PromiseState]]永远为 fulfilled。
- p 的返回值是该 s 失败(执行 reject)的错误。
Promise.all:针对所有的 Promise 实例,关注每个 Promise 实例的执行结果。
- 若所有 Promise 实例(all_s)都执行的是 resolve,返回一个新的 Promise 对象 p。p 走.then里的代码。
- p 的[[PromiseState]]为 pending。
- p 的返回值是一个数组——包含 all_s 分别成功(执行 resolve)的结果。
- 若其中有一个 Promise 实例失败(执行 reject)就抛出失败的原因。然后立即停止,返回一个新的 Promise 对象 p,p 走.catch里的代码。
- p 的[[PromiseState]]为 fulfilled。
- p 的返回值是一个数组——包含 all_s 中,成功(执行 resolve)的 promise 实例的结果,对于失败(执行 reject)的 promise 实例,则用 undefined 作为结果。
Promise.allSettled:针对第所有的 Promise 实例,不关注每个 Promise 实例的执行结果。
- 待所有 Promise 实例(all_s)都已结束后才停止,才返回一个新的 Promise 对象 p。
- 无论 all_s 分别执行的是其各自的 resolve 还是 reject,p 永远走.then里的代码,永远不会走.catch里的代码。
- p 的[[PromiseState]]永远为 fulfilled。
- p 的返回值是一个数组,该数组的每一项都是一个对象,每个对象都有一个 status 属性,值为 fulfilled 或 rejected。
- 如果 status 的值是 fulfilled,那么该对象还有一个 value 属性,其属性值是对应的 promise 实例成功(执行 resolve)的结果;
- 如果 status 的值是 rejected,那么该对象有一个 reason 属性,其属性值是对应的 promise 实例失败(执行 reject)的原因。
七、ES12(ES2021)
1、逻辑赋值运算符 和 赋值表达式(🌟🌟🌟)
JavaScript 已存在的 复合赋值运算符 有:
- 操作运算符:+= -= /= %= *=
- 位操作运算符:&= ^= |=
- 按位运算符:<<= >>= >>>=
ES12 新增的 逻辑赋值运算符:??= &&= ||=。
a ||= b; // 等价于:a = a || b; 即:if(a == 0 || a === "" || a === false || a === null || a === undefined) {a = b}
a &&= b; // 等价于:a = a && b; 即:if(a) {a = b}
a ??= b; // 等价于:a = a ?? b; 即:if(a === null || a === undefined) {a = b}
2、Promise.any(🌟🌟🌟)
当Promise列表中的任意一个promise成功resolve则返回第一个resolve的结果状态 如果所有的promise均reject,则抛出异常表示所有请求失败。
Promise.any([
new Promise((resolve, reject) => setTimeout(reject, 500, '一个苹果')),
new Promise((resolve, reject) => setTimeout(resolve, 1000, '一个桃')),
new Promise((resolve, reject) => setTimeout(resolve, 2000, '又一个桃')),
])
.then(value => console.log(`输出结果: ${value}`))
.catch (err => console.log(err))
//输出结果:一个桃
再来看下另一种情况:
Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2'),
Promise.reject('Error 3')
])
.then(value => console.log(`请求结果: ${value}`))
.catch (err => console.log(err))
// AggregateError: All promises were rejected
Promise.any与Promise.race十分容易混淆,务必注意区分,Promise.race 一旦某个promise触发了resolve或者reject,就直接返回了该状态结果,并不在乎其成功或者失败。
【拓展】:
- Promise.all 一个失败,就是失败,有成功也是失败。
- Promise.any一个成功,就是成功,有失败也是成功。
- Promise.race 谁快算谁的,不管成功或者失败,第一个结果就是最终结果。可以处理超时。
3、数字分隔符(🌟🌟🌟)
可以在数字之间,通过_下划线来分割数字,使数字更具可读性。
const money = 1_000_000_000
// 等价于
const money = 1000000000
4、WeakRefs
使用 WeakRefs 的 Class 类创建对 对象的弱引用(当该对象应该被 GC 回收时不会阻止 GC 的回收行为)。
【注意】:要尽量避免使用 WeakRef 和 FinalizationRegistry,垃圾回收机制依赖于 JavaScript 引擎的实现,考虑到兼容性,不同的引擎或是不同版本的引擎可能会有所不同。
这个提案主要包括两个主要的新功能:
- 使用 WeakRef 类创建对象的弱引用。
- 使用 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");
5、String.prototype.replaceAll(🌟🌟🌟)
replaceAll 返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。
let string = 'I love 前端,I love 前端路漫漫'
//使用replace
let replaceStr = string.replace('love','hate')
console.log(replaceStr) // 'I hate 前端,I love 前端路漫漫'
//replace使用正则匹配所有
console.log(string.replace(/love/g,'hate')) // 'I hate 前端,I hate 前端路漫漫'
//使用replaceAll
let replaceAllStr = string.replaceAll('love','hate')
console.log(replaceAllStr) // 'I hate 前端,I hate 前端路漫漫'
需要注意的是,replaceAll在使用正则表达式的时候,如果非全局匹配(/g),则replaceAll()会抛出一个异常。
八、ES13(ES2022)
1、声明类的字段(🌟)
之前类的字段定义和初始化是在类的构造函数中完成的。但是在 ES13 的提案中,类字段可以在类的顶层被定义和初始化。
class Post {
title;
content;
shares = 0;
}
2、检测私有字段(🌟)
当我们试图访问一个没有被声明的公共字段时,会得到未定义的结果,同时访问私有字段会抛出一个异常。我们根据这两个行为来判断是否含有公共字段和私有字段。但是这个建议引入了一个更有趣的解决方案,它包括使用in操作符,如果指定的属性/字段在指定的对象/类中,则返回真,并且也能判断私有字段。
class User {
name;
#password;
get #password () {
return #password;
}
#clear () {
this.name = null;
this.#password = null;
}
static hasPassword (obj) {
return #password in obj;
}
}
4、类的静态公共方法和字段(🌟)
在之前的类的字段和私有方法提案的基础上,为JavaScript类增加了静态公共字段、静态私有方法和静态私有字段的特性。
export const registry = new JSDOMRegistry();
export class JSDOM () {
#createBy;
#registerWithRegistry () {
// ... elided ...
}
static async fromURL (url, options = {}) {
normalizeFromURLOptions(options);
normalizeOptions(options);
const body = await getBodyFromURL(url);
return JSDOM.#finalizeFactoryCreated(new JSDOM(body, options), "fromRUL");
}
static async fromFile (filename, options = {}) {
nomalizeOptions(options);
const body = await getBodyFromFilename(filename);
return JSDOM.#finalizeFactoryCreated(new JSDOM(body, options), "fromFile");
}
static #finalizeFactoryCreated (jsdom, factoryName) {
jsdom.#createdBy = factoryName;
jsdom.#registerWithRegistry(registry);
return jsdom;
}
}
5、类静态初始化块(🌟)
类静态块提议提供了一种优雅的方式,在类声明/定义期间评估静态初始化代码块,可以访问类的私有字段。
class User {
static roles;
name;
#password;
}
try {
User.roles = getRolesFromDb();
} catch {
User.roles = getRolesFromBackup();
}
// 等价于
class User {
static roles;
name;
#password;
static {
try {
User.roles = getRolesFromDb();
} catch {
User.roles = getRolesFromBackup();
}
}
}
6、新的正则匹配索引/d(🌟🌟🌟)
ES13 提案中,提供了一个新的/dflag,以获得关于输入字符串中每个匹配的开始和索引位置结束的信息。
const reg = /test(\d)/g;
const reg2022 = /test(\d)/dg;
const str = 'test1test2';
const arr = [...str.matchAll(reg)];
const arr2022 = [...str.matchAll(reg2022)];
console.log('结果:', arr, arr2022);
7、Top-level await(🌟🌟🌟)
顶层的await允许在异步函数之外使用await关键字。这个提案允许模块当做大型异步函数,所以这些ECMAScript模块可以等待资源加载,这样其他导入这些模块的模块在开始执行自己的代码之前也要等待资源加载完再去执行。
import {getUser} from './data/user';
const userInfo = await getUser();
8、在所有内置的可索引数据上新增.at()方法(🌟🌟🌟)
新增一个新的数组方法,通过给定的索引来获取一个元素。当给定的索引为正数时,这个新方法的行为与使用括号符号的访问相同,但是当我们给定一个负整数的索引时,它就像python的 "负数索引 "一样工作,这意味着at()方法以负整数为索引,从数组的最后一项往后数。所以该方法可以被执行为array.at(-1),它的行为与array[array.length-1]相同。
const arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr[arr.length - 1]);// 6
console.log(arr.at(-1));// 6
9、Object.hasOwn(object, property)(🌟🌟🌟)
使用Object.hasOwn来替代Object.prototype.hasOwnProperty.call,因为后者太长且不好记。
Object.hasOwn 方法用来判断,指定的对象是否有指定的属性作为其自身的属性。如果指定的属性是对象的直接属性,则返回true——即使属性值为null或undefined。如果属性是继承的,或者根本没有声明,则该方法返回false。与in运算符不同,此方法不检查对象原型链中的指定属性。
const obj = { foo: 'bar' };
const hasFoo = Object.prototype.hasOwnProperty.call(obj, 'foo');
console.log(hasFoo);// true
const obj = { foo: 'bar' };
const hasFoo = Object.hasOwn(obj, 'foo');
console.log(hasFoo);// true