本文介紹的是,對象之間的"繼承"的五種方法。
比如,現(xiàn)在有一個"動物"對象的構(gòu)造函數(shù):
function Animal() {
this.species = "動物";
}
還有一個"貓"對象的構(gòu)造函數(shù):
function Cat(name, color) {
this.name = name;
this.color = color;
}
怎樣才能使"貓"繼承"動物"呢剖膳?
一、 構(gòu)造函數(shù)綁定
第一種方法也是最簡單的方法岭辣,使用 call
或 apply
方法吱晒,將父對象的構(gòu)造函數(shù)綁定在子對象上,即在子對象構(gòu)造函數(shù)中加一行:
function Cat(name, color) {
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛", "黃色");
cat1.species; // 動物
二沦童、 prototype 模式
第二種方法更常見仑濒,使用 prototype
屬性叹话。
如果"貓"的 prototype
對象,指向一個 Animal
的實例墩瞳,那么所有"貓"的實例驼壶,就能繼承 Animal
了:
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛", "黃色");
cat1.species; // 動物
代碼的第一行,我們將 Cat
的 prototype
對象指向一個 Animal
的實例喉酌,相當(dāng)于完全刪除了 prototype
對象原先的值辅柴,然后賦予一個新值。
Cat.prototype = new Animal();
第二行又是什么意思呢瞭吃?
任何一個 prototype
對象都有一個 constructor
屬性,指向它的構(gòu)造函數(shù)涣旨。一個普通函數(shù)的構(gòu)造函數(shù)就是其自身歪架,因此 prototype.constructor
的值就是該函數(shù)本身;而一個用關(guān)鍵字 new
創(chuàng)建的新對象的 constructor
屬性會指向創(chuàng)建它的函數(shù)霹陡。
假設(shè)我們只使用第一行代碼:
Cat.prototype = new Animal();
這時候 Cat
的構(gòu)造函數(shù)變成了 Animal
Cat.prototype.constructor == Animal; // true
更重要的是和蚪,每一個實例也有一個 constructor
屬性,默認調(diào)用 prototype
對象的 constructor
屬性:
cat1.constructor == Cat.prototype.constructor; // true
可以看到 cat1 的構(gòu)造函數(shù)也變成了 Animal
烹棉,這顯然會導(dǎo)致繼承鏈的紊亂(cat1 明明是用構(gòu)造函數(shù) Cat
生成的)攒霹,因此我們必須手動糾正,將 Cat.prototype
對象的 constructor
值改為 Cat
浆洗。這就是第二行的意思催束。
這是很重要的一點,編寫 javascript 時切記遵守伏社。即如果替換了 prototype
對象抠刺,那么,下一步必然是為新的 prototype
對象加上 constructor
屬性摘昌,并將這個屬性指回原來的構(gòu)造函數(shù):
foo.prototype = {};
foo.prototype.constructor = foo;
三速妖、 直接繼承 prototype
第三種方法是對第二種方法的改進。由于 Animal 對象中聪黎,不變的屬性都可以直接寫入 Animal.prototype
罕容。所以,我們也可以讓 Cat()
跳過 Animal()
稿饰,直接繼承 Animal.prototype
锦秒。
現(xiàn)在,我們先將 Animal
對象改寫:
function Animal(){}
Animal.prototype.species = "動物";
然后湘纵,將 Cat
的 prototype
對象指向 Animal
的 prototype
對象脂崔,這樣就完成了繼承:
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛", "黃色");
cat1.species; // 動物
與前一種方法相比,這樣做的優(yōu)點是效率比較高(不用執(zhí)行和建立 Animal
的實例了)梧喷,比較省內(nèi)存砌左。
缺點是 Cat.prototype
和 Animal.prototype
現(xiàn)在指向了同一個對象脖咐,那么任何對 Cat.prototype
的修改,都會反映到 Animal.prototype
汇歹。所以屁擅,上面這一段代碼其實是 有問題的。
因為當(dāng)執(zhí)行了:
Cat.prototype = Animal.prototype;
之后产弹,Animal
和 Cat
共享了同一個 prototype
對象派歌,對 Cat.prototype.constructor
的改動也會改變 Animal.prototype.constructor
。
所以在執(zhí)行:
Cat.prototype.constructor = Cat;
之后痰哨,Animal.prototype
對象的 constructor
屬性也就變成了 Cat
胶果。
Animal.prototype.constructor == Cat; // true
四、 利用空對象作為中介
由于直接繼承 prototype
存在上述的缺點斤斧,所以就有第四種方法早抠,利用一個空對象作為中介:
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
F
是空對象,所以幾乎不占內(nèi)存撬讽。這時蕊连,修改 Cat
的 prototype
對象,就不會影響到 Animal
的 prototype
對象:
Animal.prototype.constructor == Animal; // true
我們將上面的方法游昼,封裝成一個函數(shù)甘苍,便于使用:
function Animal(){ }
Animal.prototype.species = "動物";
function Cat(name,color) {
this.name = name;
this.color = color;
}
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
使用的時候,方法如下:
extend(Cat,Animal);
var cat1 = new Cat("大毛", "黃色");
cat1.species; // 動物
另外烘豌,說明一點载庭,函數(shù)體最后一行,意思是為子對象設(shè)一個 uber
屬性廊佩,這個屬性直接指向父對象的 prototype
屬性昧捷。(uber 是一個德語詞,意思是"向上"罐寨、"上一層"靡挥。)這等于在子對象上打開一條通道,可以直接調(diào)用父對象的方法鸯绿。這一行放在這里跋破,只是為了實現(xiàn)繼承的完備性,純屬備用性質(zhì)瓶蝴。
五毒返、 拷貝繼承
上面是采用 prototype
對象,實現(xiàn)繼承舷手。我們也可以換一種思路拧簸,純粹采用"拷貝"方法實現(xiàn)繼承。簡單說男窟,如果把父對象的所有屬性和方法盆赤,拷貝進子對象贾富,不也能夠?qū)崿F(xiàn)繼承嗎?
首先牺六,還是把 Animal
的所有不變屬性颤枪,都放到它的 prototype
對象上:
function Animal(){}
Animal.prototype.species = "動物";
然后,再寫一個函數(shù)淑际,實現(xiàn)屬性拷貝的目的:
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
這個函數(shù)的作用畏纲,就是將 Parent
對象的 prototype
對象中的屬性,一一拷貝給 Child
對象的 prototype
對象春缕。
使用的時候盗胀,這樣寫:
extend2(Cat, Animal);
var cat1 = new Cat("大毛", "黃色");
cat1.species; // 動物