基礎
js包含三部分:1,ECMAScript語法 2,DOM 3,BOM. 應該說ECMAScript這種東西,語法和許多概念都是源於那些高級語言(C/C++, Java),單就面向對象和繼承來說,js又屬於比較特殊的,它的類是一個構造函數,函數又屬於一種對象,而它又不支持直接的繼承,故而就需要使用prototype或者是冒充對象來實現繼承和多態。有人說js是屬於一種基於對象的,而不是面向對象,因爲面向對象要有三要素: 1, 封裝 2,繼承 3,多態。 而js的多態是通過給不同的對象添加不同的屬性而實現的。其實本質上還是用子類的方法覆蓋父類的方法。
js的面向對象與繼承
js的面向對象
js的類是用函數包裹的,裏面定義了屬性和方法,也就是閉包,google一下什麼是閉包:
在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。
也就是說數據與函數建立了聯繫並共同組成一個實體,且運行離開該函數後,這些數據依然存在。coolshell有一篇文章專門介紹 javascript的閉包。那麼看看代碼是怎麼寫的吧:
典型的工廠模式:
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();
整個過程就是新建一個對象,然後動態地爲對象添加各種屬性方法,就連方法也可以預先定義好,到時候予以賦值。這就很像工廠裏面進行裝配,裝配完就返回——出廠,於是也叫“工廠模式”。
構造函數方式:
就是在一個函數內將所有東西都生產出來,而不是從外面拿。故而函數什麼的要在裏面定義,這就形成了一個很奇特的現象——函數裏面包函數。一般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,顯得更加精簡了一些。其他方面大體相同,這有點像媽媽懷孕一樣,在“肚子”裏把你整個給形成了(今天恰好是母親節~)。
原型方式:
原型方式是使用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的繼承
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有點相似。有函數式編程的味道。以上觀點如有不正確,歡迎評論指正。
END
by bibodeng 2013-05-12