zoukankan      html  css  js  c++  java
  • JavaScript 的新特性:类的 #private 字段

    这是什么,如何使用,为什么需要?

    一边听“Noise Pollution” —— Portugal. The Man,一边阅读本文简直就是享受

    JavaScript 标准的第二阶段(Stage 2)加入了类私有字段。它还没有最终确定,但 JavaScript 标准委员会认为这个特性会被开发出来并最终纳入标准(虽然它可能还会改变)

    它的语法(当前)看起来像这样:

     1 class Point {
     2   **#x;**
     3   **#y;**
     4 
     5   constructor(x, y) {
     6     **this.#x** = x;
     7     **this.#y** = y;
     8   }
     9 
    10   equals(point) {
    11     return **this.#x** === **point.#x** && **this.#y** === **point.#y**;
    12   }
    13 }

      

    这个语法有两个主要部分:

    • 定义私有字段

    • 引用私有字段

    定义私有字段

    定义私有字段与定义公共字段基本相同:

    1 class Foo {
    2   **publicFieldName =** 1;
    3   **#privateFieldName =** 2;
    4 }

    为了访问私有字段,你需要定义它。如果你不想在定义的时候给它赋值,可以这样:

    1 class Foo {
    2   **#privateFieldName;**
    3 }

    引用私有字段

    引用私有字段与访问其它属性类似,只是它有一点特殊的语法。

    1 class Foo {
    2   publicFieldName = 1;
    3   #privateFieldName = 2;
    4 
    5   add() {
    6     return this.publicFieldName + **this.#privateFieldName**;
    7   }
    8 }

    this.# 也可以简写:

    1 method() {
    2   **#privateFieldName;**
    3 }

    它与下面这段代码等价:

    1 method() {
    2   **this.#privateFieldName;**
    3 }

    引用实例(对象)的私有字段

    引用私有字段并不局限于 this。也可以访问类实例(对象)的私有字段:

    1 class Foo {
    2   #privateValue = 42;
    3 
    4   static getPrivateValue(**foo**) {
    5     return **foo.#privateValue**;
    6   }
    7 }
    8 
    9 Foo.getPrivateValue(**new Foo()**); // >> 42

    这里,foo 是 Foo 的实例,所以我们允许在类定义中去寻找 #privateValue。

    私有方法(很快有会有?)

    私有字段这个提案只关注了为类添加私有字段这个问题。提案并没有提到会对类的方法做什么改变,所以私有方法可能会在后续建议中提到,像这样:

    1 class Foo {
    2   constructor() {
    3     **this.#method();**
    4   }
    5 
    6   **#method() {**
    7     // ...
    8   **}**
    9 }

    这之前,可以将函数赋值给私有字段:

    1 class Foo {
    2   constructor() {
    3     **this.#method();**
    4   }
    5 
    6   **#method = () => {**
    7     // ...
    8   **};**
    9 }

    封装

    使用某个类的实例的时候,不能引用这个类的私有字段。只能在定义这些私有字段的类中引用它们。

     1 class Foo {
     2   **#bar;**
     3 
     4   method() {
     5     **this.#bar;** // Works
     6   }
     7 }
     8 
     9 let foo = new Foo();
    10 
    11 **foo.#bar;** // Invalid!

    进一步说,作为真正的私有属性,你应该不能检测到私有字段的存在。

    为了确保你不能探测到私有字段,我们需要允许拥有同样名称的公共字段。

    1 class Foo {
    2   bar = 1; // public bar
    3   #bar = 2; // private bar
    4 }

    如果私有字段不允许公共字段有同样的名称,你就可以通过尝试写同名属性来探测到私有字段的存在:

    1 foo.bar = 1; // Error: bar is private! (boom... detected)

    或者不报错的版本:

    1 foo.bar = 1;
    2 console.log(foo.bar); // undefined (boom... detected again)

    这种封装同样对子类有效。子类应该可以拥有相同名称的私有字段,而不必担心父类是否已经存某个私有字段是这个名称。

    1 class Foo {
    2   **#fieldName** = 1;
    3 }
    4 
    5 class Bar extends Foo {
    6   **fieldName** = 2; // Works!
    7 }

    注意:关于封装或“强私有”背后的动机,阅读 FAQ 中这个部分


    那么, 为什么用 # 号[译者注:URL 的 Hash标记]?

    很多人想知道“为什么不像其它语言那样使用 private 关键字”?

    如果使用这样的语法,这里有个示例:

    1 class Foo {
    2   **private value;**
    3 
    4   equals(foo) {
    5     return **this.value** === **foo.value**;
    6   }
    7 }

    让我们单独看看语法的两个部分。

    为什么不使用 private 关键字来声明?

    private 关键字在很多不同的语言中用于声明私有字段。

    来看看使用这种语法的语言:

    1 class EnterpriseFoo {
    2   public bar;
    3   private baz;
    4 
    5   method() {
    6     this.bar;
    7     this.baz;
    8   }
    9 }

    在这些语言中,以同样的方式访问私有字段和公共字段。所以它们才会这样定义。

    但是在 JavaScript 中,我们不能使用 this.field 来引用私有属性(稍后深入),我们需要一种基于语法的方法来连接它们的关系。这两个地方使用 # 更能清楚的表明引用的是什么。

    什么引用需要 # (Hash 标记)?

    因为如下原因,我们需要使用 this.#field 而不是 this.field:

    1. 因为 #封装 (参阅上面“封装”一节),我们要能同时访问公共和私有的同名字段。因此访问一个私有字段不是按常规的方式查找。

    2. JavaScript 中的公共字段可以通过 this.field 或 this['field'] 来访问。私有字段不能支持第二种语法(因为它需要是静态的),这样会导致混淆。

    3. 你需要费劲去检查:

    来看一段示例代码。

     1 class Point {
     2   **#x;**
     3   **#y;**
     4 
     5   constructor(x, y) {
     6     **this.#x** = x;
     7     **this.#y** = y;
     8   }
     9 
    10   equals(other) {
    11     return **this.#x** === **other.#x** && **this.#y** === **other.#y**;
    12   }
    13 }

    注意我们是如何引用 other.#x 和 other.#y 的。为了访问私有字段,我们假设 other 是 Point 类的实例。

    因为我们使用了 #Hash标签 语法,它告诉 JavaScript 编译器我们正在从当前类中寻找私有属性。

    那么,如果不用 #Hash标签,会发生什么呢?

    1 equals(otherPoint) {
    2   return this.x === otherPoint.x && this.y === otherPoint.y;
    3 }

    现在我们有个问题:我们怎么知道 otherPoint 是什么?

    JavaScript 没有静态类型系统,所以 otherPoint 什么都可能是。

    有两个原因导致这个问题:

    1. 我们的函数行为依赖于传入的值类型,不同的值类型导致不同的行为:有时候是在访问私有属性,有时候又是在查找公共属性。

    2. 我们每次检查 otherPoint 的类型:

      if (otherPoint instanceof Point && isNotSubClass(otherPoint, Point)) { return getPrivate(otherPoint, 'foo'); } else { return otherPoint.foo; }

    更糟糕的是,如果我们引用一个私有属性的话,就不得不在类中对每次属性访问进行这样的检查。

    访问属性已经非常慢了,我们绝不想让它变得更慢。


    TL;DR: 我们需要对私有属性使用 #Hash标记,因为不正常的使用标准属性访问机制会导致意外行为,并造成巨大的性能问题。


    将私有属性添加到语言中是件非常好的事情。感谢努力工作在 TC39 勤劳的人们,他们正在让这美好的事情发生!

  • 相关阅读:
    PAT 解题报告 1009. Product of Polynomials (25)
    PAT 解题报告 1007. Maximum Subsequence Sum (25)
    PAT 解题报告 1003. Emergency (25)
    PAT 解题报告 1004. Counting Leaves (30)
    【转】DataSource高级应用
    tomcat下jndi配置
    java中DriverManager跟DataSource获取getConnection有什么不同?
    理解JDBC和JNDI
    JDBC
    Dive into python 实例学python (2) —— 自省,apihelper
  • 原文地址:https://www.cnblogs.com/dadifeihong/p/6964835.html
Copyright © 2011-2022 走看看