ES6 类的正确定义方式 公有类字段 getter / setter
类字段提案
https://github.com/tc39/proposal-class-fields
https://wenjun.me/2019/07/public-class-fields.html
Webpack 内置的js打包程序不支持类字段, 然而Chrome和FF已原生支持....
getter / setter
注意与Java区别.
- setter函数的定义
setter函数, 对该属性赋值时触发函数调用.
class A {
constructor() {
this.field = 'init from contructor()'; # 这里也会触发setter, 即使这里没有分配field变量, 之后的赋值语句也会将setter函数触发
}
set field(value) {
console.log(`setter被触发, 你想将field设置为${typeof value}: ${value}`);
}
}
let a = new A(); // setter被触发, 你想将field设置为string: init from contructor()
a.field = 'trigger setter()'; // setter被触发, 你想将field设置为string: trigger setter()
console.log(a.field); // undefined
- getter函数
与setter类似.
...
get field() {
console.log('你想获取field的值'); // 被打印
return '未知';
}
...
console.log(a.field); // 未知
- 如何工作
我们想当然地以为下面的代码可以工作, 事实却很残酷:
set field(value) {
console.log(`setter被触发, 你想将field设置为${typeof value}: ${value}`); # 不断打印该行, 直到报错
this.field = value; # 会导致递归地调用该函数
}
如何解决? 使用私有的公有类字段即可:
getter 与 setter 在访问公有类字段的时候不会被触发, 在公有类中可以定义一个私有的公有类字段来缓存.
class A {
#field = undefined; // #开头的公有字段是私有的, 不可以通过实例名直接访问
constructor() {
}
set field(value) {
console.log(`setter被触发, 你想将field设置为${typeof value}: ${value}`);
this.#field = value;
}
get field() {
console.log('你想获取field的值');
return this.#field;
}
}
let a = new A();
a.field = 'trigger setter()';
console.log(a.field);
setter被触发, 你想将field设置为string: trigger setter()
你想获取field的值
trigger setter()
这里使用了#
声明私有的公有类字段, 保证字段不会被外界获取, 即使使用以下方式:
function tryGetPrivateField() {
return this.#field;
}
console.log(tryGetPrivateField.apply(a)); # 语法错误:私有字段“#field”必须在封闭类中声明
实例字段与静态字段
- 静态字段
类也是一个对象, 可以为类赋值, 通过类名获取变量值, 称为静态字段:
class A {}
A.field = 'value';
// 静态字段的便捷写法: 使用static关键字以及公有字段的声明方式
class A {
static field = 'value';
}
- 实例字段
实例字段比较常见, 通常在构造函数中动态赋值. 当然, 我们建议使用公有字段的声明方式.
赋值和公有字段的区别: 前者使用"分配", 后者使用"定义". 即普通赋值和Object.defineProperty()
函数. 这两者又有什么区别?
共有类字段
JavaScript的类只有函数是可复用的, 一般来说没有什么问题:
如果字段需要共享, 那么可能直接往类的原型CustomClass.prototype
上添加属性即可, 原型委托机制能让类的实例访问这些共享字段. (使用静态字段也何尝不可)
类特有的字段只能在构造函数中动态赋值, 但是这对于IDE来说不友好, 因为我们希望一些字段是通用的, 公有的, 可提示的.
ES6有一个提案: 公有类字段, 可以提供实现:
class CustomClass {
version = '1.0'; // 声明公有类字段
/** 创建该类的作者 */ // 为字段编写文档
author; // 可以声明但不赋值
constructor(version, author = 'Develon') { // 如果需要在构造函数中提供赋值的话, 可以使用默认参数, 而不在声明author时赋值
// this.version = version; // 不建议单独在构造函数中使用this关键字构造字段, 而省略公有类字段的声明
if (version !== undefined) this.version = version; // 公有类字段的正确构造方法, 毕竟构造函数不可重载(难不成基于参数个数重载? 可变参数咋办呢....)
this.author = author;
}
printVersion() {
console.log(this.version);
}
}
new CustomClass().printVersion(); // 1.0
console.log(new CustomClass()); // CustomClass { version: '1.0', author: 'Deve'on' }
共有类字段也可以是函数, 一般来说是箭头函数, 可以捕获this关键字, 这在React中有应用:
class CustomComponent extends Component {
constructor(props) {
super(props);
}
handleClick = event => { // 要紧: 公有类字段 + 箭头函数
this === inst of CustomComponent // 捕获到this关键字, 将函数引用注册到按钮事件也不会丢失this值
use event && this.setState(...); // 正确执行, 根据事件设置组件状态
};
render() {
return createElement('button', {
onClick: this.handleClick,
onClick: event => { this.handleClick(event); }, // 也可以直接在这里使用this关键字, 但是需要显式传递事件
});
}
}
私有类字段
使用#
符号即可.