详解 JavaScript Class 私有属性与私有方法:# 符号的语义与封装机制

作者:小括号时间:2026-10-24 00:20:48浏览:338

本文深入解析 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 的私有属性与私有方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持! 如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

作者最新文章