ES 提案:JavaScript 类中私有方法和其访问器

这篇博客文章是关于类定义主体中新成员的系列文章的一部分:

  1. 公有类字段
  2. 私有类字段
  3. JavaScript 类中的私有方法和其读写

在这篇文章中,我们了解 JavaScript 类的私有方法和私有访问器(getter 和 setter)。它们是类的新成员,不能在类的外部访问到。为了理解这篇文章,需要先了解私有类字段

该功能是由 Daniel Ehrenberg 的 ECMAScript 提案“Private methods and getter/setters for JavaScript classes”

概述:私有方法和访问器

存在下面种类的私有方法和访问器:

class MyClass {
  #privateOrdinaryMethod() {}
  * #privateGeneratorMethod() {}

  async #privateAsyncMethod() {}
  async * #privateAsyncGeneratorMethod() {}
  
  get #privateGetter() {}
  set #privateSetter(value) {}
}

如你所见,它们的名称之前都有与私有类字段相同的前置 #,这表示它们是私有的。

从名称约定到真正的私有

在下面的代码中,方法 ._computeDist() 的名称开始于下划线。这是向人暗示这个类的这个方法是私有的,但是它没有使它真正私有:它依然可以访问到类 Point 的主体。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  _computeDist() {
    return Math.hypot(this.x, this.y);
  }
  dist() {
    return this._computeDist();
  }
}

console.log(new Point(4, 3).dist());
// output: Math.sqrt(4**2 + 3**2) -> 5

console.log(Reflect.ownKeys(new Point().__proto__));
// output: ['constructor', '_computeDist', 'dist']

在下面的代码段,我们转化 ._computeDist() 为私有方法 .#computeDist()

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  #computeDist() {
    return Math.hypot(this.x, this.y);
  }
  dist() {
    return this.#computeDist();
  }
}

console.log(Reflect.ownKeys(new Point().__proto__));
// output: ['constructor', 'dist']

ECMAScript 规范如何处理私有方法?(高级)

下面的代码说明规范怎么样处理类 Point 的私有方法。私有访问器采取相似处理。

{
  // Private name
  const __computeDist = {
    __Description__: 'computeDist',
    __Kind__: 'method',
    __Brand__: Point.prototype,
    __Value__: function () { // (A)
      return Math.hypot(this.x, this.y);
    },
  };
  class Object {
    // Maps private names to values
    // (not used in this example)
    __PrivateFieldValues__ = new Map();
    
    // Prototypes with associated private members
    __PrivateBrands__ = new Set();
  }
  class Point extends Object {
    static __PrivateBrand__ = Point.prototype;
    constructor(x, y) {
      this.__PrivateBrands__.add(Point.__PrivateBrand__);
      this.x = x;
      this.y = y;
    }
    dist() {
      PrivateBrandCheck(this, __computeDist);
      __computeDist.__Value__.call(this);
    }
  }
}
function PrivateBrandCheck(obj, privateName) {
  if (! obj.__PrivateBrands__.has(privateName.__Brand__)) {
    throw new TypeError();
  }
}

私有名称

私有字段相似,这里有一个私有名称(__computeDist)。这个名称只能在这个类的主体内被访问。

该私有方法 .#computeDist() 被存储在私有名称属性 __Value__(行 A) 中,属性 __Brand__ 指示该私有方法与 Point.prototype 有关联(单不是存储在属性中)。这意味着什么?

  • 因为 .#computeDist() 不是存储在 Point.prototype 中,所以它不能在类的主体外访问到。
  • __computeDist.__Value__ 设置 Point.prototype 为它的主对象。作为结果,如果在 .#computeDist() 内部使用 super.prop,将在 Point.prototype.__proto__. 内部检索以 .prop 开始的属性。想要详细了解,请阅读“探索 ES6”章节“Referring to superproperties in methods”

私有类型检查

在前面的章节,我们看到私有方法的类型与原型对象相关联。在内部字段 .__PrivateBrands__ 中,每个对象记录着私有方法和私有访问器的私有类型。这个字段被构造函数设置。

在调用私有方法和私有访问器之前,总会进行私有类型检查。该检查确认该私有方法与当前对象(this)的(原始)原型之一相关联。

实践

Babel 有两个关联插件:

你可以在Babel REPL中使用这些插件,但是你需要把它们添加到列表中激活功能(左侧导航栏的"Plugins"栏)。

扩展阅读


英文原文:https://2ality.com/2019/07/private-methods-accessors-in-classes.html

上次更新: 7/24/2019, 2:14:48 PM