繼承
原型鏈
講原型的時(shí)候提到過(guò)繼承,設(shè)計(jì)原型的初衷就是為了繼承,原型鏈?zhǔn)菍?shí)現(xiàn)繼承的主要方法监署。
那什么是原型鏈颤专,還記得之前提到過(guò)的作用域鏈嗎,它表示標(biāo)識(shí)符在環(huán)境中的查找順序钠乏,原型鏈與作用域鏈相似栖秕,它表示屬性或方法在實(shí)例和構(gòu)造函數(shù)間的追溯順序,看一個(gè)例子:
function Father() {
}
Father.prototype.familyName = "Zhao";
function Child() {
}
var father = new Father();
Child.prototype = father;
var me = new Child();
me.familyName;
// "Zhao"
當(dāng)我們?cè)?code>me實(shí)例上訪問(wèn)
familyName
屬性時(shí)缓熟,搜索過(guò)程會(huì)從原型鏈的末端開(kāi)始逐步向上累魔,即:me實(shí)例 → Child原型 → Father原型
默認(rèn)的原型
由于所有引用類型都繼承了Object
因此所有引用類型原型鏈的頂端都是Object.prototype
,因此所有自定義類型都會(huì)繼承toString()
valueOf()
等默認(rèn)方法够滑。
確定原型和實(shí)例的關(guān)系
- instanceof
用這個(gè)操作符測(cè)試實(shí)例和原型鏈上出現(xiàn)的構(gòu)造函數(shù)垦写,即返回true
- isPropertyOf
用這個(gè)方法測(cè)試原型鏈中出現(xiàn)過(guò)的原型,即返回true
謹(jǐn)慎地定義方法
在上面的例子中彰触,我們有一步替換原型對(duì)象的操作:
Child.prototype = father;
很多時(shí)候梯投,我們想要在子類型中添加一些超類型沒(méi)有的方法,應(yīng)該放在替換原型對(duì)象之后况毅。
不能使用對(duì)象字面量
通過(guò)原型鏈實(shí)現(xiàn)繼承時(shí)分蓖,不能使用對(duì)象字面量來(lái)重寫(xiě)原型,因此這樣做會(huì)切斷子類型與超類型之間的聯(lián)系尔许。
原型鏈的問(wèn)題
在上述例子中么鹤,Child
構(gòu)造函數(shù)的原型是Father
的實(shí)例father
,那么father
實(shí)例的屬性就變成Child
的原型屬性味廊,接下來(lái)在Child
的所有實(shí)例蒸甜,均會(huì)共享father
實(shí)例的屬性,我們修改一下上述例子:
function Father() {
this.hobbies = [];
}
function Child() {
}
Child.prototype = new Father();
var me = new Child();
var sister = new Child();
me.hobbies.push("dancing");
sister.hobbies;
// ["dancing"]
me
修改Child
原型上的hobbies
屬性會(huì)影響到sister
訪問(wèn)該屬性時(shí)獲得的值余佛,注意柠新,當(dāng)我們直接在實(shí)例對(duì)象上對(duì)某個(gè)屬性賦值時(shí),我們相當(dāng)于修改或添加實(shí)例中的某個(gè)屬性(同訪問(wèn)不一樣辉巡,不遵循原型鏈)恨憎,比如:
me.hobbies = [];
sister.hobbies;
//["dancing"]
上述例子中,直接在me
實(shí)例中添加屬性hobbies
郊楣,因此沒(méi)有影響sister
憔恳。
借用構(gòu)造函數(shù)
即在子類型的構(gòu)造函數(shù)調(diào)用超類型的構(gòu)造函數(shù),舉一個(gè)例子:
function Father(givenName) {
this.givenName = givenName;
}
function Child(givenName) {
Father.call(this, givenName)
}
var child = new Child("Xianshu");
child.givenName
// "Xianshu"
- 私有屬性
使用這種方式痢甘,可以使每個(gè)實(shí)例從超類型中繼承的屬性私有化喇嘱,一個(gè)實(shí)例更改屬性值不會(huì)影響到其他屬性,舉一個(gè)例子:
var child1 = new Child("Sue")
var child2 = new Child("Jane")
child1.givenName
// "Sue"
child2.givenName
// "Jane"
傳遞參數(shù)
通過(guò)這種方式塞栅,子類型可以在調(diào)用超類型構(gòu)造函數(shù)時(shí)向其傳參者铜。結(jié)構(gòu)構(gòu)造函數(shù)的問(wèn)題
首先沒(méi)辦法在實(shí)例間復(fù)用函數(shù)腔丧,比如:
function Father(givenName) {
this.givenName = givenName;
this.cook = function() {
return "delicious food";
}
}
function Child(givenName) {
Father.call(this, givenName)
}
var child1 = new Child("Sue")
var child2 = new Child("Jane")
child1.cook === child2.cook
// false
上述例子中,可以看出作烟,兩個(gè)實(shí)例的cook
方法引用的不是同一塊內(nèi)存地址愉粤,說(shuō)明cook
被實(shí)例化了多次,這顯然是冗余的拿撩。
其次衣厘,超類型的原型中定義的方法對(duì)于子類型來(lái)說(shuō)也是不可見(jiàn)的。
組合繼承
使用原型鏈來(lái)實(shí)現(xiàn)方法的繼承压恒,使用構(gòu)造函數(shù)實(shí)現(xiàn)屬性的繼承影暴。比如:
function Father(givenName) {
this.givenName = givenName;
}
Father.prototype.cook = function() {
return "delicious food";
}
function Child(givenName) {
Father.call(this, givenName)
}
Child.prototype = new Father();
Child.prototype.constructor = Child;
var child = new Child("Sue")
child.cook();
// "delicious food"
在創(chuàng)建Child
構(gòu)造函數(shù)時(shí),首先繼承超類型Father
中的屬性探赫,然后創(chuàng)建一個(gè)Father
實(shí)例型宙,將其賦值給Child
的原型,使其繼承Father
定義在原型中的方法伦吠,這里需要注意兩點(diǎn):
-
new Father()
中的givenName
屬性不會(huì)覆蓋child
中的givenName
屬性妆兑,這是由標(biāo)識(shí)符在實(shí)例及其原型鏈上的搜索順序決定的 - 需要修正
Child.prototype.constructor
屬性
原型式繼承
無(wú)需構(gòu)造函數(shù)的一種繼承方式,在一個(gè)對(duì)象的基礎(chǔ)上創(chuàng)建出一個(gè)新對(duì)象:
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
var person = { age : 18, hobbies: [] };
var anotherPerson = object(person);
anotherPerson.age;
// 18
上述例子中毛仪,person
不是構(gòu)造函數(shù)搁嗓,只是一個(gè)普通的對(duì)象,anotherPerson
繼承了它的屬性箱靴,需要注意的是腺逛,繼承到的屬性是在實(shí)例間共享的。
anotherPerson.hobbies.push("dancing");
anotherPerson.age = 19;
var anotherPerson2 = object(person);
anotherPerson2.age; // 18
anotherPerson2.hobbies; // ["dancing"]
寄生式繼承
寄生式繼承封裝了如下過(guò)程:
- 調(diào)用一個(gè)能夠返回新對(duì)象的函數(shù)
- 以某種方式來(lái)增強(qiáng)返回的對(duì)象
- 返回對(duì)象
function printBook(original) {
var book = object(original);
book.auther = "Sue";
return book;
}
var book = {
name: "travel notes",
type: "travel"
}
var travelBook = printBook(book);
travelBook.name
// "travel notes"
travelBook.auther
// "Sue"
上述例子中衡怀,travelBook
不繼承了book
中的屬性屉来,而且還具有自己的屬性。需要注意的是狈癞,使用寄生式繼承,在第二步中添加的屬性或方法不能在實(shí)例間復(fù)用茂契。
寄生組合式繼承
還記得我們?cè)诮M合繼承中提到的注意事項(xiàng)第一條嗎...為什么Child
原型中以及child
實(shí)例中都包含givenName
屬性蝶桶,這是因?yàn)槲覀冋{(diào)用了兩次Father
構(gòu)造函數(shù),第一次將屬性賦予child
實(shí)例掉冶,第二次是將Father
的實(shí)例賦值給Child
的原型真竖,雖然第二次調(diào)用添加的givenName
屬性并沒(méi)有影響到child.givenName
,但兩次調(diào)用畢竟是冗余的厌小,況且Child
只需要繼承Father
的原型恢共,而Father
的實(shí)例包含了額外的屬性。寄生組合方式就是為了解決上述的問(wèn)題:
function Father(givenName) {
this.givenName = givenName;
}
Father.prototype.cook = function() {
return "delicious food";
}
function Child(givenName) {
Father.call(this, givenName)
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
inheritPrototype(Child, Father);
var child = new Child("Sue")
undefined
child.givenName
// "Sue"
"givenName" in Child.prototype
// false
child.cook()
// "delicious food"
child instanceof Father
// true
Father.prototype.isPrototypeOf(child)
// true
Child
使用寄生組合的方式繼承了Father
璧亚,可以調(diào)用Father
原型中的方法讨韭,同時(shí),沒(méi)有留下Father
“私有”屬性的痕跡,instanceof()
與isPrototypeOf()
可以正常使用