這篇文章帶給大家的內容是關於js中私有成員的全面解析(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
Class field declarations for JavaScript(JavaScript 類別的欄位宣告)目前已經進入了 stage-3,其中包含一項 OOP 開發者都很注意的內容:Private fields。 JavaScript 一直沒有私有成員並不是沒有原因,所以這項提議為 JavaScript 帶來了新的挑戰。但同時,JavaScript 在 ES2015 發布的時候已經在考慮私有化的問題了,所以要實作私有成員也並非毫無基礎。
先挖個坑 —— 這是一段 JS 程式碼,BusinessView
中要乾兩件事情,也就是對表單和地圖進行佈局。
class BaseView { layout() { console.log("BaseView Layout"); } } class BusinessView extends BaseView { layout() { super.layout(); this._layoutForm(); this._layoutMap(); } _layoutForm() { // .... } _layoutMap() { // .... } }
然後,由於業務的發展,發現有很多視圖都存在地圖佈局。這裡選用繼承的方式來實現,所以從BusinessView
中把地圖相關的內容抽象成一個基底類別叫做MapView
:
class MapView extends BaseView { layout() { super.layout(); this._layoutMap(); } _layoutMap() { console.log("MapView layout map"); } } class BusinessView extends MapView { layout() { super.layout(); this._layoutForm(); this._layoutMap(); } _layoutForm() { // .... } _layoutMap() { console.log("BusinessView layout map"); } }
上面這兩段程式碼是很典型的基於繼承的OOP 思想,本意是期望各個層次的類別都可以透過layout()
來進行各層次應該負責的佈局任務。但理想和現實總是有差距的,在 JavaScript 中運行就會發現 BusinessView._layoutMap()
被執行了兩次,而 MapView._layoutMap()
未執行。 為什麼?
JavaScript 中如果在祖先和子孫類別中定義了相同的名稱的方法,預設會呼叫子孫類別中的這個方法。如果想要呼叫祖先類別中的同名方法,需要在子孫類別中透過 super.
來呼叫。
這裡可以分析一下這個過程:
在子類別建立物件的時候,其類別和所有祖先類別的定義都已經載入了。這時候
呼叫BusinessView.layout()
#找到super.layout()
,開始呼叫MapView.layout()
MapView.layout()
中呼叫this._layoutMap()
於是從目前物件(BusinessView
物件)尋找_layoutMap()
找到,呼叫它
定義了_layoutMap
,所以壓根都沒去搜尋原型鏈。對的,這是基於原型關係的 OOP 的限制。如果我們看看C# 的處理過程,就會發現有所不同
,開始呼叫
MapView.layout()#MapView.layout()
中呼叫
如果不是,直接呼叫在
MapView
中找到
檢查是否虛擬函數
然而,這跟私有成員又有什麼關係呢?因為私有函數絕對不是虛函數,所以在C# 中,如果將
_layoutMap
MapView.layout()呼叫的就一定是
MapView._layoutMap( )
。 ######虛函數的概念有點小複雜。不過可以簡單理解為,如果一個成員方法被宣告為虛函數,在呼叫的時候就會延著其虛函數鏈找到最後的重載來進行呼叫。 ######JavaScript 中雖然約定 ###_### 前綴的是私有,那也只是君子之約,它實質上仍然不是私有。君子之約對人有效,計算機又不知道你有這個約定……。但是,如果 JavaScript 真的實作了私有成員,那麼電腦就知道了,###_layoutMap()### 是個私有方法,應該呼叫本類別中的定義,而不是去尋找子類別中的定義。 ######解決當下的私有化問題######JavaScript 當下沒有私有成員,但是我們又需要切時有效地解決私有成員問題,怎麼辦?當然有辦法,用 ###Symbol### 和閉包來解決。 ######注意,這裡的閉包不是指導在函數函數中產生閉包,請繼續往下看#######首先搞清楚,我們變通的看待這個私有化問題—— 就是讓祖先類別呼叫者在呼叫某個方法的時候,它不會先去子類別尋找。這個問題從語法解決不了,JavaScript 就是要從具體的實例從後往前去找指定名稱的方法。但是,如果找不到這個方法名稱呢? ###之所以能找到,因为方法名是字符串。一个字符串在全局作用域内都表示着同样的意义。但是 ES2015 带来了 Symbol
,它必须实例化,而且每次实例化出来一定代表着不同的标识 —— 如果我们将类定义在一个闭包中,在这个闭包中声明一个 Symbol
,用它来作为私有成员的名称,问题就解决了,比如
const MapView = (() => { const _layoutMap = Symbol(); return class MapView extends BaseView { layout() { super.layout(); this[_layoutMap](); } [_layoutMap]() { console.log("MapView layout map"); } } })(); const BusinessView = (() => { const _layoutForm = Symbol(); const _layoutMap = Symbol(); return class BusinessView extends MapView { layout() { super.layout(); this[_layoutForm](); this[_layoutMap](); } [_layoutForm]() { // .... } [_layoutMap]() { console.log("BusinessView layout map"); } } })();
而现代基于模块的定义,甚至连闭包都可以省了(模块系统会自动封闭作用域)
const _layoutMap = Symbol(); export class MapView extends BaseView { layout() { super.layout(); this[_layoutMap](); } [_layoutMap]() { console.log("MapView layout map"); } }
const _layoutForm = Symbol(); const _layoutMap = Symbol(); export class BusinessView extends MapView { layout() { super.layout(); this[_layoutForm](); this[_layoutMap](); } [_layoutForm]() { // .... } [_layoutMap]() { console.log("BusinessView layout map"); } }
改革过后的代码就可以按预期输出了:
BaseView Layout MapView layout map BusinessView layout map
相关推荐:
以上是js中私有成員的全面解析(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!