編程語言按數據類型大致能夠分爲兩類:靜態類型語言與動態類型語言。編程
靜態類型語言在編譯時已肯定變量類型,
動態類型語言的變量類型要到程序運行時,待變量被賦值後,才具備某種類型。設計模式
而JavaScript是一門典型的動態類型語言。數組
動態類型語言對變量類型的寬容使得編程變得很靈活。因爲不用進行類型檢測,咱們能夠調用任何對象的任意方法,而無需去考慮它本來是否被設計爲擁有該方法。而這是創建在鴨子類型的概念上。瀏覽器
鴨子類型通俗的說法是:安全
若是它走起路來像鴨子,叫起來也是鴨子,那麼它就是鴨子。編程語言
鴨子類型指導咱們只關注對象的行爲,而不關注對象自己。函數
在動態類型語言的面向對象設計中,利用鴨子類型的思想,咱們沒必要藉助超類型的幫助,就能輕鬆地在動態類型語言中實現一個原則:「面向接口編程,而不是面向實現編程」。例如:prototype
一個對象如有push和pop方法,而且這些方法提供了正確的實現,它就能夠被看成棧來使用;設計
一個對象如有length屬性,且能夠依照下標來存取屬性,這個對象就能夠被看成數組來使用。code
同一操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果。
咱們說的多態性,其實就是對象的多態性,那麼,對象的多態性是怎樣的?如何讓對象表現出多態性?
對象多態性的一個簡單的例子:
// 讓動物發聲 var makeSound = function(animal){ animal.sound(); } // 鴨子的叫聲 var Duck = function(){}; Duck.prototype.sound = function(){ console.log('嘎嘎嘎'); }; // 小雞的叫聲 var Chicken = function(){}; Chicken.prototype.sound = function(){ console.log('咯咯咯'); } // 讓鴨子發聲 makeSound(new Duck()); // 讓小雞發聲 makeSound(new Chicken()); // 若是像讓小狗發聲,只須要簡單地追加相似的代碼 var Dog = function(){}; Dog.prototype.sound = function(){ console.log('汪汪汪'); } makeSound(new Dog());
靜態類型語言(例如Java)在編譯時會進行類型匹配檢查,這種檢查在帶來安全性的同時,讓代碼顯得僵硬。所以,靜態類型語言一般被設計爲能夠向上轉型:
當給一個類變量賦值時,這個變量的類型既可使用這個類自己,也可使用這個類的超類。
就像咱們在描述「一隻麻雀在飛」,「一隻喜鵲在飛」時,若是想忽略他們的具體類型,能夠說成「一隻鳥在飛」,這時「鳥」就是「麻雀」和「喜鵲」的超類。
而JavaScript是一門沒必要進行類型檢查的動態類型語言。
多態是面向對象編程語言中最重要的技術。
多態最根本的做用就是經過把過程化的條件分支語句轉化爲對象的多態性,從而消除這些條件分支語句。有一個例子能夠很好地詮釋:
在電影的拍攝現場,當導演喊出「anciton」時,主角開始背臺詞,照明師負責打燈光,後面的羣衆演員僞裝中槍倒地,道具師往鏡頭裏撒上雪花。在獲得同一個消息時,每一個對象都知道本身應該作什麼。若是不利用對象的多態性,而是用面向過程的方式來編寫這一段代碼,那麼至關於在電影開始拍攝後,導演每次都要走到每一個人的面前,確認他們的職業分工(類型),而後告訴他們要作什麼。若是映射到程序中,那麼程序中將充斥着條件分支語句。
將行爲分佈在各個對象中,並讓這些對象各自負責本身的行爲,這正是面向對象設計的優勢。
從面向對象設計的角度出發,經過對封裝、繼承、多態、組合等技術的反覆使用,提煉出一些可重複使用的面向對象設計技巧,咱們將其概括爲設計模式。而多態在其中是重中之重,絕大多部分設計模式的實現都離不開多態性的思想。例如:
命令模式
組合模式
策略模式
...
Javascript將函數做爲一等對象,因此函數自己也是對象,函數用來封裝行爲而且可以四處傳遞。當咱們對一些函數發出「調用」的消息時,這些函數會返回不一樣的執行結果,這是多態性的一種體現。
封裝的目的是將信息隱藏。封裝包括:
封裝數據
封裝實現
封裝類型
封裝變化
在許多語言的對象系統中,封裝數據是由語法解析來實現的,這些語言可能提供了private、public、protected等關鍵字來提供不一樣的訪問權限。但JavaScript並無提供對這些關鍵字的支持,只能依賴變量的做用域來實現封裝特性,並且只能模擬出public和private這兩種封裝性。
通常咱們經過函數來建立做用域:
var myObject = (function(){ var __name = 'sven'; //私有(private)變量 return { getName:function(){ //公開(public)方法 return __name; } } })(); console.log(myObject.getName()); //輸出:sven console.log(myObject.__name); //輸出:undefined
從封裝實現細節來說,封裝使得對象內部的變化對其餘對象而言是透明的(即不可見)。對象對它本身的行爲負責。其餘對象或者用戶都不關心它的內部實現。對象使得對象之間的耦合變得鬆散,對象之間只經過暴露的API接口來通訊。
封裝實現細節的例子很是多,例如迭代器。迭代器的做用是在不暴露一個聚合對象的內部表示的前提下,提供一種方式來順序訪問這個聚合對象。如一個each函數,它的做用就是遍歷一個聚合對象,使用這個each函數的人不用關心它的內部代碼是怎麼實現的,只要它提供的功能正確即可以了。
封裝類型是靜態類型語言的一種重要封裝方式。封裝類型是經過抽象類和接口來進行的。
在JavaScript中,並無對抽象類和接口的支持。JavaScript自己也是一門類型模糊的語言。在封裝類型方面,JavaScript沒有能力,也沒有必要作得更多。
從設計模式的角度出發,封裝在更重要的層面體現爲封裝變化。
經過封裝變化的方式,把系統中穩定不變的部分和容易變化的部分隔離開來,在系統的演變過程當中,咱們只須要替換掉那些容易變化的部分,若是這些部分是已經封裝好的,替換起來也想對容易。這能夠最大程度地保證程序的穩定性和可擴展性。
原型模式是用於建立對象的一種模式。
原型模式不用關心對象的具體類型,只需找到一個對象,而後經過克隆來創造一個如出一轍的對象。
原型模式的實現關鍵是語言自己是否提供了clone方法,ECMAScript5提供了Object.create方法,能夠用來克隆對象。
原型模式的真正目的不在於須要獲得如出一轍的對象,而是提供了一種便捷的方式去建立某個類型的對象,克隆只是建立這個對象的過程和手段。
在JavaScript這種類型模糊的語言中,建立對象很是容易,也不存在類型耦合的問題。從設計模式的角度來看,原型模式的意義並不算大。但JavaScript自己是一門基於原型的面嚮對象語言,它的對象系統就是使用原型模式來搭建的,在這裏稱爲原型編程範型也許更合適。
原型編程中有一個重要特性,即當對象沒法響應某個請求時,會把該請求委託給它本身的原型。
而原型編程範型至少包括如下基本原則:
全部的數據都是對象
要獲得一個對象,不是經過實例化類,而是找到一個對象做爲原型並克隆它
對象會記住它的原型
若是對象沒法響應某個請求,它會把這個請求委託給它本身的原型
JavaScript在原型編程範型的規則的基礎上來構建它的對象系統。
JavaScript在設計的時候,模仿Java引入了兩套類型機制:基本類型和對象類型。
按照JavaScript設計者的本意,除了undefined以外,一切都應是對象。爲了實現這一目標,number、boolean等幾種基本類型數據能夠經過「包裝類」的方式變成對象類型數據。
JavaScript絕大部分數據都是對象。事實上,JavaScript中的根對象是Object.prototype對象。Object.prototype對象是一個空對象。JavaScript的每一個對象,都是從Object.prototype對象克隆而來。
JavaScript經過顯式地調用 var obj1 = new Object()
, 或者 var obj2 = {}
。此時,引擎內部會從Object.prototype上面克隆一個對象出來。
這裏用了new運算符從構造器中獲得了一個對象。在JavaScript裏,函數既能夠做爲普通的函數被調用,也能夠做爲構造器被調用。用new運算符來建立對象的過程,實際上也只是先克隆Object.prototype對象,再進行一些其餘額外操做的過程。
就JavaScript的真正實現來講,其實並不能說對象有原型,而只能說對象的構造器有原型。「對象把請求委託給它本身的原型」就是對象把請求委託給它的構造器的原型。
JavaScript給對象提供了一個名爲__proto__的隱藏屬性,某個對象的__proto__屬性默認會指向它的構造器的原型對象,即{Constructor}.prototype。在一些瀏覽器中,__proto__被公開出來。
這條規則是原型繼承的精髓所在。當一個對象沒法響應某個請求時,它會順着原型鏈把請求傳遞下去,直到遇到一個能夠處理請求的對象爲止。
雖然JavaScript的對象最初都是由Object.prototype對象克隆而來,但對象構造器的原型並不只限於Object.prototype上,而是能夠動態地指向其餘對象。例如,當對象A須要對象B的能力時,能夠有選擇地把對象A的構造器的原型指向對象B,從而達到繼承的效果。
PS:本節內容爲《JavaScript設計模式與開發實踐》第一章 筆記。