首頁  >  文章  >  web前端  >  js中私有成員的全面解析(附程式碼)

js中私有成員的全面解析(附程式碼)

不言
不言原創
2018-08-14 11:09:521940瀏覽

這篇文章帶給大家的內容是關於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()

  • 找到,呼叫它

  • 你看,由於
  • BusinessView

    定義了_layoutMap,所以壓根都沒去搜尋原型鏈。對的,這是基於原型關係的 OOP 的限制。如果我們看看C# 的處理過程,就會發現有所不同

  • 呼叫
      BusinessView.layout()
    • # #找到
    • base.layout()

      ,開始呼叫

      MapView.layout()
    • #MapView.layout()中呼叫

      this._layoutMap()
    #如果是,請向子類別找到最後一個重載(override)函數,呼叫

如果不是,直接呼叫MapView 中找到

_layoutMap()

檢查是否虛擬函數

發現差異了嗎?關鍵是在於判斷「虛函數」。

然而,這跟私有成員又有什麼關係呢?因為私有函數絕對不是虛函數,所以在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中对执行上下文以及变量对象的解析

js中字符方法以及字符串操作方法的总结(附代码)

以上是js中私有成員的全面解析(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn