js包含三部分:1,ECMAScript語法 2,DOM 3,BOM. 應該說ECMAScript這種東西,語法和許多概念都是源於那些高級語言(C/C++, Java),單就面向對象和繼承來講,js又屬於比較特殊的,它的類是一個構造函數,函數又屬於一種對象,而它又不支持直接的繼承,故而就須要使用prototype或者是冒充對象來實現繼承和多態。有人說js是屬於一種基於對象的,而不是面向對象,由於面向對象要有三要素: 1, 封裝 2,繼承 3,多態。 而js的多態是經過給不一樣的對象添加不一樣的屬性而實現的。其實本質上仍是用子類的方法覆蓋父類的方法。
javascript
js的類是用函數包裹的,裏面定義了屬性和方法,也就是閉包,google一下什麼是閉包:
css
在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。
html
也就是說數據與函數創建了聯繫並共同組成一個實體,且運行離開該函數後,這些數據依然存在。coolshell有一篇文章專門介紹 javascript的閉包。那麼看看代碼是怎麼寫的吧:java
典型的工廠模式:
python
function sayName(){ alert("hi, I am "+ this.name); } // 工廠模式 function createUser(name,age){ var user = new Object; // 進行裝配 user.name = name; user.age = age; user.sayName = sayName; return user; } var bibo = createUser("bibo", 20); bibo.sayName();
整個過程就是新建一個對象,而後動態地爲對象添加各類屬性方法,就連方法也能夠預先定義好,到時候予以賦值。這就很像工廠裏面進行裝配,裝配完就返回——出廠,因而也叫「工廠模式」。web
構造函數方式:shell
就是在一個函數內將全部東西都生產出來,而不是從外面拿。故而函數什麼的要在裏面定義,這就造成了一個很奇特的現象——函數裏面包函數。通常C++裏面不會出現這樣的東西,可是咱們要有一種觀念,對象既然是構造函數造出來的,那麼這個類和造這個類的函數就能夠劃等號了。而C++的方式叫作「對象模板」會至關貼切。編程
// class User function User(name, age){ this.name = name; this.age = age; // 定義方法 this.sayName = function(){ alert("hi, I am "+ this.name); } } var bibo = new User("bibo", 20); bibo.sayName();
有幾個比較明顯不一樣的地方,如在構造函數內使用this引用,指代該對象。而後在新建的時候用了new操做符,相比工廠模式的new Object,顯得更加精簡了一些。其餘方面大致相同,這有點像媽媽懷孕同樣,在「肚子」裏把你整個給造成了(今天剛好是母親節~)。閉包
原型方式:app
原型方式是使用prototype來定義類所包含的屬性和方法,它使用的是一個空構造函數,而後把整個構造函數的過程實現,使得它可以造出一個對象。用代碼說話吧,由於暫時找不到恰當的隱喻來講明:
function User(){ } User.prototype.name = "bibo"; User.prototype.age = 20; User.prototype.sayName = function(){ alert("hi, I am "+ this.name); } var bibo = new User(); bibo.sayName();
能夠發現,這在寫好構造函數後,依然能夠修改構造函數實現的方法,故而可以達到定製的目的。js的多態是使用這個實現的,由於prototype只有一個,新的會覆蓋舊的。
以上三種很自由的對象構造方法,各有優缺點,能夠混合使用,所以會很是靈活。另外極可能會引出如動態原型方法(動態檢測是否含有原型方法,不然添加)等更加複雜的方式,其實都是這三種演變而來。
js的繼承可使用假冒對象和原型繼承的方式(一個類的原型是另一個類)
對象冒充:
// class A function User(name, age){ this.name = name; this.age = age; // 定義方法 this.sayName = function(){ alert("hi, I am "+ this.name); } } // class B function VIP_User(name, age, money){ this.newMethod = User; // 拷貝一份構造函數 this.newMethod(name, age); // 用構造函數運行 delete this.newMethod; // 繼承完畢 // 新的屬性/方法 this.money = money; this.pay = function(){ alert("pay"); } } var bibo = new VIP_User("bibo", 20, 10000); bibo.sayName(); // 繼承的 bibo.pay(); // 本身的
核心部分就在13-15行,咋一看,是拷貝一份構造函數,而後運行該方法,而後刪除該方法,玄妙就在於14行,在該函數內運行了一遍class A的構造函數後,class 產生的對象中就帶有了A的血液。由於等於把代碼貼過來,運行一遍。
還有一種使用call和apply來運行父類的構造函數的方法,可是要傳入一個this對象。是這樣寫的,把核心的13-15行換成:
User.call(this, name, age);
下面討論另一種不一樣的方式
原型鏈繼承:
// Class A function User(){ } User.prototype.name = "bibo"; User.prototype.age = 20; User.prototype.sayName = function(){ alert("hi, I am "+ this.name); } // Class B function VIP_User(){ } VIP_User.prototype = new User(); // 用A的實例來做爲B的原型 VIP_User.prototype.pay = function(){ alert("pay"); } var bibo = new VIP_User(); bibo.sayName(); bibo.pay();
繼承的在前,而後添加子類的新的方法或屬性。VIP_User以User的實例爲原型,而後才予以擴展的。
以上兩種繼承的方法也能夠混合使用,只要理解了這兩種繼承的思想,就不難在複雜的繼承中混合使用這兩種方法。
剛剛接觸js,就面向對象這兩件東西比較難理解,其餘語法不少都和C++類似,又和動態腳本語言如python有點類似。有函數式編程的味道。以上觀點若有不正確,歡迎評論指正。
by bibodeng 2013-05-12