JS中的繼承
許多OO語言都支持兩種繼承方式:接口繼承和實現(xiàn)繼承;
因為JS中沒有類和接口的概念 , 所以JS不支持接口繼承 , 只支持實現(xiàn)繼承;
實現(xiàn)繼承主要是依靠原型鏈來實現(xiàn)的;
1.原型鏈
基本思想:
利用原型讓一個對象繼承另一個對象的屬性和方法 ;
回顧構(gòu)造函數(shù),原型和實例的關(guān)系 :
每個構(gòu)造函數(shù)都有一個原型對象 ; 原型對象都包含一個指向構(gòu)造函數(shù)的指針 , 而實例包含一個指向原型 ;
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.speakName = function () {
console.log(this.name)
}
var p1 = new Person("老王",25);
var p2 = new Person("小王",15);
那假如我們把一個實例對象,作為另外一個實例的原型對象會發(fā)生什么呢?
//定義一個構(gòu)造函數(shù)翘盖。
function Father () {
// 添加name屬性. 默認直接賦值了谊囚。當(dāng)然也可以通過構(gòu)造函數(shù)傳遞過來
this.name = "馬云";
}
//給Father的原型添加giveMoney方法
Father.prototype.giveMoney = function () {
alert("我是Father原型中定義的方法");
}
//再定義一個構(gòu)造函數(shù)涎永。
function Son () {
//添加age屬性
this.age = 18;
}
//關(guān)鍵地方:把Son構(gòu)造方法的原型替換成Father的對象迷殿。 因為原型是對象,任何對象都可以作為原型
Son.prototype = new Father();
//給Son的原型添加getMoney方法
Son.prototype.getMoney = function () {
alert("我是Son的原型中定義的方法");
}
//創(chuàng)建Son類型的對象
var son1 = new Son();
//發(fā)現(xiàn)不僅可以訪問Son中定義屬性和Son原型中定義的方法,也可以訪問Father中定義的屬性和Father原型中的方法。
//這樣就通過原型完成了類型之間的繼承。
// Son繼承了Father中的屬性和方法励七,當(dāng)然還有Father原型中的屬性和方法智袭。
son1.giveMoney();
son1.getMoney();
alert("Father定義的屬性:" + son1.name);
alert("Son中定義的屬性:" + son1.age);
- 定義Son構(gòu)造函數(shù)后,我們沒有再使用Son的默認原型掠抬,而是把他的默認原型更換成了Father類型對象吼野。
- 這時,如果這樣訪問 son1.name, 則先在son1中查找name屬性两波,沒有然后去他的原型( Father對象)中找到了瞳步,所以是"馬云"闷哆。
- 如果這樣訪問 son1.giveMoney(), 先在son1中找這個方法,找不到去他的原型中找单起,仍然找不到抱怔,則再去這個原型的原型中去找,然后在 Father的原型對象中 找到了嘀倒。
- 從圖中可以看出來屈留,在訪問屬性和方法的時候,查找的順序是這樣的:對象->原型->原型的原型->...->原型鏈的頂端测蘑。 就像一個鏈條一樣灌危,這樣 由原型連成的"鏈條",就是我們經(jīng)常所說的原型鏈碳胳。
- 從上面的分析可以看出勇蝙,通過原型鏈的形式就完成了JavaScript的繼承。
2.默認原型鏈
原型鏈的頂端一定是Object這個構(gòu)造函數(shù)的原型對象挨约。
3. 測試數(shù)據(jù)類型
isPrototypeOf( 對象 ) : 這是個 原型對象 的方法味混,參數(shù)傳入一個對象,判斷參數(shù)對象是不是由這個原型派生出來的烫罩。 也就是判斷這個原型是不是參數(shù)對象原型鏈中的一環(huán)惜傲。
4.原型鏈的缺陷
父類型的構(gòu)造函數(shù)創(chuàng)建的對象,會成為子類型的原型
缺陷:
- 父類型中定義的實例屬性 , 會成為子類型的原型屬性 ; 子類型原型中的屬性被所有的子類型的實例所共用;(屬性共用)
- 原型鏈繼承中,只有一個地方用到了父類型的構(gòu)造函數(shù) , 也就意味著只有一次傳遞參數(shù)的機會 , 并且參數(shù)的值在所有的子類實例里都是一樣的;(參數(shù)傳遞困難)
5.借用構(gòu)造函數(shù)彌補缺陷
如何借用?
? 使用call或apply完成==構(gòu)造函數(shù)的借調(diào)== ,目的在于借用父類型(或其他類型)的構(gòu)造函數(shù) , 完成子類型的屬性添加 ;
原理
? call和apply 可以更改 構(gòu)造方法內(nèi)部的 this 指向指定的對象上;
function Father (name,age) {
this.name = name;
this.age = age;
}
//如果這樣直接調(diào)用,那么father中的this只的是 window贝攒。 因為其實這樣調(diào)用的: window.father("李四", 20)
// name 和age 屬性就添加到了window屬性上
Father("李四", 20);
alert("name:" + window.name + "\nage:" + window.age); //可以正確的輸出
//使用call方法調(diào)用盗誊,則可以改變this的指向
function Son (name, age, sex) {
this.sex = sex;
//調(diào)用Father方法(看成普通方法),第一個參數(shù)傳入一個對象this隘弊,則this(Son類型的對象)就成為了Father中的this
Father.call(this, name, age);
}
var son = new Son("張三", 30, "男");
alert("name:" + son.name + "\nage:" + son.age + "\nsex:" + son.sex);
alert(son instanceof Father); //false
注意
? 在這里并不是真正的繼承 , 借用的構(gòu)造函數(shù)在這里也只是作為一個普通的方法;
缺陷
? 單獨使用借用時 ; 父類型原型對象中的共享屬性和方法 ; 通過借用無法獲取 ;
6. 組合繼承
組合函數(shù)利用了原型繼承和構(gòu)造函數(shù)借調(diào)繼承的優(yōu)點哈踱,組合在一起。成為了使用最廣泛的一種繼承方式梨熙。
//定義父類型的構(gòu)造函數(shù)
function Father (name,age) {
// 屬性放在構(gòu)造函數(shù)內(nèi)部
this.name = name;
this.age = age;
// 方法定義在原型中
if((typeof Father.prototype.eat) != "function"){
Father.prototype.eat = function () {
alert(this.name + " 在吃東西");
}
}
}
// 定義子類類型的構(gòu)造函數(shù)
function Son(name, age, sex){
//借調(diào)父類型的構(gòu)造函數(shù)开镣,相當(dāng)于把父類型中的屬性添加到了未來的子類型的對象中
Father.call(this, name, age);
this.sex = sex;
}
//修改子類型的原型為父類型的對象。這樣就可以繼承父類型中的方法了咽扇。
Son.prototype = new Father( );
var son1 = new Son("志玲", 30, "女");
alert(son1.name);
alert(son1.sex);
alert(son1.age);
son1.eat();
總結(jié)
共同的方法和屬性通過原型鏈繼承 ;
需要傳入?yún)?shù)的屬性(子類型實例中的不相同屬性)通過 call或apply的方法 借調(diào)一個 構(gòu)造函數(shù)來完成屬性的添加 (子類屬性中的同名屬性會覆蓋掉原型中的屬性);