ECMAScript 2022 正式发布,有哪些新特性?(上)https://developer.aliyun.com/article/1411126
6. 类的实例成员
(1)公共实例字段
公共类字段允许我们使用赋值运算符 (=) 将实例属性添加到类定义中。下面是一个计数器的例子:
import React, { Component } from "react"; export class Incrementor extends Component { constructor() { super(); this.state = { count: 0, }; this.increment = this.increment.bind(this); } increment() { this.setState({ count: this.state.count + 1 }); } render() { return ( <button onClick={this.increment}>Increment: {this.state.count}</button> ); } }
在这个例子中,在构造函数中定义了实例字段和绑定方法,通过新的类语法,可以使代码更加直观。新的公共类字段语法允许我们直接将实例属性作为属性添加到类上,而无需使用构造函数方法。这样就简化了类的定义,使代码更加简洁、可读:
import React from "react"; export class Incrementor extends React.Component { state = { count: 0 }; increment = () => this.setState({ count: this.state.count + 1 }); render = () => ( <button onClick={this.increment}>Increment: {this.state.count}</button> ); }
有些小伙伴可能就疑问了,这个功能很早就可以使用了呀。但是它现在还不是标准的 ECMAScript,默认是不开启的,如果使用 create-react-app
创建 React 项目,那么它默认是启用的,否则我们必须使用正确的babel插件才能正常使用(@babel/preset-env
)。
下面来看看关于公共实例字段的注意事项:
- 公共实例字段存在于每个创建的类实例上。它们要么是在
Object.defineProperty()
中添加,要么是在基类中的构造时添加(构造函数主体执行之前执行),要么在子类的super()
返回之后添加:
class Incrementor { count = 0 } const instance = new Incrementor(); console.log(instance.count); // 0
- 未初始化的字段会自动设置为
undefined
:
class Incrementor { count } const instance = new Incrementor(); console.assert(instance.hasOwnProperty('count')); console.log(instance.count); // undefined
- 可以进行字段的计算:
const PREFIX = 'main'; class Incrementor { [`${PREFIX}Count`] = 0 } const instance = new Incrementor(); console.log(instance.mainCount); // 0
(2)私有实例字段、方法和访问器
默认情况下,ES6 中所有属性都是公共的,可以在类外检查或修改。下面来看一个例子:
class TimeTracker { name = 'zhangsan'; project = 'blog'; hours = 0; set addHours(hour) { this.hours += hour; } get timeSheet() { return `${this.name} works ${this.hours || 'nothing'} hours on ${this.project}`; } } let person = new TimeTracker(); person.addHours = 2; // 标准 setter person.hours = 4; // 绕过 setter 进行设置 person.timeSheet;
可以看到,在类中没有任何措施可以防止在不调用 setter
的情况下更改属性。
而私有类字段将使用哈希#
前缀定义,从上面的示例中,可以修改它以包含私有类字段,以防止在类方法之外更改属性:
class TimeTracker { name = 'zhangsan'; project = 'blog'; #hours = 0; // 私有类字段 set addHours(hour) { this.#hours += hour; } get timeSheet() { return `${this.name} works ${this.#hours || 'nothing'} hours on ${this.project}`; } } let person = new TimeTracker(); person.addHours = 4; // 标准 setter person.timeSheet // zhangsan works 4 hours on blog
当尝试在 setter
方法之外修改私有类字段时,就会报错:
javascript
复制代码
person.hours = 4// Error Private field '#hours' must be declared in an enclosing class
还可以将方法或 getter/setter
设为私有,只需要给这些方法名称前面加#
即可:
class TimeTracker { name = 'zhangsan'; project = 'blog'; #hours = 0; // 私有类字段 set #addHours(hour) { this.#hours += hour; } get #timeSheet() { return `${this.name} works ${this.#hours || 'nothing'} hours on ${this.project}`; } constructor(hours) { this.#addHours = hours; console.log(this.#timeSheet); } } let person = new TimeTracker(4); // zhangsan works 4 hours on blog
由于尝试访问对象上不存在的私有字段会发生异常,因此需要能够检查对象是否具有给定的私有字段。可以使用 in
运算符来检查对象上是否有私有字段:
(3)静态公共字段
在ES6中,不能在类的每个实例中访问静态字段或方法,只能在原型中访问。ES 2022 提供了一种在 JavaScript 中使用 static
关键字声明静态类字段的方法。下面来看一个例子:
class Shape { static color = 'blue'; static getColor() { return this.color; } getMessage() { return `color:${this.color}` ; } }
可以从类本身访问静态字段和方法:
console.log(Shape.color); // blue console.log(Shape.getColor()); // blue console.log('color' in Shape); // true console.log('getColor' in Shape); // true console.log('getMessage' in Shape); // false
实例不能访问静态字段和方法:
const shapeInstance = new Shape(); console.log(shapeInstance.color); // undefined console.log(shapeInstance.getColor); // undefined console.log(shapeInstance.getMessage());// color:undefined
静态字段只能通过静态方法访问:
javascript
复制代码
console.log(Shape.getColor()); // blueconsole.log(Shape.getMessage()); //TypeError: Shape.getMessage is not a function
这里的 Shape.getMessage()
就报错了,因为 getMessage
不是一个静态函数,所以它不能通过类名 Shape
访问。可以通过以下方式来解决这个问题:
getMessage() { return `color:${Shape.color}` ; }
静态字段和方法是从父类继承的:
class Rectangle extends Shape { } console.log(Rectangle.color); // blue console.log(Rectangle.getColor()); // blue console.log('color' in Rectangle); // true console.log('getColor' in Rectangle); // true console.log('getMessage' in Rectangle); // false
(4)静态私有字段和方法
与私有实例字段和方法一样,静态私有字段和方法也使用哈希 (#) 前缀来定义:
class Shape { static #color = 'blue'; static #getColor() { return this.#color; } getMessage() { return `color:${Shape.#getColor()}` ; } } const shapeInstance = new Shape(); shapeInstance.getMessage(); // color:blue
私有静态字段有一个限制:只有定义私有静态字段的类才能访问该字段。这可能在使用 this
时导致出乎意料的情况:
class Shape { static #color = 'blue'; static #getColor() { return this.#color; } static getMessage() { return `color:${this.#color}` ; } getMessageNonStatic() { return `color:${this.#getColor()}` ; } } class Rectangle extends Shape {} console.log(Rectangle.getMessage()); // Uncaught TypeError: Cannot read private member #color from an object whose class did not declare it const rectangle = new Rectangle(); console.log(rectangle.getMessageNonStatic()); // TypeError: Cannot read private member #getColor from an object whose class did not declare it
在这个例子中,this
指向的是 Rectangle
类,它无权访问私有字段 #color
。当我们尝试调用 Rectangle.getMessage()
时,它无法读取 #color
并抛出了 TypeError
。可以这样来进行修改:
class Shape { static #color = 'blue'; static #getColor() { return this.#color; } static getMessage() { return `${Shape.#color}`; } getMessageNonStatic() { return `color:${Shape.#getColor()} color`; } } class Rectangle extends Shape {} console.log(Rectangle.getMessage()); // color:blue const rectangle = new Rectangle(); console.log(rectangle.getMessageNonStatic()); // color:blue
(5)类静态初始化块
静态私有和公共字段只能让我们在类定义期间执行静态成员的每个字段初始化。如果我们需要在初始化期间像 try…catch
一样进行异常处理,就不得不在类之外编写此逻辑。该规范就提供了一种在类声明/定义期间评估静态初始化代码块的优雅方法,可以访问类的私有字段。
先来看一个例子:
class Person { static GENDER = "Male" static TOTAL_EMPLOYED; static TOTAL_UNEMPLOYED; try { // ... } catch { // ... } }
上面的代码就会引发错误,可以使用类静态块来重构它,只需将try...catch
包裹在 static
中即可:
class Person { static GENDER = "Male" static TOTAL_EMPLOYED; static TOTAL_UNEMPLOYED; static { try { // ... } catch { // ... } } }
此外,类静态块提供对词法范围的私有字段和方法的特权访问。这里需要在具有实例私有字段的类和同一范围内的函数之间共享信息的情况下很有用。
let getData; class Person { #x constructor(x) { this.#x = { data: x }; } static { getData = (obj) => obj.#x; } } function readPrivateData(obj) { return getData(obj).data; } const john = new Person([2,4,6,8]); readPrivateData(john); // [2,4,6,8]
这里,Person
类与 readPrivateData
函数共享了私有实例属性。