找工做時一些公司給了offer後我就想知道真正拿到手的是多少,畢竟賦稅繁重。但各類稅也好,五險一金也好我實在是弄不清楚,因而我就會在網上的一些稅後收入計算器上進行計算,只須要填寫一些基本信息,好比稅前收入,所在地區等等,就可以得到詳細的結果,包括各類稅收的詳細數值。在這個過程當中,我只是按照接口給定的要求進行了數據的輸入,具體計算過程我並不知道。也就是說,在這個程序內部,數據表現形式和實現細節是隱藏的,這在某種意義上也是封裝的一種。javascript
在Javascript中,對象中的細節有時也須要隱藏,但JS不像其餘的靜態語言,好比Java,同樣,有private
這樣的關鍵字。那麼在Javascript中,就能夠用閉包的概念來建立只能從對象內部訪問的方法和屬性。java
在使用接口實現信息隱藏的過程當中,同時也是使用了接口的概念。比如火影裏的通靈術,人與動物簽定契約,進行某種交換。這中間的溝通渠道不變,簽定契約的人就能夠隨時隨地進行通靈。一個類中,應該定義足夠的安全的接口。然而在JS中,語言特性很是靈活,類中的公有方法和私有方法實際是同樣的。所以,在實現類的時候,應該避免公開未定義於接口的方法。程序員
Javascript中,建立對象的基本模式有三種。
1. 直接建立 對象中的全部方法都是公有的,能夠公開訪問。
2. 使用下劃線 在私有方法名稱前加下劃線,表示該方法私有。
3. 使用閉包 閉包能夠建立真正意義上的私有成員,這些成員只能經過特定方法訪問。安全
所謂直接建立,就是按照傳統的方式建立一個類,構造器是一個函數,屬性和方法所有公開,好比:閉包
var Fruit = function(color, weight) { this.color = color || 'orange'; this.weight = weight || 150; } Fruit.prototype.boom = function() { ... }
這種方法通常來講沒什麼問題,可是當其原型上的方法boom
對自身的屬性color
或者weight
有必定依賴,而構造時傳入的參數不符合必定要求時就會出錯。但若是構造時沒有出錯則全部方法應該能正常工做纔是。函數
固然這個問題能夠在構造對象時就對傳入的參數進行驗證,也不算太嚴重。然而另外一個問題在於,即便能對參數進行驗證,任何程序員仍是可以隨意修改屬性的值。爲了解決這個問題,能夠設計一個數據的取值器和賦值器。單元測試
var Fruit = function(color, weight) { this.setColor(color); this.setWeight(weight); } Fruit.prototype = { checkColor: function(color) { ... }, setColor: function(color) { ... this.color = color; }, getColor: function() { return this.color; }, ... }
當程序員之間約定以提供的方法對屬性值進行操做時,操做過程能夠相對獲得規範。但實際上屬性仍然是公開的,能夠被直接設置,這種方法並不能阻止這種行爲。測試
此種方法與前一種方法實際上是一回事,只是在私用的方法和屬性前加了下劃線表示它是私用的。ui
var Fruit = function(color, weight) { this.setColor(color); this.setWeight(weight); } Fruit.prototype = { _checkColor: function(color) { ... }, setColor: function(color) { ... // 此處對輸入的數據進行驗證,不能經過驗證就拋出異常 this._color = color; }, getColor: function() { return this._color; }, ... }
這種方法有助於防止對私用方法的無心使用,但沒法保證程序員不有意使用它們。this
藉助閉包就能夠建立只容許特定函數訪問的變量了,私用屬性的建立方法便是在構造函數的做用域中建立變量便可,這些變量能夠被該做用域中的全部函數訪問。
var Fruit = function(newColor, weight) { var color, weight; // 私用方法 function _checkColor = function(color) { ... } // 特權方法 this.getColor = function() { return color; }; this.setColor = function(newColor) { ... // 驗證輸入 color = newColor; } // 構造過程代碼 this.setColor(newColor); }
藉助this
關鍵字建立的方法就是特權方法了,它們是公開方法,同時也可以訪問私有變量。若是須要建立一些不須要訪問私有屬性的方法的話,能夠在Fruit.prototype
上進行建立。經過這種方式建立的方法,不能直接訪問私有變量,可是能夠經過getColor
這樣的特權方法進行間接訪問。
固然這種方式也有弊端,若是特權方法過多,會佔用較多內存,由於經過構造函數建立的實例都保存了特權方法的副本,而原型上的方法只有一份。所以,在設計構造函數時,須要進行慎重考慮。
另外一個問題就在於這樣的方法沒法做用於須要建立子類的場景,因爲特權方法都是新的副本,因此子類沒法訪問超類的任何私用屬性或方法。所以在Javascript中,這種問題被稱做「繼承破壞封裝」(inheritance breaks encapsulation)。
以上的三種方法屬於創造對象的基本方法,但要實現一些高級的建立對象模式,有必要先了解一些概念。
前述的閉包建立對象法能夠建立私用屬性和方法,可是這樣的話,這些屬性在建立子類時會同時建立副本存於子類,形成內存的浪費。對於一些只須要在類層面進行操做和訪問的屬性,能夠利用閉包建立靜態成員。靜態成員每一個只有一份,直接經過類對象進行訪問。
仍然以以前的Orange
類爲例,使用閉包特性添加一些靜態成員:
var Orange = (function() { // 私用靜態屬性 var numOfOranges = 0; // 私用靜態方法 function checkColor() { ... } // 返回構造器 return function(newColor, weight) { var color, weight; // 特權方法 this.getColor = function() { return color; }; this.setColor = function(newColor) { ... color = newColor; } // 構造器代碼 numOfOrange++; if (numOfOrange > 100) { throw new Error('Only 100 instances of Orange can be created.'); } this.setColor(newColor); } })(); // 公開靜態方法 Orange.turnToJuice = function() { // 不添加在Orange的prototype上 ... }; // 公開的非特權方法 Orange.prototype = { checkWeight: function() { ... } };
這與以前的閉包建立類的最大區別在於構造器由一個普通函數變成了一個內嵌函數,經過一個自執行函數的返回值賦給Orange
。在實例化Orange
時,調用的是返回的內嵌函數,外層函數只是一個用來存放靜態私用成員的閉包。在構造器中的特權方法能夠訪問構造器以外的靜態屬性和方法,而靜態方法不能訪問任何定義在構造器中的私用屬性。
常量就是不能被修改的變量,利用靜態屬性能夠在Javascript中模擬常量。對常量只建立做爲取值器的靜態方法,而不建立賦值器,在外圍做用域中也不能訪問到常量:
var Class = (function() { // 常量 var CONST = 100; // 構造器 var constructorFunc = function(param) { ... }; // 靜態方法,取值器 constructorFunc = function() { return CONST; }; return constructorFunc; })();
保護內部數據,只對外提供取值器和賦值器,便於重構。減小模塊間耦合。
難以進行單元測試,外部測試沒法訪問到內部變量和方法。不過如公用方法能夠間接訪問私用方法的話,能夠對私用方法進行間接單元測試。
實現過程較爲複雜,調試難度比較大。