[译] ECMAScript 类 —— 定义私有属性

介绍

像往常同样,咱们将从一些理论知识开始介绍。ES 的类是 JavaScript 中新的语法糖。它提供了一种简洁的编写方法,而且实现了与咱们使用原型链相同的功能。惟一的区别是,它看起来更像是面向对象编程了,并且,若是你是 C# 或 Java 开发者,感受会更友好。有人可能会说它们不适合 JavaScript,但对我来讲,使用类或 ES5 的原型都没有问题。javascript

它提供了一种更简单的方式来封装和定义多个属性,这些属性能够在具体的实例对象上被访问到。事实上,咱们能够经过类的方式编写更少的代码来实现更多的功能。有了类,JavaScript 正朝着面向对象的方式发展,经过使用类,咱们能够实现面向对象编程,而不是函数式编程。不要误解个人意思,函数式编程并非一件坏事,实际上,这是一件好事,它也有一些优于类的好处,但这应该是另外一篇文章要讨论的主题。前端

举一个实际的例子,每当咱们想在应用程序中定义来自真实世界的事物时,咱们都会使用一个类来描述它。例如,building、car、motorcycle……它们表明一类真实的事物。java

可访问范围

在后端语言中,咱们有访问修饰符或可见性级别,如 publicprivateprotectedinternalpackage……不幸的是,JavaScript 仅以本身的方式支持前两种方法。它不经过编写访问修饰符(publicprivate)来声明字段,JavaScript 在某种程度上假定全部的区域都是公共的,这就是我写这篇文章的缘由。android

注意,咱们有一种方法能够在类上声明私有和公共的字段,可是这些字段声明方法仍是实验性的特性,所以还不能安全的使用它。ios

class SimCard {
  number; // public field
  type; // public field
  #pinCode; // private field
}
复制代码

若是没有像 Babel 这样的编译器,就不支持使用上面这样的字段声明方式。git

定义私有属性 —— 封装

封装是编程中的一个术语,好比它用来描述某个变量是受保护的或对外部是不可见的。为了保持数据私有而且只对内部可见,咱们须要封装它。在本文中,咱们将使用几种不一样的方法来封装私有数据。让咱们开始吧。github

1. 习惯约定

这种方式只是假定数据或变量的 private 状态。实际上,它们是公开的,外部能够访问。我了解到的两种最多见的定义私有状态的习惯约定是 $_ 前缀。若是某个变量以这些符号做为前缀(一般在整个应用中会规定使用某一个),那么该变量应该做为非公共属性来处理。编程

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);

// 这里,咱们将访问私有的 _pinCode 属性,这并非咱们预期的行为
console.log(card._pinCode); // 输出 1515
复制代码

2. 闭包

闭包对于控制变量的可访问性很是有用。它被 JavaScript 开发者使用了几十年。这种方法为咱们提供了真正的私有性,数据对外部来讲是没法访问的,它只能被内部访问。这里咱们要作的是在类构造函数中建立局部变量,并用闭包捕获它们。要实现这个效果,方法必须定义在实例上,而不是在原型链上。后端

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); // 输出 undefined
console.log(card.getPinCode()); // 输出 1515
复制代码

3. Symbols 和 Getters

Symbol 是 JavaScript 中一种新的基本数据类型。它是在 ECMAScript 6 中引入的。Symbol() 返回的每一个值都是惟一的,这种类型的主要目的是用做对象属性的标识符。安全

因为咱们的意图是在类定义的外部建立 Symbol 变量,但也不是全局的,因此引入了模块。这样,咱们可以在模块内部建立私有字段,将它们定义到类的构造函数中,并经过 getter 返回 Symbol 变量对应的值。注意,咱们可使用在原型链上建立的方法来代替 getter。我选择了 getter 方法,由于这样咱们就不须要调用函数来获取值了。

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

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

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

const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card._pinCode); // 输出 undefined
console.log(card.pinCode); // 输出 1515
复制代码

这里须要指出的一点是 Object.getOwnPropertySymbols 方法,此方法可用于访问咱们用来保存私有属性的 Symbol 变量。上面的类中的 _pinCode 值就能够这样被获取到:

const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card[Object.getOwnPropertySymbols(card)[0]]); // 输出 1515

复制代码

4. Map 和 Getters

ECMAScript 6 还引入了 MapWeakMap。它们以键值对的形式存储数据,这使得它们很是适合存储咱们的私有变量。在咱们的示例中,Map 被定义在模块的内部,而且在类的构造函数中设置每一个私有属性的键值。这个值被类的 getter 引用,一样,咱们不须要调用函数来获取值。另外,请注意,考虑到 Map 自己的结构,咱们不须要为每一个私有属性定义 Map 映射。

const SimCard = (() => {
  const _privates = new Map();

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

    get pinCode() {
       return _privates.get('pinCode');
    }

    get pukCode() {
       return _privates.get('pukCode');
    }
  }
  
  return SimCard;
})();

const card = new SimCard("444-555-666", "Nano SIM", 1515, 45874589);
console.log(card.pinCode); // 输出 1515
console.log(card.pukCode); // 输出 45874589
console.log(card._privates); // 输出 undefined
复制代码

注意,在这种方法中,咱们也可使用普通对象而不是 Map,并在构造函数中动态地为其分配值。

总结和进一步阅读

但愿这些示例对你会有帮助,而且可以用到你的工做中。若是是的话,而且你也喜欢这篇文章,那欢迎分享。这里我只实现了 Twitter 的分享按钮,(哈哈)但我也正在实现其余的方式。

若是要进一步阅读,我推荐一篇文章:JavaScript Clean Code - Best Practices

感谢你的阅读,下一篇文章再见。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索