ES 提案: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__() {
// (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”栏)。
扩展阅读
- “JavaScript for impatient programmers” 的章节“Prototype chains and classes”
- “探索 ES6”章节“Referring to superproperties in methods”
文章由 吳文俊 翻译,原文地址 ECMAScript proposal: private prototype methods and accessors in classes,转载请注明来源。