摘要:創(chuàng)建對象中,創(chuàng)建對象的基本模式有三種。因此,在設計構造函數(shù)時,需要進行慎重考慮。因此在中,這種問題被稱作繼承破壞封裝。靜態(tài)成員每個只有一份,直接通過類對象進行訪問。
什么是封裝
找工作時一些公司給了offer后我就想知道真正拿到手的是多少,畢竟賦稅繁重。但各種稅也好,五險一金也好我實在是弄不清楚,于是我就會在網(wǎng)上的一些稅后收入計算器上進行計算,只需要填寫一些基本信息,比如稅前收入,所在地區(qū)等等,就能夠獲得詳細的結果,包括各種稅收的詳細數(shù)值。在這個過程中,我只是按照接口給定的要求進行了數(shù)據(jù)的輸入,具體計算過程我并不知道。也就是說,在這個程序內(nèi)部,數(shù)據(jù)表現(xiàn)形式和實現(xiàn)細節(jié)是隱藏的,這在某種意義上也是封裝的一種。
在Javascript中,對象中的細節(jié)有時也需要隱藏,但JS不像其他的靜態(tài)語言,比如Java,一樣,有private這樣的關鍵字。那么在Javascript中,就可以用閉包的概念來創(chuàng)建只能從對象內(nèi)部訪問的方法和屬性。
在使用接口實現(xiàn)信息隱藏的過程中,同時也是使用了接口的概念。好比火影里的通靈術,人與動物簽訂契約,進行某種交換。這中間的溝通渠道不變,簽訂契約的人就可以隨時隨地進行通靈。一個類中,應該定義足夠的安全的接口。然而在JS中,語言特性非常靈活,類中的公有方法和私有方法實際是一樣的。因此,在實現(xiàn)類的時候,應該避免公開未定義于接口的方法。
創(chuàng)建對象Javascript中,創(chuàng)建對象的基本模式有三種。
1. 直接創(chuàng)建 對象中的所有方法都是公有的,可以公開訪問。
2. 使用下劃線 在私有方法名稱前加下劃線,表示該方法私有。
3. 使用閉包 閉包可以創(chuàng)建真正意義上的私有成員,這些成員只能通過特定方法訪問。
所謂直接創(chuàng)建,就是按照傳統(tǒng)的方式創(chuàng)建一個類,構造器是一個函數(shù),屬性和方法全部公開,比如:
var Fruit = function(color, weight) { this.color = color || "orange"; this.weight = weight || 150; } Fruit.prototype.boom = function() { ... }
這種方法一般來說沒什么問題,但是當其原型上的方法boom對自身的屬性color或者weight有一定依賴,而構造時傳入的參數(shù)不符合一定要求時就會出錯。但如果構造時沒有出錯則所有方法應該能正常工作才是。
當然這個問題可以在構造對象時就對傳入的參數(shù)進行驗證,也不算太嚴重。然而另一個問題在于,即使能對參數(shù)進行驗證,任何程序員還是能夠隨意修改屬性的值。為了解決這個問題,可以設計一個數(shù)據(jù)的取值器和賦值器。
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; }, ... }
當程序員之間約定以提供的方法對屬性值進行操作時,操作過程可以相對得到規(guī)范。但實際上屬性仍然是公開的,可以被直接設置,這種方法并不能阻止這種行為。
使用下劃線,區(qū)別私用成員此種方法與前一種方法其實是一回事,只是在私用的方法和屬性前加了下劃線表示它是私用的。
var Fruit = function(color, weight) { this.setColor(color); this.setWeight(weight); } Fruit.prototype = { _checkColor: function(color) { ... }, setColor: function(color) { ... // 此處對輸入的數(shù)據(jù)進行驗證,不能通過驗證就拋出異常 this._color = color; }, getColor: function() { return this._color; }, ... }
這種方法有助于防止對私用方法的無意使用,但無法保證程序員不有意使用它們。
使用閉包借助閉包就可以創(chuàng)建只允許特定函數(shù)訪問的變量了,私用屬性的創(chuàng)建方法即是在構造函數(shù)的作用域中創(chuàng)建變量即可,這些變量可以被該作用域中的所有函數(shù)訪問。
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關鍵字創(chuàng)建的方法就是特權方法了,它們是公開方法,同時也能夠訪問私有變量。如果需要創(chuàng)建一些不需要訪問私有屬性的方法的話,可以在Fruit.prototype上進行創(chuàng)建。通過這種方式創(chuàng)建的方法,不能直接訪問私有變量,但是可以通過getColor這樣的特權方法進行間接訪問。
當然這種方式也有弊端,如果特權方法過多,會占用較多內(nèi)存,因為通過構造函數(shù)創(chuàng)建的實例都保存了特權方法的副本,而原型上的方法只有一份。因此,在設計構造函數(shù)時,需要進行慎重考慮。
另一個問題就在于這樣的方法無法作用于需要創(chuàng)建子類的場景,由于特權方法都是新的副本,所以子類無法訪問超類的任何私用屬性或方法。因此在Javascript中,這種問題被稱作“繼承破壞封裝”(inheritance breaks encapsulation)。
高級創(chuàng)建對象模式初探以上的三種方法屬于創(chuàng)造對象的基本方法,但要實現(xiàn)一些高級的創(chuàng)建對象模式,有必要先了解一些概念。
靜態(tài)方法和屬性前述的閉包創(chuàng)建對象法可以創(chuàng)建私用屬性和方法,但是這樣的話,這些屬性在創(chuàng)建子類時會同時創(chuàng)建副本存于子類,造成內(nèi)存的浪費。對于一些只需要在類層面進行操作和訪問的屬性,可以利用閉包創(chuàng)建靜態(tài)成員。靜態(tài)成員每個只有一份,直接通過類對象進行訪問。
仍然以之前的Orange類為例,使用閉包特性添加一些靜態(tài)成員:
var Orange = (function() { // 私用靜態(tài)屬性 var numOfOranges = 0; // 私用靜態(tài)方法 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); } })(); // 公開靜態(tài)方法 Orange.turnToJuice = function() { // 不添加在Orange的prototype上 ... }; // 公開的非特權方法 Orange.prototype = { checkWeight: function() { ... } };
這與之前的閉包創(chuàng)建類的最大區(qū)別在于構造器由一個普通函數(shù)變成了一個內(nèi)嵌函數(shù),通過一個自執(zhí)行函數(shù)的返回值賦給Orange。在實例化Orange時,調(diào)用的是返回的內(nèi)嵌函數(shù),外層函數(shù)只是一個用來存放靜態(tài)私用成員的閉包。在構造器中的特權方法可以訪問構造器之外的靜態(tài)屬性和方法,而靜態(tài)方法不能訪問任何定義在構造器中的私用屬性。
常量常量就是不能被修改的變量,利用靜態(tài)屬性可以在Javascript中模擬常量。對常量只創(chuàng)建作為取值器的靜態(tài)方法,而不創(chuàng)建賦值器,在外圍作用域中也不能訪問到常量:
var Class = (function() { // 常量 var CONST = 100; // 構造器 var constructorFunc = function(param) { ... }; // 靜態(tài)方法,取值器 constructorFunc = function() { return CONST; }; return constructorFunc; })();封裝之利弊 封裝之利
保護內(nèi)部數(shù)據(jù),只對外提供取值器和賦值器,便于重構。減少模塊間耦合。
封裝之弊難以進行單元測試,外部測試無法訪問到內(nèi)部變量和方法。不過如公用方法可以間接訪問私用方法的話,可以對私用方法進行間接單元測試。
實現(xiàn)過程較為復雜,調(diào)試難度比較大。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/87512.html
摘要:訂閱模式的一個典型的應用就是后面會寫一篇相關的讀書筆記。享元模式享元模式的核心思想是對象復用,減少對象數(shù)量,減少內(nèi)存開銷。適配器模式對目標函數(shù)進行數(shù)據(jù)參數(shù)轉化,使其符合目標函數(shù)所需要的格式。 設計模式 單例模式 JS的單例模式有別于傳統(tǒng)面向對象語言的單例模式,js作為一門無類的語言。使用全局變量的模式來實現(xiàn)單例模式思想。js里面的單例又分為普通單例和惰性單例,惰性單例指的是只有這個實例...
摘要:如果為假值,不傳或者傳入,函數(shù)都會返回但是,傳入這個值是完全有可能的,所以這種判斷形勢是不正確的或者使用來判斷也可以原始類型優(yōu)于封裝類型對象擁有六個原始值基本類型布爾值,數(shù)字,字符串,,和對象。 作為一個前端新人,多讀書讀好書,夯實基礎是十分重要的,正如蓋樓房一樣,底層穩(wěn)固了,才能越壘越高。從開始學習到現(xiàn)在,基礎的讀了紅寶書《JavaScript高級程序設計》,犀牛書《JavaScri...
摘要:繼承的是超類型中構造函數(shù)中的屬性,如上繼承了屬性,但沒有繼承原型中的方法。上述造成的結果是子類型實例中有兩組超類型的構造函數(shù)中定義的屬性,一組在子類型的實例中,一組在子類型實例的原型中。 ECMAScript只支持實現(xiàn)繼承,主要依靠原型鏈來實現(xiàn)。與實現(xiàn)繼承對應的是接口繼承,由于script中函數(shù)沒有簽名,所以無法實現(xiàn)接口繼承。 一、原型鏈 基本思想:利用原型讓一個引用類型繼承另一個引用...
摘要:簡單的門面模式實例事件綁定函數(shù)門面模式的作用是將復雜的接口進行包裝,變成一個便于使用的接口。還是以事件相關為例,事件綁定中還有兩個常用的分別是和。 門面模式是什么,與其我去用笨拙的語言去解釋,不如看下面這張圖,曾經(jīng)在網(wǎng)上很火的一張圖片,說的是一位兒子為他的爸媽設置的電腦桌面。 showImg(http://segmentfault.com/img/bVcgHm); 有了這些起好名字...
摘要:對象字面量定義一個空對象這里的空指的是其自身屬性為空,對象繼承了的屬性和方法添加屬性方法完全刪除屬性方法自定義構造函數(shù)用操作符調(diào)用構造函數(shù)時,函數(shù)內(nèi)部會發(fā)發(fā)生以下情況創(chuàng)建一個新對象,并且引用了該對象并繼承了該函數(shù)的原型屬性和方法被加入到的引 對象字面量 //定義一個空對象,這里的空指的是其自身屬性為空,dog對象繼承了Object.prototype的屬性和方法 var dog={} ...
閱讀 1813·2021-11-22 09:34
閱讀 3097·2019-08-30 15:55
閱讀 676·2019-08-30 15:53
閱讀 2067·2019-08-30 15:52
閱讀 3009·2019-08-29 18:32
閱讀 1999·2019-08-29 17:15
閱讀 2405·2019-08-29 13:14
閱讀 3566·2019-08-28 18:05