lostvita / blog

60 stars 47 forks source link

ES6 Class中实现私有属性的几种方法 #15

Open lostvita opened 5 years ago

lostvita commented 5 years ago

私有属性

私有属性是面向对象编程(OOP)中非常常见的一个特性,一般满足以下的特点:

备胎Class

时间回到四年前,2015年6月,ES6发布成为标准,为了纪念这个历史性时刻,这个标准又被称为ES2015,至此,JavaScript中的class从备胎中转正。在这之前,class一直作为JS的关键字,雪藏不见天日。 class被认为是JS的一种语法糖,它为JavaScript带来了用面向对象的思想描述一个实体的能力。但似乎还远远不够,class的能力远没满足JS开发者们的期待。于是,TC39的大佬们也努力地去做到更好,并推出了一项新的提案:

class Foo {
  #a;  // 私有属性
  constructor(a, b) {
    this.#a = a;
    this.b = b
  }
}

上面私有属性的声明,需要先经过Babel等编译器编译后才能正常使用。

提案已经到Stage 3了,未来可期!

可是,一众JSer们已经等不及了......

JSer的挣扎

通过对数据的一定封装,JS开发者们走上了曲线实现“私有属性”之路。

1. 约定俗成

JS界以一种不成文的规定,在变量前加上下划线"_"前缀,约定这是一个私有属性;但实际上,它仍然是一个穿上皇帝新衣般的公共属性。

class SimCard {
  constructor(number, type, pinCode) {
    this.number = number;
    this.type = type;

    // 使用下划线约定为私有属性
    this._pinCode = pinCode;
  }
}

const card = new SimCard("444-555-666", "Micro SIM", 1515);
// 类外部仍然可以访问这个变量
console.log(card._pinCode); // outputs 1515

2. 闭包

constructor作用域内定义局部变量,内部载通过闭包的方式对外暴露该变量。

class SimCard {
  constructor(number, type, pinCode) {
    this.number = number;
    this.type = type;

    let _pinCode = pinCode;  // 定义私有变量
    this.getPinCode = () => {
        return _pinCode;
    };
  }
}

const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card._pinCode); // outputs undefined
console.log(card.getPinCode()); // outputs 1515

这种方式,虽然实现了私有属性外部不可访问,但在类内部,该属性同样没法在不同的方法内共享,仍然不是严格意义上的“私有属性”。

3. Symbols & Getters

利用Symbol变量可以作为对象key的特点,我们可以模拟实现更真实的私有属性。

const SimCard = (() => {
  const _pinCode = Symbol('PinCode');

  class SimCard {
    constructor(number, type, pinCode) {
      this.number = number;
      this.type = type;
      this[_pinCode] = pinCode;  // this[_pinCode]是一个私有属性
    }

    get pinCode() {
       return this[_pinCode];
    }
  }

  return SimCard;
})();

const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card._pinCode); // outputs undefined
console.log(card.pinCode); // outputs 1515

可是,也不是毫无破绽:

console.log(card[Object.getOwnPropertySymbols(card)[0]]); // outputs 1515

借助getOwnPropertySymbols方法可以取出对象的Symbol键值。

4. WeakMap & Getters

WeakMap的实现与Symbol如出一辙。

const SimCard = (() => {
  const _pinCode = new WeakMap();

  class SimCard {
    constructor(number, type, pinCode) {
      this.number = number;
      this.type = type;
      _pinCode.set(this, pinCode);
    }

    get pinCode() {
       return _pinCode.get(this);
    }
  }

  return SimCard;
})();

const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card.pinCode); // outputs 1515
console.log(card._pinCode); // outputs undefined

巨人的肩膀

以上,是目前阶段JS实现属性私有比较可行的方案,可能实际中很少应用,但如果你在面试中遇到这个问题,或许这些方案可以参考下。

然后,所有这些方案里面,建议选TypeScriptprivate

最后,关于JS中实现私有属性,欢迎读者们评论交流你的看法~


参照