• 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 勤劳的人们,他们正在让这美好的事情发生!

  • 相关阅读:
    简易发号SQL,可用于生成指定前缀自增序列--改进版
    关于【【故障公告】数据库服务器 CPU 近 100% 引发的故障(源于 .NET Core 3.0 的一个 bug)】IS NOT NULL测试
    简易发号SQL,可用于生成指定前缀自增序列,如订单号,生成优惠券码等
    [EntityFramework]记录Linq中如何比较数据库中Timestamp列的方法(如大于0x00000000000007D1的记录)
    [高并发]抢红包设计(使用redis)
    [.Net跨平台]部署DTCMS到Jexus遇到的问题及解决思路--验证码
    [.Net跨平台]部署DTCMS到Jexus遇到的问题及解决思路---部署
    【迷你微信】基于MINA、Hibernate、Spring、Protobuf的即时聊天系统 :1.技术简介之Mina连接
    Unity光晕剑效果的Shader简单实现
    Unity3D 调用Android与IOS的剪贴板
  • 原文地址:https://www.cnblogs.com/dadifeihong/p/6964835.html
Copyright © 2020-2023  润新知