ECMAScript 2022(ES13)
Class Public Instance Fields
公共实例字段允许在类中直接声明属性,无需在构造函数中初始化。
typescript
{
class ES13Class {
name = 'wudi'
age: unknown
sex: unknown
}
const es13class = new ES13Class()
console.log(es13class.name) // 'wudi'
console.log(es13class.age) // undefined
console.log(es13class.sex) // undefined
}
Private Instance Fields
使用 #
前缀声明私有字段,只能在类内部访问。
typescript
{
class ES13Class {
#name: string // 私有字段
#age = 18
constructor(name: string) {
this.#name = name
}
getName() {
return this.#name
}
getAge() {
return this.#age
}
}
const es13class = new ES13Class('wudi')
console.log(es13class.getName()) // 'wudi'
console.log(es13class.getAge()) // 18
// console.log(es13class.#name) // SyntaxError: Private field '#name' must be declared in an enclosing class
}
Private Methods and Accessors
私有方法和访问器只能在类内部调用。
typescript
{
class ES13Class {
#name: string
constructor(name: string) {
this.#name = name
}
// 私有方法
#formatName() {
return `My name is: ${this.#name}`
}
// 私有 getter
get #privateAge() {
return 18
}
// 私有 setter
set #privateName(value: string) {
this.#name = value
}
getFormattedName() {
return this.#formatName()
}
}
const es13class = new ES13Class('wudi')
console.log(es13class.getFormattedName()) // 'My name is: wudi'
// console.log(es13class.#formatName()) // SyntaxError: Private field '#formatName' must be declared in an enclosing class
}
Static Class Fields and Methods
静态字段和方法属于类本身,而不是实例。
typescript
{
class ES13Class {
static baseNum = 100
static #privateStaticNum = 200
// 静态方法
static getBaseNum() {
return this.baseNum
}
// 静态方法访问私有静态字段
static getPrivateNum() {
return this.#privateStaticNum
}
// 静态箭头函数
static doubleBaseNum = () => this.baseNum * 2
}
console.log(ES13Class.baseNum) // 100
console.log(ES13Class.getBaseNum()) // 100
console.log(ES13Class.getPrivateNum()) // 200
console.log(ES13Class.doubleBaseNum()) // 200
// 实例无法访问静态成员
const instance = new ES13Class()
// console.log(instance.baseNum) // undefined
}
Class Static Block
类静态初始化块在类定义时执行,用于初始化静态字段或执行静态初始化逻辑。
typescript
{
let getPrivateData: (obj: any) => any
class ES13Class {
static config = 'default'
static #privateData = []
#instanceData
constructor(data: any) {
this.#instanceData = { data }
}
static {
// 静态初始化块
console.log('Class is being initialized')
this.#privateData = ['item1', 'item2']
// 导出访问私有数据的函数
getPrivateData = (obj) => {
if (!(obj instanceof ES13Class)) {
throw new Error('Invalid object')
}
return obj.#instanceData
}
}
static {
// 可以有多个静态块
this.config = 'initialized'
}
}
console.log(ES13Class.config) // 'initialized'
const instance = new ES13Class({ value: 42 })
console.log(getPrivateData(instance)) // { data: { value: 42 } }
}
Ergonomic Brand Checks for Private Fields
使用 in
操作符检查对象是否包含特定的私有字段。
typescript
{
class ES13Class {
#brand = 'ES13'
static hasValidBrand(obj: any) {
// 检查对象是否包含私有字段 #brand
return #brand in obj
}
static getBrand(obj: any) {
if (!(#brand in obj)) {
throw new Error('Object does not have valid brand')
}
return obj.#brand
}
}
const validInstance = new ES13Class()
const invalidObject = {}
console.log(ES13Class.hasValidBrand(validInstance)) // true
console.log(ES13Class.hasValidBrand(invalidObject)) // false
console.log(ES13Class.getBrand(validInstance)) // 'ES13'
// console.log(ES13Class.getBrand(invalidObject)) // Error: Object does not have valid brand
}
Object.hasOwn()
Object.hasOwn()
作为 Object.prototype.hasOwnProperty()
的更安全替代方案。
typescript
{
const obj = {
name: 'wudi',
age: 18
}
// 传统方式
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')) // true
// 新方式 - 更简洁安全
console.log(Object.hasOwn(obj, 'name')) // true
console.log(Object.hasOwn(obj, 'height')) // false
// 处理 null 原型对象
const nullObj = Object.create(null)
nullObj.prop = 'value'
// console.log(nullObj.hasOwnProperty('prop')) // TypeError: nullObj.hasOwnProperty is not a function
console.log(Object.hasOwn(nullObj, 'prop')) // true
}
Array.prototype.at()
使用 at()
方法进行反向索引,支持负数索引。
typescript
{
const arr = ['a', 'b', 'c', 'd', 'e']
// 传统方式获取最后一个元素
console.log(arr[arr.length - 1]) // 'e'
// 使用 at() 方法
console.log(arr.at(-1)) // 'e'
console.log(arr.at(-2)) // 'd'
console.log(arr.at(0)) // 'a'
console.log(arr.at(1)) // 'b'
// 超出范围返回 undefined
console.log(arr.at(10)) // undefined
console.log(arr.at(-10)) // undefined
// 链式调用中的便利性
const result = [1, 2, 3]
.map(x => x * 2)
.filter(x => x > 2)
.at(-1) // 获取最后一个元素
console.log(result) // 6
}
Error Cause
为 Error 对象添加 cause
属性,用于错误链追踪。
typescript
{
function parseData(data: string) {
try {
return JSON.parse(data)
} catch (err) {
throw new Error('Failed to parse data', { cause: err })
}
}
function processFile(filename: string) {
try {
// 模拟文件读取错误
throw new Error(`File not found: ${filename}`)
} catch (err) {
throw new Error('Failed to process file', { cause: err })
}
}
try {
processFile('config.json')
} catch (error) {
console.log(error.message) // 'Failed to process file'
console.log(error.cause?.message) // 'File not found: config.json'
// 错误链追踪
let currentError = error
while (currentError) {
console.log('Error:', currentError.message)
currentError = currentError.cause
}
}
}
RegExp Match Indices
正则表达式新增 d
标志,返回匹配的索引信息。
typescript
{
const regex = /t(e)(st(\d?))/d
const str = 'test1test2'
const match = regex.exec(str)
console.log(match[0]) // 'test1'
console.log(match.indices[0]) // [0, 5] - 整个匹配的位置
console.log(match.indices[1]) // [1, 2] - 第一个捕获组 'e' 的位置
console.log(match.indices[2]) // [2, 5] - 第二个捕获组 'st1' 的位置
console.log(match.indices[3]) // [4, 5] - 第三个捕获组 '1' 的位置
// 命名捕获组的索引
const namedRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/d
const dateStr = '2022-12-25'
const dateMatch = namedRegex.exec(dateStr)
console.log(dateMatch.indices.groups.year) // [0, 4]
console.log(dateMatch.indices.groups.month) // [5, 7]
console.log(dateMatch.indices.groups.day) // [8, 10]
}
Top-level await
在模块的顶层直接使用 await
,无需包装在 async 函数中。
typescript
{
// 在模块顶层使用 await
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('Data loaded'), 1000)
})
}
// 顶层 await
const data = await fetchData()
console.log(data) // 'Data loaded'
// 动态导入
const { default: moment } = await import('moment')
// 条件性导入
const locale = 'zh-cn'
const localeModule = await import(`./locales/${locale}.js`)
// 异步配置初始化
const config = await fetch('/api/config').then(res => res.json())
console.log('Config loaded:', config)
}
// 注意:需要在模块环境中使用,文件需要有 import/export 或添加 export {}
export {}