GOF在《設計模式》中說到:面向接口編程,而非面向實現編程javascript
鑑於此,這個概念可見一斑!java
JS卻不像其餘面向對象的高級語言(C#,Java,C++等)擁有內建的接口機制,以肯定一組對象和另外一組對象包含類似的的特性。所幸的是JS擁有強大的靈活性,這使得模仿接口特性又變得很是簡單。那麼究竟是接口呢?編程
接口概念:設計模式
接口提供了一種用以說明一個對象應該具備那些方法的手段閉包
接口,爲一些具備類似行爲的類之間(可能爲同一種類型,也可能爲不一樣類型)提供統一的方法定義,使這些類之間可以很好的實現通訊app
使用接口的優勢:框架
一個需求,須要多個部門協調合做的時候,接口的概念就特別重要了,每一個部門能夠循序漸進的作本身的事情,涉及到交互的時候,提供接口處理便可函數
就好像主板上的內存條,CPU,硬盤,主板提供各類接口,其餘設備直接按相應的接口插入便可this
javascript語言要實現接口的概念仍是有侷限性的問題的spa
javascript中模仿接口
經常使用的幾種手法:
接口自己就是一個抽象的概念,至於如何實現各有不一樣
這是個人一段項目代碼,接口是經過模塊閉包實現的,這樣的好處更能體現出封裝性
//引入編譯模塊 define('ProcessMgr', [ 'Compile' ], function(compile) { var flipContentProcessed, flipWidgetProcessed, disposeWidgetBind, objectKeys, converWidgetWapper, ActionMgr, getHotspot, //內部消息傳遞接口 // // 1 構建節點樹 // A 構建純DOM節點 // B 構建content對象, 這是第二種加載模式,動態預加載 // 2 綁定節點事件 // 3 手動觸發事件 // 4 自動觸發事件 // 5 事件委託處理 // 6 翻頁動做 // 7 翻頁完成動做 // 8 復位動做 // 9 銷燬動做 // 10 移除整個頁面結構 //ProcessMgr =
{ 'preCompile' : preCompile, 'executeCompile' : executeCompile, 'assignTrigger' : assignTrigger, 'assignAutoRun' : assignAutoRun, 'assignSuspend' : assignSuspend, 'assignDispose' : assignDispose, 'recovery' : recovery, 'destroy' : destroy, 'removePage' : removePage }, ...........具體處理的方法................... return ProcessMgr; //返回對外接口 })
jQuery的接口更加的直接,直接掛在在對應的原型鏈上或是靜態鏈
封裝
爲何要封裝?
由於咱們不關心它是如何實現的,咱們只關心如何使用
手機,電腦,咱們接觸的形形色色的東西,其實咱們一直都只是在使用它
一樣的道理,咱們放到程序設計中也是如此,封裝實現的細節 ,下降對象之間的耦合程序,保持數據的完整性與修改的約束
因此說一個設計良好的API可讓開發者賞心悅目
javascript實現封裝的手段
對於JS語法規則,咱們要緊緊抓住3點
根據這3個規則咱們就能夠幹不少別的語言幹不了的事了
咱們來模擬一個完整的封裝
私有屬性和方法
var encapsulation = function(){ //私有屬性 var name = 'aaron' //私有方法 var getName = function(){ alert(name) } } alert(name) //空
函數做用域實現變量與方法私有化,外邊天然沒法方法,固然這種徹底沒有實際意義了,咱們要配合後面的處理
特權屬性和方法
簡單的說就可以讓實例直接有權力訪問內部的屬性與方法,因此在設計上要修改下實現手法,
var encapsulation = function(){ //特權 this.name = 'aaron' this.getName = function(){ alert(name) } } alert(new encapsulation().name) //aaron
弊端也顯而易見,每次new一個新的對象,特權屬性與方法都會被從新拷貝一份,也就是須要單獨佔據一塊堆內存
共有屬性和方法
*注意了,命名函數與函數表達式在本質上雖然沒什麼區別,可是在處理上仍是有很大不一樣
函數表達式
var encapsulation = function(){ //靜態屬性方法 encapsulation.name = 'aaron' encapsulation.getName = function(){ alert(name) } } console.log( encapsulation.name ) // 無
命名函數
即使方法不執行,靜態屬性也能訪問到,就證實了JS有一種預解析的機制,也就是常說的函數提高了
function encapsulation(){ //靜態屬性方法 encapsulation.name = 'aaron' encapsulation.getName = function(){ alert(name) } } console.log( encapsulation.name ) // aaron
共有靜態屬性和方法
這是最最經常使用的,JS 的prototype原型特性,能夠共享全部屬性與方法,固然,屬性是引用類型的時候就會存在問題了
var encapsulation = function(){ //共享屬性方法 encapsulation.prototype.name = 'aaron' encapsulation.prototype.getName = function(){ alert(name) } } console.log(new encapsulation().name ) // aaron console.log(new encapsulation().name ) // aaron
繼承
大型設計中,代碼確定是不少的,因此咱們須要減小重複性的代碼,儘量的弱化對象之間的耦合:
經過幾種手段
固然並不是全部的重複代碼均可以使用一種繼承方法,因此咱們通常要區分共性是否一致
我項目中使用的繼承手段
呵呵,眼熟吧,借鑑EXT的處理機制,有繼承與混入,本身改了點,畢竟那種分層結構,倒序調用,還有點不合適套用,下文會介紹實現
類式繼承
如今的框架,庫大多繼承的手段都是類式繼承,並且繼承的處理方式也基本一致,可是裏面的細節問題可能你們沒注意到,咱們一塊兒分析下
咱們看看幾個框架的繼承實現
mootools
Class
Backbone
extend
ext
extend
ext比較特殊,由於是經過注入的手法,實現繼承了類名轉化繼承混入靜態擴充
對比幾個框架,咱們紅線部分的相同之處沒?實現原理確是同樣的,包括EXT也同樣,只是在具體實現上增長了各自的方案,
設計的原理
拋開復雜的框架,咱們看看設計的底層原理是什麼,又會有什麼問題?
原型鏈做爲JS實現繼承的主要方法,其根本的思路利用原型鏈讓一個引用類型,繼承另外一個引用類型
原型與實例的關係:
構造器有一個原型對象,原型對象有一個指針指向構造器,那麼實例則是包含一個指向原型的指針
因此實例只與原型有關係
實現中的細節:
function SuperType(){ //父類 this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ //子類 this.subproperty = false; } //繼承了 SuperType SubType.prototype = new SuperType(); //實現原型繼承,引用 SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
1 繼承的本質是引用,那麼N多組實例其實都是操做的同一個引用,那麼問題來了,若是父類中有個一引用屬性,那麼一個子類操做修改了,全部的子類都會被影響
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //繼承了 SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black"
2 在解決原型中包含引用類型值所帶來問題的過程當中,開發人員開始使用一種叫作借用構造函數(constructor stealing)的技術(有時候也叫作僞造對象或經典繼承)
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //繼承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
經過使用 call()方法(或 apply()方法也能夠),咱們其實是在(將來將要)新建立的 SubType 實例的環境下調用了 SuperType 構造函數。這樣一來,就會在新 SubType 對象上執行 SuperType()函數中定義的全部對象初始化代碼。結果,SubType 的每一個實例就都會具備本身的 colors 屬性的副本了
借用構造函數的問題
若是僅僅是借用構造函數,那麼也將沒法避免構造函數模式存在的問題——方法都在構造函數中定義,所以函數複用就無從談起了。並且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果全部類型都只能使用構造函數模式。考慮到這些問題,借用構造函數的技術也是不多單獨使用的
經過SubType.prototype = new SuperType(); 的方式實現引用的繼承,看似很簡單,可是裏面還有幾個問題
看看Backbone的如何處理
var ctor = function () { };
ctor.prototype = parent.prototype; child.prototype = new ctor(); child.prototype.constructor = child;
可見backbone引用一箇中介ctor函數
其實就是一種代理繼承
proxy來實現繼承,這樣就避免了在classical繼承中出現的parent的constructor被使用兩次帶來的效率問題和在原型鏈中再次繼承this的屬性
function obj (o){ var f = {} f.prototype = o return new f(); }
最後還要修正一下constructor的指針,由於是繼承ctor了
至於原型式繼承能夠參考高級程序設計3, 比較詳細了