澄清在先Java 和Javascript是雷鋒和雷峰塔的關系Javascript原名Mocha當時還叫做LiveScript創造者是Brendan Eich現任Mozilla公司首席技術官
年歷史上第一個比較成熟的網絡浏覽器——Navigator版誕生在網景公司(Netscape)極為轟動
但是Navigator只能用來浏覽不具備與訪問者交互的能力比如用戶提交一個數據表單如果表單為空浏覽器是無法判斷的只能直接提交給服務器端再把空值的錯誤返回讓用戶重新填寫這樣顯然是低效率和浪費資源的
這個時候對於正處於技術革新最前沿的 Netscape開發一種實用的客戶端腳本語言來處理這些問題變得必要起來於是這個任務落到了工程師Brendan Eich身上他覺得吧木必要設計得很復雜只要能搞定一些簡單操作就夠了比如判斷用戶有沒有填寫表單
年正是面向對象編程(objectoriented programming)的興盛時代C++是最流行的語言而Java語言的版即將於次年推出Brendan Eich難免受其影響 他想將Javascript裡面所有的數據類型看做是對象(object)這一點與Java非常相似但是他馬上就遇到了一個難題到底要不要設計”繼承”機制呢?
采用new關鍵字生成實例
處理表單驗證這樣簡單功能腳本語言顯然是不需要”繼承”機制的然而如果Javascript裡面都是對象就需要有一種辦法來把所有對象聯系起來最後Brendan Eich還是設計了”繼承”只是他並沒有引入”類”(class)的概念因為一旦有了”類”Javascript就是一種完整的面向對象編程語言了
這好像有點太正式了與設計初衷也遠了同時增加了初學者的入門難度
參照到C++和Java語言都使用new命令來生成實例
C++這樣寫
ClassName *object = new ClassName(param);
Java這樣寫
Foo foo = new Foo();
那麼也可以把new命令引入了Javascript用來從原型對象生成一個實例對象但是Javascript中沒有”類”的話怎樣表示原型對象呢?
依然是參照C++和Java使用new命令時都會調用”類”的構造函數(constructor)Brendan Eich簡化了設計在Javascript語言中new命令後面跟的是構造函數不再是類
我們舉個例子來說現在有一個叫做WD構造函數表示前端開發(webdevelopper)對象的原型
function WD(skill){
thisskill = skill;
}
對這個構造函數使用new關鍵字就會生成一個前端開發對象的實例
var WD = new WD(’html’);
consolelog(WDskill); // html
在構造函數中的this關鍵字它其實代表的是新創建的實例對象
new 出來對象的缺陷
采用new關鍵字用構造函數生成實例對象無法共享屬性和方法
比如在WD對象的構造函數中設置一個實例對象的共有屬性skill
function WD(skill){
thisskill = skill;
thissex = ’男’;
}
然後生成兩個實例對象
var WD = new WD(’html’);
var WD = new WD(’css’);
這兩個對象的skill屬性是獨立的修改其中一個不會影響到另一個
WDskill= ’Javascript’;
consolelog(WDskill);//“css”不受WD的影響
每一個實例對象都有自己的屬性和方法的副本這不僅無法做到數據共享也是極大的資源浪費
引入prototype屬性
為了實現屬性和方法的共享Brendan Eich決定為構造函數設置一個prototype屬性
這個屬性包含一個對象(以下簡稱”prototype對象”)所有實例對象需要共享的屬性和方法都放在這個對象裡面;那些不需要共享的屬性和方法就放在構造函數裡面
實例對象一旦創建將自動引用prototype對象的屬性和方法也就是說實例對象的屬性和方法分成兩種一種是本地的另一種是引用的
還是以WD構造函數為例現在用prototype屬性進行改寫
function WD(skill){
thisskill = skill;
}
WDprototype = { sex : ’男’ };
var WD = new WD(’html’);
var WD = new WD(’css’);
consolelog(WDsex); // 男
consolelog(WDsex); // 男
現在sex屬性放在prototype對象裡是兩個實例對象共享的只要修改了prototype對象就會同時影響到兩個實例對象
WDprototypesex = ’女’;
consolelog(WDsex); //女
consolelog(WDsex); // 女
由於所有的實例對象共享同一個prototype對象那麼從外界看起來prototype對象就好像是實例對象的原型而實例對象則好像”繼承”了prototype對象一樣這就是Javascript繼承機制的設計思想
三構造函數如何實現繼承
現在有一個”MED”對象的構造函數(MED:Marketing Experience Design營銷體驗設計)
function MED(){
thisaim = "營銷體驗設計";
}
依然是”WD”對象的構造函數
function WD(skillsex){
thisskill = skill;
thissex = sex;
}
怎樣才能使”WD”繼承”MED”呢?
apply綁定構造函數實現
最簡單的方法大概就是使用call或apply方法將父對象的構造函數綁定在子對象上也就是在子對象構造函數中加一行
function WD(skillsex){
MEDapply(this arguments);
thisskill = skill;
thissex = sex;
}
var WD = new WD("Html""男");
consolelog(WDaim); // "營銷體驗設計"
prototype模式實現
我們通常的做法是使用prototype屬性如果”WD”的prototype對象指向一個MED的實例那麼所有”WD”的實例就能繼承MED了
WDprototype = new MED();//我們將WD的prototype對象指向一個MED的實例
WDprototypeconstructor = WD;
var WD = new WD("Html""男");
consolelog(WDaim); // 營銷體驗設計
這句
WDprototype = new MED();
相當於完全刪除了prototype 對象原先的值然後賦予一個新值那麼第二行又是什麼意思呢?
WDprototypeconstructor = WD;
原來任何一個prototype對象都有一個constructor屬性指向它的構造函數也就是說WDprototype 這個對象的constructor屬性是指向WD的
我們在前一步已經刪除了這個prototype對象原來的值所以新的prototype對象沒有constructor屬性需要我們手動加上去否則後面的”繼承鏈”會出問題這就是第二行的意思
注意這是很重要的一點編程時務必要遵守下文都遵循這一點即如果替換了prototype對象
oprototype = {};
那麼下一步必然是為新的prototype對象加上constructor屬性並將這個屬性指回原來的構造函數
oprototypeconstructor = o;
從prototype直接繼承實現
由於MED對象中不變的屬性都可以直接寫入MEDprototype所以我們也可以讓WD()跳過 MED()直接繼承MEDprototype
現在我們先將MED對象改寫
function MED(){ }
MEDprototypeskill = "MED";
然後將WD的prototype對象指向MED的prototype對象這樣就完成了繼承
WDprototype = MEDprototype;
WDprototypeconstructor = WD;
var WD = new WD("Html""男");
consolelog(WDskill); // MED
與前一種方法相比這樣做的優點是效率比較高(不用執行和建立MED的實例了)比較省內存缺點是 WDprototype和MEDprototype現在指向了同一個對象那麼任何對WDprototype的修改都會反映到MEDprototype
所以上面這一段代碼其實是有問題的請看第二行
WDprototypeconstructor = WD;
這一句實際上把MEDprototype對象的constructor屬性也改掉了!
consolelog(MEDprototypeconstructor); // WD
利用一個空對象作為中介來實現
由於”直接繼承prototype”存在上述的缺點所以可以利用一個空對象作為中介
var F = function(){};
Fprototype = MEDprototype;
WDprototype = new F();
WDprototypeconstructor = WD;
F是空對象所以幾乎不占內存這時修改WD的prototype對象就不會影響到MED的prototype對象
consolelog(MEDprototypeconstructor); // MED
利用 prototype模式的封裝函數
我們將上面的方法封裝成一個函數便於使用
function extend(Child Parent) {
var F = function(){};
Fprototype = Parentprototype;
Childprototype = new F();
Childprototypeconstructor = Child;
}
使用的時候方法如下
extend(WDMED);
var WD = new WD("Html""男");
consolelog(WDaim); // 營銷體驗設計
這個extend函數就是YUI庫如何實現繼承的方法
拷貝繼承實現
上面是采用prototype方式來實現繼承其實既然子對象會擁有父對象的屬性和方法我們直接采用”拷貝”方法也可以達到效果簡單說如果把父對象的所有屬性和方法拷貝進子對象不也能夠實現繼承嗎?
首先還是把MED的所有不變屬性都放到它的prototype對象上
function MED(){}
MEDprototypeaim = "營銷體驗設計";
然後再寫一個函數實現屬性拷貝的目的
function extendCopy(Child Parent) {
var p = Parentprototype;
var c = Childprototype;
for (var i in p) {
c[i] = p[i];
}
}
這個函數的作用就是將父對象的prototype對象中的屬性一一拷貝給Child對象的prototype對象
使用的時候這樣寫
extendCopy(WD MED);
var WD = new WD("Html""男");
consolelog(WDaim); // 營銷體驗設計
四”非構造函數”的如何實現繼承
比如現在有一個對象叫做”MED”–營銷體驗設計
var MED = {
aim:’營銷體驗設計’
}
還有一個對象叫做”前端開發”
var WD ={
skill:’html’
}
請問怎樣才能讓”前端開發”去繼承”營銷體驗設計”就是說我怎樣才能生成一個”營銷體驗設計的前端開發”對象?
這裡要注意這兩個對象都是普通對象不是構造函數無法使用構造函數方法實現”繼承”
object()方法
Json格式的發明者Douglas Crockford提出了一個object()函數可以做到這一點
function object(o) {
function F() {}
Fprototype = o;
return new F();
}
這個object()函數其實只做一件事就是把子對象的prototype屬性指向父對象從而使得子對象與父對象連在一起
使用的時候第一步先在父對象的基礎上生成子對象
var WD = object(MED);
然後再加上子對象本身的屬性
WDskill = ’html’;
這時子對象已經繼承了父對象的屬性了
consolelog(WDaim); //營銷體驗設計
淺拷貝
除了使用”prototype鏈”以外還有另一種思路把父對象的屬性全部拷貝給子對象也能實現繼承
下面這個函數就是在做拷貝
function LightCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
//cuber = p;
return c;
}
使用的時候這樣寫
var WD = LightCopy(MED);
WDaim = ’前端開發’;
但是這樣的拷貝有一個問題那就是如果父對象的屬性等於數組或另一個對象那麼實際上子對象獲得的只是一個內存地址而不是真正拷貝因此存在父對象被篡改的可能
請看現在給MED添加一個”技能”屬性它的值是一個數組
MEDskills = [’‘html’’’css’’Javascript’];
通過LightCopy()函數WD繼承了MED
var WD = LightCopy(MED);
然後我們為WD的”技能”添加一個屬性
WDskillspush(’teamwork’);
發生了什麼事?MED的”技能”也被篡改了!
consolelog(WDskills); //‘html’’Javascript’’css’’teamwork’
consolelog(MEDskills); //‘html’’Javascript’’css’’teamwork’
所以LightCopy()只是拷貝基本類型的數據我們把這種拷貝叫做”淺拷貝”這是早期jQuery實現繼承的方式
深拷貝
所謂”深拷貝”就是能夠實現真正意義上的數組和對象的拷貝它的實現並不難只要遞歸調用”淺拷貝”就行了
function deepCopy(p c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === ’object’) {
c[i] = (p[i]constructor === Array) ? [] : {};
deepCopy(p[i] c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
使用的時候這樣寫
var WD = deepCopy(MED);
現在給父對象加一個屬性值為數組然後在子對象上修改這個屬性
MEDskills = [’‘html’’’css’’Javascript’];
WDskillspush(’teamwork’);
這時父對象就不會受到影響了
consolelog(WDskills); //‘html’’css’’Javascript’’teamwork’
consolelog(MEDskills); //‘html’’css’’Javascript’
目前jQuery庫使用的就是這種繼承方法
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/20268.html