本文深入解析 JavaScript 中 Class 私有属性与私有方法的实现机制。通过 proposal-class-fields 和 proposal-private-methods 提案,使用 # 符号定义私有成员。文章详细阐述了私有成员的语法、引用限制、同名公共属性封装策略,并重点解释了为何必须使用 # 符号以区分引用方式、保护 this['key'] 语法及避免性能损耗。
译者注:为何必须采用 # 符号?
原文标题:JavaScript's new #private class fields •译者:Fundebug
本文采取意译方式,版权归原作者所有
Class 私有成员提案概述
proposal-class-fields与proposal-private-methods规定了 Class 的私有属性及私有方法,此两项提案已处于 Stage 3 阶段,表明它们已基本定型,正等待纳入新的 ECMAScript 版本。实际上,最新版本的 Chrome 浏览器已支持 Class 私有属性功能。
那么,Class 的私有属性与私有方法具体指代什么?其运行机制如何?为何必须使用 # 符号进行定义?
私有属性语法与引用规则
Class 私有属性的语法规范如下:
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
}
equals(point) {
return this.#x === point.#x && this.#y === point.#y;
}
}
我们可以将该语法解析为两个组成部分:
• Class 私有属性的定义 • Class 私有属性的引用
Class 私有属性的定义
私有属性与公共属性的定义方式几乎一致,区别仅在于属性名称前需添加 # 符号:
class Foo {
publicFieldName = 1;
#privateFieldName = 2;
}
定义私有属性时,也可不进行赋值操作:
class Foo {
#privateFieldName;
}
Class 私有属性的引用
引用私有属性时,仅需使用 # 符号即可。
class Foo {
publicFieldName = 1;
#privateFieldName = 2;
add() {
return this.publicFieldName + this.#privateFieldName;
}
}
其中,this.# 可以简化,省略 this 关键字同样可行,以下两种写法等价:
method() {
#privateFieldName;
}
method() {
this.#privateFieldName;
}
在 Class 定义内部引用 Class 实例的私有属性
对于私有属性,通常无法直接通过 Class 实例进行引用,这正是私有属性的核心含义。但存在一种例外情况:在 Class 定义内部,我们可以引用 Class 实例的私有属性:
class Foo {
#privateValue = 42;
static getPrivateValue(foo) {
return foo.#privateValue;
}
}
Foo.getPrivateValue(new Foo()); // >> 42
其中,foo 是 Foo 类的实例,在 Class 定义内部,我们可以通过 foo 来引用私有属性 #privateValue。
私有方法的定义与实现
Class 的私有方法
Class 私有属性属于提案 proposal-class-fields 的一部分,该提案仅关注 Class 的属性,未对 Class 的方法做出任何修改。而 Class 私有方法同样属于提案 proposal-class-fields 的一部分。
Class 私有方法的语法规范如下:
class Foo {
constructor() {
this.#method();
}
#method() {
// ...
}
}
我们也可以将函数赋值给私有属性:
class Foo {
constructor() {
this.#method();
}
#method = () => {
// ...
};
}
私有属性的封装与隐藏机制
封装(隐藏)私有属性
我们无法直接通过 Class 实例引用私有属性,只能在 Class 定义内部引用它们:
class Foo {
#bar;
method() {
this.#bar; // Works
}
}
let foo = new Foo();
foo.#bar; // Invalid!
此外,为了实现真正的私有化,我们需要确保无法检测该私有属性是否存在,因此,我们需要允许定义同名的公共属性:
class Foo {
bar = 1; // public bar
#bar = 2; // private bar
}
如果我们禁止公共属性与私有属性同名,则可以通过给同名的公共属性赋值来监测该私有属性是否存在:
foo.bar = 1; // Error: `bar` is private! (报错,说明私有属性存在)
不报错也是可行的方案:
foo.bar = 1;
foo.bar; // `undefined` (赋值失败,说明私有属性存在)
对于子类(subclass)也应遵循相同规则,它允许公共属性与私有属性同名:
class Foo {
#fieldName = 1;
}
class Bar extends Foo {
fieldName = 2; // Works!
}
关于 Class 私有属性的封装机制,可参考 Why is encapsulation a goal of this proposal?。
使用 # 符号而非 private 的原因
为何使用 # 符号?
许多人存在疑问,为何 JS 不借鉴其他语言,使用 private 关键字来定义私有属性和私有方法?为何要采用看似奇怪的 # 符号?
若使用 private 关键字,代码可读性会更高:
class Foo {
private value;
equals(foo) {
return this.value === foo.value;
}
}
为何不使用 private 来定义私有属性?
许多语言使用 private 关键字来定义私有属性,示例如下:
class EnterpriseFoo {
public bar;
private baz;
method() {
this.bar;
this.baz;
}
}
对于这些语言中的属性,私有属性和公共属性的引用方式相同,因此它们可以使用 private 关键字来定义私有属性。
然而,对于 JavaScript 而言,我们不能使用 this.field 来引用私有属性(我将在后续解释原因),我们需要在语法层面区分私有属性和公共属性。在定义和引用私有属性时,使用 # 符号,可以使私有属性与公共属性清晰区分。
为何引用私有属性时需要 # 符号?
引用私有属性时,我们需要使用 this.#field,而非 this.field,原因如下:
• 因为我们需要封装私有属性,需允许公共属性与私有属性同名,因此私有属性与公共属性的引用方式必须不同。这一点在前文已有详细阐述。 • 公共属性可以通过 this.field 以及 this['field'] 来引用,但私有属性不支持 this['field'] 这种方式,否则会破坏私有属性的隐私性,示例如下:
class Dict extends null {
#data = something_secret;
add(key, value) {
this[key] = value;
}
get(key) {
return this[key];
}
}
new Dict().get("#data"); // 返回私有属性
因此,私有属性与公共属性的引用方式必须不同,否则会破坏 this['field'] 语法的正确性。
• 若私有属性与公共属性的引用方式相同,则会导致每次都需要检查属性是公共的还是私有的,这将造成严重的性能问题。
本文遵循 Creative Commons Attribution 4.0 International License。
参考
• Why is encapsulation a goal of this proposal?
总结
以上所述是小编给大家介绍的JavaScript 新语法之Class 的私有属性与私有方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持! 如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!