跳到主要内容

ECMAScript

ES9(ECMAScript 2018)

ECMAScript 2018 是 JavaScript 语言的第 9 个版本,发布于 2018 年 6 月。

1. 异步迭代

为了支持异步迭代,ECMAScript 2018 引入了 Symbol.asyncIterator 符号,该符号用于定义异步迭代器。此外,还引入了 for-await-of 循环,用于异步遍历数据集。

下面是一个简单的示例,展示了如何使用异步迭代器和 for-await-of 循环:

http://localhost:3000
async function* asyncGenerator() {
yield 1
yield 2
yield 3
}

async function main() {
for await (const value of asyncGenerator()) {
console.log(value)
}
}
main()

2. Promise.finally()

Promise.finally()方法用于在 Promise 结束后执行一些操作,不论 Promise 是成功还是失败。这个方法可以替代 Promise.then()和 Promise.catch()中的重复代码。

下面是一个简单的示例,展示了如何使用 Promise.finally()方法:

http://localhost:3000
somePromise()
.then((result) => {
// 处理成功的结果
})
.catch((error) => {
// 处理错误
})
.finally(() => {
// 在Promise结束后执行一些操作
})

3. Rest/Spread 属性

Rest/Spread 属性允许我们使用...语法来传递不定数量的参数。在 ECMAScript 2018 之前,...语法只能用于函数参数和数组/对象解构。

下面是一个示例,展示了如何使用 Rest/Spread 属性来传递不定数量的参数:

http://localhost:3000
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }
console.log(x) // 1
console.log(y) // 2
console.log(z) // { a: 3, b: 4 }

function sum(...args) {
return args.reduce((acc, val) => acc + val, 0)
}

console.log(sum(1, 2, 3, 4, 5)) // 15

4. 正则表达式命名捕获组

ECMAScript 2018 允许在正则表达式中使用命名捕获组。这使得正则表达式更易读、更易维护。

下面是一个示例,展示了如何使用命名捕获组:

http://localhost:3000
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const result = re.exec('2022-01-01')
console.log(result)
console.log(result.groups.year) // "2022"
console.log(result.groups.month) // "01"
console.log(result.groups.day) // "01"

在上面的示例中,我们使用了命名捕获组来从日期字符串中提取年、月和日。

5. 正则表达式反向断言

ECMAScript 2018 还引入了正则表达式的反向断言。反向断言允许我们在匹配之前指定一个断言条件。

下面是一个示例,展示了如何使用反向断言:

http://localhost:3000
const re = /(?<!foo)bar/
console.log(re.test('bar')) // true
console.log(re.test('foobar')) // false
console.log(re.test('foobarbar')) // true

在上面的示例中,我们使用了反向断言来匹配 bar 单词,但是不匹配前面有 foo 的情况。

总结一下,ECMAScript 2018 增加了一些有用的功能和语言特性,包括异步迭代、Promise.finally()、Rest/Spread 属性、正则表达式命名捕获组和正则表达式反向断言。这些新功能有助于提高 JavaScript 代码的可读性、可维护性和性能。

ES10 (ECMAScript 2019)

ES2019 是 ECMAScript 的最新版本,也称为 ECMAScript 10。它于 2019 年正式发布,为 JavaScript 带来了一些有用的新功能和语言改进。

1. Array.prototype.flat()和 Array.prototype.flatMap()

Array.prototype.flat()方法可以将一个嵌套的数组变成一个一维数组。它接受一个可选参数,表示要展平的嵌套级别。如果没有指定参数,则默认为 1。

例如,假设我们有一个嵌套数组:

const arr = [1, [2, [3, 4]]]

要将其展平,可以使用flat()方法:

const flatArr = arr.flat()
console.log(flatArr) // [1, 2, [3, 4]]

默认情况下,它只展开一级。如果想要展开更多级,可以传递一个数字参数:

const deepFlatArr = arr.flat(2)
console.log(deepFlatArr) // [1, 2, 3, 4]

Array.prototype.flatMap()方法在map()方法的基础上添加了展平一维数组的功能。它对数组中的每个元素调用一个回调函数,然后将返回的值展平成一个新数组。flatMap()方法可以避免在使用map()方法时需要手动展平数组。

例如,假设我们有一个包含每个单词的字符串数组:

