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关键字, 但是需要显式传递事件
            });
      }
}

私有类字段

使用#符号即可.

posted @ 2020-09-04 15:26  develon  阅读(746)  评论(0编辑  收藏  举报