為什麼 JavaScript 的私有屬性使用 # 符號

justjavac發表於2018-10-22

這幾天 JavaScript 的私有屬性又成為了前端社群熱議的話題。原因很簡單,這傢伙長這樣:

為什麼 JavaScript 的私有屬性使用 # 符號

驚不驚喜!意不意外!

而且 TC39 委員會以及對此達成了一致意見,並且該提案已經進入了 stage 3。在 es 規範階段 stage 3 是候選提案,又很大的可能會進入到下一個標準。到目前為止,已經可以確定會進入 es2019(es10) 標準的有 optional-catch-binding 和 proposal-json-superset。而這個 private 大概也只能進入到 es2020 後的標準了。

TC39 委員會解釋道,他們也是做了深思熟慮最終選擇了 # 符號,而沒有使用 private 關鍵字。其中還討論了把 private# 符號一起使用的方案。並且還打算預留了一個 @ 關鍵字作為 protected 屬性?,不過這個 @ 已經被 decorator 使用了。

我們在此就不討論這個語法到底醜不醜了,這不顯而易見的嗎,還用討論嗎。我們討論一下為什麼要使用 # 符號。

有人說,“如果這個進入了規範,我就再也不用 JavaScript了,以後專案都用 TypeScript 寫”。

這就有點由不得你了,我們看 TypeScript 的官網介紹:

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

TypeScript 是 JavaScript 的超集,言外之意就是 js 有的 ts 也全都有。對於一個已經進入 stage 3 的提案,ts 當然不會坐視不理的。ts 也正在抓緊支援這個語法:All Bloomberg Changes for Private Fields and Methods

也有人疑問:“為什麼 ts 的私有屬性就可以使用 private 關鍵字,而 js 就不行呢?”

因為 ts 的 private 和這個 # 語法完全是不同的東西。TypeScript 是一種靜態型別語言而不是強型別語言(有爭議)。private 只是在編譯時做檢查,最終生成的 js 程式碼中被 private 修飾的屬性依然是公開的。

那為什麼不使用下劃線(_)呢,畢竟這個已經是私有屬性的非正式的最佳實踐了?

之所以不選擇下劃線是出於相容性考慮,另一個由於相容性而妥協的提案是 JavaScript 社群由一個庫引發的“smoosh門”事件到底怎麼回事? 由於下劃線是合法的 js 變數識別符號所以很多的程式碼都使用了下劃線,如果新的 js 規範把下劃線開頭的變數作為私有屬性,那麼會導致很多就程式碼無法執行。比如知名的 lodash 和 underscore 庫就是使用的下劃線,這兩個庫每週在 npm 的下載量是 2 千萬。

“為什麼不使用 private?”

這裡有一個經典的例子:

class Foo {
  private value;

  equals(foo) {
    return this.value === foo.value;
  }
}
複製程式碼

在很多物件導向語言中都有這種寫法,判斷類的兩個例項是否相等。如果在 js 中也這麼寫,會有一些問題。

在靜態型別語言中,我們可以明確的知道傳入的引數是 Foo 型別,因此在 Fooequals 方法中我們可以訪問 foo 的私有屬性 value。但是 js 是動態型別的,我們無法知道引數 foo 的型別,如果我們傳入了 {value: '123'} 會,則函式的行為不符合我們的預期。這也導致了該函式有時訪問的是私有屬性,有時訪問的是公有屬性。

另一方面,私有屬性只在類的內部可以訪問,外部無法訪問,甚至不知道此變數的存在。因此在 JavaScript 中同名的私有屬性和公有屬性可以同時存在,兩者使用 # 做區分。

如果使用 private 進行修飾,則我們可以探測類的私有屬性:

foo.bar = 1; // Error: `bar` is private! (boom... detected)
複製程式碼

或者:

foo.bar = 1;
foo.bar; // `undefined` (boom... detected again)
複製程式碼

PS:雖然社群的整體意見是使用 private 代替 #,但是 TC39 的意見很堅決。

最後,這種引入私有屬性的方式,其實隱式的為 js 引入了靜態型別。

相關文章