const words = ['hello world', 'how are you']

要将其转换为单词数组,可以使用map()split()方法:

const wordArrays = words.map((word) => word.split(' '))
const flattenedWords = [].concat(...wordArrays)
console.log(flattenedWords) // ["hello", "world", "how", "are", "you"]

使用flatMap()方法可以更简洁地实现相同的结果:

const flattenedWords = words.flatMap((word) => word.split(' '))
console.log(flattenedWords) // ["hello", "world", "how", "are", "you"]

2. Object.fromEntries()

Object.fromEntries()方法将一个键值对数组转换为一个对象。它接受一个键值对数组作为参数,并返回一个新对象。

例如,假设我们有一个键值对数组:

const entries = [
['a', 1],
['b', 2],
['c', 3],
]

要将其转换为对象,可以使用Object.fromEntries()方法:

const obj = Object.fromEntries(entries)
console.log(obj) // {a: 1, b: 2, c: 3}

这个方法可以方便地将键值对数组转换为对象,并且比使用reduce()方法更简单。

3. String.prototype.trimStart()和 String.prototype.trimEnd()

String.prototype.trimStart()String.prototype.trimEnd()方法可以分别从字符串的开头和结尾删除空格。

例如,假设我们有一个字符串:

const str = '  hello world  '

要从字符串的开头删除空格,可以使用trimStart()方法:

const trimmedStart = str.trimStart()
console.log(trimmedStart) // "hello world "

要从字符串的结尾删除空格,可以使用trimEnd()方法:

const trimmedEnd = str.trimEnd()
console.log(trimmedEnd) // " hello world"

如果需要同时从开头和结尾删除空格,可以使用标准的trim()方法。

4. Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors()方法返回指定对象所有自身属性的描述符。它接受一个对象作为参数,并返回一个新对象,该对象的键是属性名称,值是属性描述符对象。

例如,假设我们有一个对象:

const obj = {
name: 'lsss',
age: 18,
get fullName() {
return this.name
},
}

要获取该对象的所有属性描述符,可以使用Object.getOwnPropertyDescriptors()方法:

const descriptors = Object.getOwnPropertyDescriptors(obj)
console.log(descriptors)

输出结果为:

{
name: {value: "lsss", writable: true, enumerable: true, configurable: true},
age: {value: 18, writable: true, enumerable: true, configurable: true},
fullName: {get: [Function: get fullName], set: undefined, enumerable: true, configurable: true}
}

这个方法可以方便地获取对象属性的详细信息,包括属性值、可写性、可枚举性和可配置性等。

5. Symbol.prototype.description

Symbol.prototype.description属性返回一个可读的字符串,表示Symbol实例的描述符。它可以用于检索创建Symbol实例时使用的字符串描述符。

例如,假设我们创建一个 Symbol 实例:

const sym = Symbol('my symbol')

要获取该实例的描述符,可以使用 description 属性:

const desc = sym.description
console.log(desc) // "my symbol"

这个属性可以用于识别创建 Symbol 实例时使用的描述符,从而帮助开发人员更好地理解代码。

以上就是 ES2019 中所有新增内容的概念和代码示例。这些新功能为 JavaScript 带来了更多的语言特性和改进,使得开发人员能够更轻松地编写高质量的代码。

ES11(ECMAScript 2020)

ES11(ECMAScript 2020)作为 ES 的一个重要版本,为我们带来了许多实用的新特性。本文将带你了解 ES11 的新增内容,并通过实际示例与使用场景展示它们的用途。

1. BigInt

JavaScript 的数字类型(Number)在处理大于 2 ** 53 - 1 的整数时会失去精度。为了解决这个问题,ES11 引入了 BigInt 类型。BigInt 可以表示任意大的整数,让我们能够准确处理大整数运算。

const bigNumber = 9007199254740993n
const bigNumber2 = BigInt('9007199254740993')
console.log(bigNumber === bigNumber2) // true

使用场景:BigInt 在大数计算、金融应用、高精度计时等场景中非常实用。

2. Promise.allSettled

Promise.allSettled 可以等待所有的 Promise 实例都结束(无论是成功还是失败)后返回一个结果数组。

const promise1 = Promise.resolve(3)
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, 'foo')
)
const promise3 = 42

