基於JavaScript如何?私人成員的文法特徵及私人成員的實現方式,javascript文法

來源:互聯網
上載者:User

基於JavaScript如何?私人成員的文法特徵及私人成員的實現方式,javascript文法

前言

在物件導向的編程範式中,封裝都是必不可少的一個概念,而在諸如 Java,C++等傳統的物件導向的語言中, 私人成員是實現封裝的一個重要途徑。但在 JavaScript 中,確沒有在文法特性上對私人成員提供支援, 這也使得開發人員使出了各種奇技淫巧去實現 JS 中的私人成員,以下將介紹下目前實現 JS 私人成員特性的幾個方案以及它們之間的優缺點對比。

現有的一些實現方案

約定命名方案

約定以底線'_'開頭的成員名作為私人成員,僅允許類成員方法訪問調用,外部不得訪問私人成員。簡單的代碼如下:

JavaScript

var MyClass = function () {  this._privateProp = ‘privateProp';};MyClass.prototype.getPrivateProp = function () {  return this._privateProp;};var my = new MyClass();alert(my.getPrivateProp()); // ‘privateProp';alert(my._privateProp); // 並未真正隱藏,依然彈出 ‘privateProp'

優點

毫無疑問,約定命名是最簡單的私人成員實現方案,沒有代碼層面上的工作。
調試方便,能夠在控制台上直接看到對象上的私人成員,方便排查問題。
相容性好,ie6+都支援

不足

無法阻止外部對私人成員的訪問和變更,如果真有不知道或者不遵守約定的開發人員變更私人屬性,也是無能為力。
必須強制或說服大家遵守這個約定,當然這個在有代碼規範的團隊中不是什麼太大的問題。

es6 symbol 方案

在 es6中,引入了一個 Symbol 的特性,該特性正是為了實現私人成員而引入的。
主要的思路是,為每一個私人成員的名稱產生一個隨機且唯一的字串key,這個 key 對外不可見,對內的可見度則是通過 js 的閉包變數實現,範例程式碼如下:

JavaScript

(function() {   var privateProp = Symbol(); // 每次調用會產生一個唯一的key   function MyClass() {     this[privateProp] = ‘privateProp'; // 閉包內引用到這個 key   }   MyClass.prototype.getPrivateProp = function () {     return this[privateProp];   };})();var my = new MyClass();alert(my.getPrivateProp()); // ‘privateProp';alert(my.privateProp); // 彈出 undefined,因為成員的key其實是隨機字串

優點

彌補了命名規範方案的缺陷,外部無法通過正常途徑獲得私人成員的訪問權。
調試便捷程度上可以接受,一般是通過給 symbol 的建構函式傳入一個字串參數,則控制台上對應的私人屬性名稱會展示為:Symbol(key)

相容性不錯,不支援 Symbol的瀏覽器可以很容易的 shim 出來。

不足

寫法上稍顯彆扭,必須為每一個私人成員都建立一個閉包變數讓內部方法可以訪問。
外部還是可以通過 Object.getOwnPropertySymbols的方式擷取執行個體的 symbol 屬性名稱,通過該名稱獲得私人成員的訪問權。這種情境出現得比較少,且知道這種途徑的開發人員水平相信都是有足夠的能力知道自己的行為會有什麼影響,因此這個不足點也算不上真正意義的不足。

es6 WeakMap 方案

在 es6 中引入了 Map, WeakMap 容器,最大的特點是容器的鍵名可以是任意的資料類型,雖說初衷不是為了實現私人成員引入,但意外的可以被用來實現私人成員特性。

主要的思路是,在類的層級上建立一個 WeakMap 容器,用於儲存各個執行個體的私人成員,這個容器對外不可見,對內通過閉包方式可見;內部方法通過將執行個體作為鍵名擷取容器上對應執行個體的私人成員,範例程式碼如下:

JavaScript

(function() {   var privateStore = new WeakMap(); // 私人成員儲存容器   function MyClass() {     privateStore.set(this, {privateProp: ‘privateProp'}); // 閉包內引用到privateStore, 用當前執行個體做 key,設定私人成員   }   MyClass.prototype.getPrivateProp = function () {     return privateStore.get(this).privateProp;    };})();var my = new MyClass();alert(my.getPrivateProp()); // ‘privateProp';alert(my.privateProp); // 彈出 undefined,執行個體上並沒有 privateProp 屬性

優點

彌補了命名規範方案的缺陷,外部無法通過正常途徑獲得私人成員的訪問權。
對 WeakMap 做一些封裝,抽出一個私人特性的實現模組,可以在寫法上相對 Symbol 方案更加簡潔乾淨,其中一種封裝的實現可以查看參考文章3。
最後一個是個人認為最大的優勢:基於 WeakMap 方案,可以方便的實現保護成員特性(這個話題會在其他文章說到:))

不足

不好調試,因為是私人成員都在閉包容器內,無法在控制台列印執行個體查看對應的私人成員
待確認的效能問題,根據 es6的相關郵件清單,weakmap 內部似乎是通過順序一一對比的方式去定位 key 的,時間複雜度為 O(n),和 hash 演算法的 O(1)相比會慢不少

最大的缺陷則是相容性帶來的記憶體膨脹問題,在不支援 WeakMap 的瀏覽器中是無法實現 WeakMap 的弱引用特性,因此執行個體無法被記憶體回收。 比如範例程式碼中 privateProp 是一個很大的資料項目,無弱引用的情況下,執行個體無法回收,從而造成記憶體泄露。

現有實現方案小結

從上面的對比來看,Symbol方案最大優勢在於很容易類比實現;而WeakMap的優勢則是能夠實現保護成員, 現階段無法忍受的不足是無法類比實現弱引用特性而導致的記憶體問題。於是我的思路又轉向了將兩者優勢結合起來的方向。

Symbol + 類WeakMap 的整合方案

在 WeakMap 的方案中最大的問題是無法 shim 弱引用,較次要的問題是不大方便調試。

shim 出來的 WeakMap 主要是無法追溯執行個體的生命週期,而執行個體上的私人成員的生命週期又是依賴執行個體, 因此將執行個體層級的私人成員部分放在執行個體上不就好了? 執行個體沒了,自然其屬性也隨之摧毀。而私人儲存地區的隱藏則可以使用 Symol 來做。

該方案的提供一個 createPrivate 函數,該函數會返回一個私人的 token 函數,對外不可見,對內通過閉包函數獲得, 傳入當前執行個體會返回當前執行個體的私人儲存地區。使用方式如下:

JavaScript

(function() {   var $private = createPrivate(); // 私人成員 token 函數,可以傳入對象參數,會作為原型鏈上的私人成員   function MyClass() {     $private(this).privateProp = ‘privateProp' ; // 閉包內引用到privateStore, 用當前執行個體做 key,設定私人成員   }   MyClass.prototype.getPrivateProp = function () {     return $private(this).privateProp;    };})();var my = new MyClass();alert(my.getPrivateProp()); // ‘privateProp';alert(my.privateProp); // 彈出 undefined,執行個體上並沒有 privateProp 屬性

代碼中主要就是實現 createPrivate 函數,大概的實現如下:

JavaScript

// createPrivate.jsfunction createPrivate(prototype) {  var privateStore = Symbol('privateStore');  var classToken = Symbol(‘classToken');  return function getPrivate(instance) {     if (!instance.hasOwnProperty(privateStore)) {       instance[privateStore] = {};     }    var store = instance[classToken];     store[token] = store[token] || Object.create(prototype || {});     return store[token];   };}

上述實現做了兩層儲存,privateStore 這層是執行個體上統一的私人成員儲存地區,而 classToken 對應的則是繼承層次之間不同類的私人成員定義,基類有基類的私人成員地區,子類和基類的私人成員地區是不同的。

當然,只做一層的儲存也可以實現,兩層儲存僅僅是為了調試方便,可以直接在控制台通過Symbol(‘privateStore')這個屬性來查看執行個體各個層次的私人部分。

奇葩的 es5 property getter 攔截方案

該方案純粹是閑得無聊玩了玩,主要是利用了 es5 提供的 getter,根據 argument.callee.caller 去判斷調用情境,如果是外部的則拋異常或返回 undefined, 如果是內部調用則返回真正的私人成員,實現起來比較複雜,且不支援 strict 模式,不推薦使用。 有興趣的同學可以看看實現。

總結

以上幾個方案對比下來,我個人是傾向 Symbol+WeakMap 的整合方案,結合了兩者的優點,又彌補了 WeakMap 的不足和 Symbol 書寫的冗餘。 當然了,我相信隨著 JS 的發展,私人成員和保護成員也遲早會在文法層面上進行支援,正如 es6 對 class 關鍵字和 super 文法糖的支援一樣, 只是現階段需要開發人員使用一些技巧去填補語言特性上的空白。

Javascript私人成員的實現方式

總體來講這本書還是可以的,但看完這本書還留了幾個問題一直困擾著我,如js中私人變數的實現,prototype等,經過自己一系列測試,現在終於弄明白了。

很多書上都是說,Javascript是不能真正實現Javascript私人成員的,因此在開發的時候,統一約定 __ 兩個底線開頭為私人變數。

後來,發現Javascript中閉包的特性,從而徹底解決了Javascript私人成員的問題。

 function testFn(){     var _Name;//定義Javascript私人成員     this.setName = function(name){      _Name = name; //從當前執行環境中擷取_Name     }     this.getName = function(){      return _Name;     } }// End testFn var test = testFn(); alert(typeof test._Name === "undefined")//true test.setName("KenChen"); 

test._Name 根本訪問不到,但是用對象方法能訪問到,因為閉包能從當前的執行環境中擷取資訊。

接下來我們看看,共有成員是怎樣實現的

function testFn(name){   this.Name = name;   this.getName = function(){    return this.Name;   } } var test = new testFn("KenChen"); test.getName(); //KenChen test.Name = "CC"; est.getName();//CC 

接下來在看看類靜態變數是怎樣實現的

function testFn(){ } testFn.Name = "KenChen"; alert(testFn.Name);//KenChen testFn.Name = "CC"; alert(testFn.Name);//CC 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.