標籤:oal 委員會 open 機制 商業 content 沒有 私人屬性 markdown
這是什麼,如何使用,為什麼需要?
一邊聽“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 bar3 #bar = 2; // private bar4 }
如果私人欄位不允許公用欄位有同樣的名稱,你就可以通過嘗試寫同名屬性來探測到私人欄位的存在:
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:
因為 #封裝 (參閱上面“封裝”一節),我們要能同時訪問公用和私人的同名欄位。因此訪問一個私人欄位不是按常規的方式尋找。
JavaScript 中的公用欄位可以通過 this.field 或 this[‘field‘] 來訪問。私人欄位不能支援第二種文法(因為它需要是靜態),這樣會導致混淆。
你需要費勁去檢查:
來看一段範例程式碼。
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 什麼都可能是。
有兩個原因導致這個問題:
我們的函數行為依賴於傳入的實值型別,不同的實值型別導致不同的行為:有時候是在訪問私人屬性,有時候又是在尋找公用屬性。
我們每次檢查 otherPoint 的類型:
if (otherPoint instanceof Point && isNotSubClass(otherPoint, Point)) { return getPrivate(otherPoint, ‘foo‘); } else { return otherPoint.foo; }
更糟糕的是,如果我們引用一個私人屬性的話,就不得不在類中對每次屬性訪問進行這樣的檢查。
訪問屬性已經非常慢了,我們絕不想讓它變得更慢。
TL;DR: 我們需要對私人屬性使用 #Hash標記,因為不正常的使用標準屬性訪問機制會導致意外行為,並造成巨大的效能問題。
將私人屬性添加到語言中是件非常好的事情。感謝努力工作在 TC39 勤勞的人們,他們正在讓這美好的事情發生!
著作權聲明本譯文僅用於學習、研究和交流目的,歡迎非商業轉載。轉載請註明出處、譯者和眾成翻譯的完整連結。要擷取包含以上資訊的本文Markdown源文本,請點擊這裡。關注更多學習資料分享!
JavaScript 的新特性:類的 #private 欄位