Promise.allSettled([promise1, promise2, promise3]).then((results) =>
console.log(results)
)

使用场景:适用于需要等待多个异步操作完成后进行统一处理的场景,例如并发请求数据。

3. Nullish Coalescing Operator (??)

空值合并操作符 ?? 用于返回左侧操作数的值,当且仅当左侧操作数是 null 或 undefined 时,返回右侧操作数的值。

const foo = null ?? 'default'
console.log(foo) // "default"

const bar = 0 ?? 42
console.log(bar) // 0

使用场景:用于为变量提供默认值,特别是在处理用户输入、配置选项等情况时。

4. Optional Chaining Operator (?.)

可选链操作符 ?. 允许我们在查询一个可能不存在的对象属性时,不会抛出错误,而是返回 undefined。

const user = {
name: 'Alice',
address: {
street: 'Main St',
},
}

const street = user?.address?.street
console.log(street) // "Main St"

const city = user?.address?.city
console.log(city) // undefined

使用场景:在访问嵌套对象属性时,避免因为访问 null 或 undefined 而导致的错误。

5. globalThis

globalThis 提供了一个全局对象的统一访问方式,无论当前 JavaScript 环境是浏览器、Node.js 还是其他环境。

// 浏览器环境
console.log(globalThis === window) // true

// Node.js 环境
console.log(globalThis === global) // true

使用场景:在跨平台的 JavaScript 项目中,需要访问全局对象时,globalThis 可以确保代码在各种环境下的兼容性。

6. import.meta

import.meta 是一个在 ES 模块中的元数据对象,它包含了有关当前模块的信息,例如当前模块的 URL。

console.log(import.meta.url) // "file:///path/to/your/module.js"

使用场景:可以用于动态加载模块、获取模块路径等操作。

7. String.prototype.matchAll

matchAll 方法返回一个包含所有匹配正则表达式的结果迭代器。

const regex = /t(e)(st(\d?))/g
const str = 'test1test2test3'

for (const match of str.matchAll(regex)) {
console.log(match)
}

使用场景:在处理字符串时,需要查找并处理所有匹配项,例如文本解析、模板引擎等。

8. 动态导入(Dynamic Import)

动态导入允许我们在代码运行时按需加载模块,而不是在一开始就加载所有模块。

async function loadModule() {
const module = await import('./module.js')
module.default()
}
loadModule()

使用场景:适用于按需加载模块,提高首屏渲染速度,减少不必要的资源请求,例如懒加载组件、代码分割等。

ES12(ECMAScript 2021)

ES2021 是 JavaScript 的最新版本,它引入了一些新的功能和改进,这些功能和改进可以帮助开发人员更轻松地编写可维护和可扩展的代码。本文将介绍 ES2021 的所有新增内容,包括 String.prototype.replaceAll()、Promise.any()、Logical Assignment Operators、WeakRef 和 FinalizationRegistry 等。

1. String.prototype.replaceAll()

replaceAll 是一个新的字符串方法,用于替换目标字符串中所有匹配的子串。在此之前,我们不得不使用正则表达式和 replace 方法实现全局替换。现在,这变得更加简单:

const originalString = 'I love apples. Apples are great.'
const replacedString = originalString.replaceAll('apples', 'bananas')

console.log(replacedString)
// 输出:'I love bananas. Bananas are great.'

replaceAll 方法与 replace 方法类似,但它会替换目标字符串中所有匹配的子串,而不仅仅是第一个匹配。这使得全局替换变得更加简单,不需要使用正则表达式和 g 标志。需要注意的是,在某些情况下,使用正则表达式可能仍然是必要的,例如当需要使用特殊的匹配模式(如忽略大小写)时

使用场景:在处理文本数据时,如日志文件、用户输入等,可以使用 replaceAll 简化文本替换任务。例如,可以用它来标准化用户输入的格式,或批量替换敏感词。

2. Promise.any()

Promise.any 是一个新的 Promise 组合方法,它接受一个 Promise 可迭代对象(例如数组),并在第一个成功的 Promise 完成时解析。如果所有的 Promise 都失败,Promise.any 会返回一个 AggregateError。

const promise1 = new Promise((_, reject) => setTimeout(reject, 100, 'Error 1'))
const promise2 = new Promise((resolve) => setTimeout(resolve, 200, 'Success'))
const promise3 = new Promise((_, reject) => setTimeout(reject, 300, 'Error 3'))

Promise.any([promise1, promise2, promise3])
.then((value) => console.log(value))
.catch((error) => console.error(error))
// 输出:'Success'

Promise.any 的工作原理类似于 Promise.race,但它们在处理失败的 Promise 时有所不同。Promise.race 会在任何一个 Promise 完成(无论成功还是失败)后立即解析或拒绝。Promise.any 只在第一个成功的 Promise 完成时解析,否则返回一个 AggregateError。这使得 Promise.any 更适合处理并行操作,其中只要一个操作成功,整个操作就被认为是成功的。

使用场景:在处理多个 API 请求或资源加载时,可以使用 Promise.any 实现“优先返回”的策略,即当多个请求中任意一个成功时,就认为整个操作成功。这可以加快响应时间,提高用户体验。

3. 逻辑赋值运算符

ES2021 引入了三种逻辑赋值运算符,这些运算符可以使我们的代码更简洁:

  • ||=:当左侧变量为 null、undefined 或者 false 时,将右侧值赋给左侧变量。
  • &&=:当左侧变量为真值时,将右侧值赋给左侧变量。
  • ??=:当左侧变量为 null 或 undefined 时,将右侧值赋给左侧变量。
let a = null
let b = true
let c = undefined

a ||= 'default value'
b &&= false
c ??= 'default value'

console.log(a) // 输出:'default value'
console.log(b) // 输出:true
console.log(c) // 输出:'default value'

逻辑赋值运算符基于现有的逻辑运算符(||、&& 和 ??),但它们会直接修改左侧变量的值。使用逻辑赋值运算符可以使代码更简洁,但在使用时要确保理解其运算顺序。需要注意的是,当右侧表达式有副作用时,逻辑赋值运算符可能会产生意外行为。

使用场景:逻辑赋值运算符可用于简化条件赋值语句,例如在为变量设置默认值或更新状态时。在使用这些运算符时,请确保充分理解其语义,以避免引入错误。

4. WeakRef

WeakRef 是一个新的构造函数,允许我们创建对象的弱引用。这意味着 WeakRef 对象不会阻止其引用的对象被垃圾回收。这在处理大型数据结构(如缓存)时非常有用,因为它可以帮助我们在保持性能的同时,避免内存泄漏。

class SomeClass {
constructor(data) {
this.data = data
}
}

const someInstance = new SomeClass({ key: 'value' })
const weakRef = new WeakRef(someInstance)

console.log(weakRef.deref().data) // 输出:{ key: 'value' }

WeakRef 的主要用途是为对象创建弱引用,这意味着垃圾回收器可以在适当的时候回收这些对象。这对于处理大型数据结构和缓存非常有用,因为它有助于防止内存泄漏。然而,WeakRef 不应该用于管理应用程序的主要数据结构,因为弱引用的生命周期不可预测,可能会导致意外的数据丢失。

5. FinalizationRegistry

FinalizationRegistry 是一个新的构造函数,它提供了一种在对象被垃圾回收后执行清理操作的方法。这在处理资源回收、清理外部资源或解除引用时非常有用。

const finalizationRegistry = new FinalizationRegistry((heldValue) => {
console.log(`Cleaning up: ${heldValue}`)
})

const someResource = {
data: 'Important data',
}

finalizationRegistry.register(someResource, 'Resource 1')

// 当 someResource 对象被垃圾回收时,控制台将输出 "Cleaning up: Resource 1"

FinalizationRegistry 提供了在对象被垃圾回收后执行清理操作的能力。这对于处理资源回收、清理外部资源或解除引用非常有用。需要注意的是,FinalizationRegistry 的回调并不保证会在特定时间点执行,也不保证会按照注册的顺序执行。此外,回调函数不应该引用已注册的对象,以避免潜在的内存泄漏。

使用场景:在实现缓存、对象池等资源管理模块时,可以考虑使用 WeakRef 和 FinalizationRegistry。WeakRef 可以帮助我们在不阻止垃圾回收的前提下引用对象,而 FinalizationRegistry 则可以用于在对象被回收时执行清理操作。需要注意的是,这些特性不应用于关键数据结构,因为它们的生命周期不可预测。