繼承
繼承是 OOP 語言中的一個最為人津津樂道的概念。許多OOP 語言都支持兩種繼承方式:接口繼承和實現(xiàn)繼承。接口繼承只繼承方法簽名,而實現(xiàn)繼承則繼承實際的方法。如前所述,由于函數(shù)沒有簽名,在ECMAScript 中無法實現(xiàn)接口繼承。ECMAScript 只支持實現(xiàn)繼承,而且其實現(xiàn)繼承主要是依靠原型鏈來實現(xiàn)的。
原型鏈
ECMAScript 中描述了原型鏈的概念,并將原型鏈作為實現(xiàn)繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
構(gòu)造函數(shù)、原型和實例的關(guān)系:每個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,而實例都包含一個指向原型對象的內(nèi)部指針。那么,假如我們讓原型對象等于另一個類型的實例,結(jié)果會怎么樣呢?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應(yīng)地,另一個原型中也包含著一個指向另一個構(gòu)造函數(shù)的指針。假如另一個原型又是另一個類型的實例,那么上述關(guān)系依然成立,如此層層遞進,就構(gòu)成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。

說明
- 定義Son構(gòu)造函數(shù)后,我們沒有再使用Son的默認原型,而是把他的默認原型更換成了Father類型對象。
- 這時,如果這樣訪問 son1.name, 則先在son1中查找name屬性,沒有然后去他的原型( Father對象)中找到了,所以是"馬云"。
- 如果這樣訪問 son1.giveMoney(), 先在son1中找這個方法,找不到去他的原型中找,仍然找不到,則再去這個原型的原型中去找,然后在 Father的原型對象中 找到了。
- 從圖中可以看出來,在訪問屬性和方法的時候,查找的順序是這樣的:對象->原型->原型的原型->...->原型鏈的頂端。 就像一個鏈條一樣,這樣 由原型連成的"鏈條",就是我們經(jīng)常所說的原型鏈。
- 從上面的分析可以看出,通過原型鏈的形式就完成了JavaScript的繼承
默認頂端原型
在 JavaScript 中所有的類型如果沒有指明繼承某個類型,則默認是繼承的 Object 類型。這種 默認繼承也是通過原型鏈的方式完成的。
下面的圖就是一個完整的原型鏈:

說明:
- 原型鏈的頂端一定是Object這個構(gòu)造函數(shù)的原型對象。這也是為什么我們隨意創(chuàng)建一個對象,就有很多方法可以調(diào)用,其實這些方法都是來自O(shè)bject的原型對象。
- 通過對象訪問屬性、方法的時候,一定是會通過原型鏈來查找的,直到原型鏈的頂端。
- 一旦有了繼承,就會出現(xiàn)多態(tài)的情況。假設(shè)需要一個Father類型的數(shù)據(jù),那么你給一個Father對象,或Son對象都是沒有任何問題的。而在實際執(zhí)行的過程中,一個方法的具體執(zhí)行結(jié)果,就看在原型鏈中的查找過程了。給一個實際的Father對象則從Fahter的原型鏈中查找,給一個實際的Son則從Son的原型鏈中查找。
- 因為繼承的存在,Son的對象,也可以看出Father類型的對象和Object類型的對象。 子類型對象可以看出一個特殊的父類型對象。
幾種測試數(shù)據(jù)的類型
1.typeof:一般用來測試簡單數(shù)據(jù)類型和函數(shù)的類型。
2.instanceof: 用來測試一個對象是不是屬于某個類型。結(jié)果為boolean值。
3.isPrototypeOf( 對象 ) : 這是個 原型對象 的方法,參數(shù)傳入一個對象,判斷參數(shù)對象是不是由這個原型派生出來的。 也就是判斷這個原型是不是參數(shù)對象原型鏈中的一環(huán)。
4.函數(shù)借調(diào) Object.prototype.toString.call()
<script>
function Father () {
}
function Son () {
}
Son.prototype = new Father();
var son = new Son();
console.log(son instanceof Son); // true
// Son通過原型繼承了Father
console.log(son instanceof Father); // true
//Father又默認繼承了Objcet
console.log(son instanceof Object); // true
console.log(Son.prototype.isPrototypeOf(son))//true
console.log(typeof new Son())//object
console.log(Object.prototype.toString.call(/a/gi))//借調(diào)可以檢測出它的類型 RegExp
console.log(Object.prototype.toString.call([]))//返回Array
console.log(Object.prototype.toString.call(function () {
}))//返回function
var msg=Object.prototype.toString.call([])
console.log(/[ ](\w+)]/gi.exec(msg))//用正則表達式輸出類型
</script>
借用構(gòu)造函數(shù)調(diào)用"繼承"
借用構(gòu)造函數(shù)調(diào)用 繼承,又叫偽裝調(diào)用繼承或冒充調(diào)用繼承。雖然有了繼承兩個字,但是這種方法從本質(zhì)上并沒實現(xiàn)繼承,只是完成了構(gòu)造方法的調(diào)用而已。
使用 call 或 apply 這兩個方法完成函數(shù)借調(diào)。這兩個方法的功能是一樣的,只有少許的區(qū)別(暫且不管)。功能都是更改一個構(gòu)造方法內(nèi)部的 this 指向到指定的對象上。
<script>
function foo(name,age) {
this.name=name;
this.age=age;
}
var obj={};
//函數(shù)借調(diào) 借用call
foo.call(obj,"張三","20")//借調(diào)了foo的this,此處的this就指向obj,所以obj就多了
//foo的屬性
console.log(obj)
</script>
組合繼承
組合函數(shù)利用了原型繼承和構(gòu)造函數(shù)借調(diào)繼承的優(yōu)點,組合在一起。成為了使用最廣泛的一種繼承方式。
<script>
function Animal(name,color) {
this.name=name;
this.color=color;
}
Animal.prototype.eat=function () {
console.log("動物在吃")
}
function Dog(name,color,weight) {
Animal.call(this,name,color);//借調(diào)animal中的this屬性
//this是Animal的this 只是指向改變 指向了dog
this.weight=weight;
}
Dog.prototype=new Animal();
Dog.prototype.constructor=Dog;
var dog=new Dog("黑子","黑色",20)
console.log(dog)
dog.eat()
console.log(dog instanceof Animal)
</script>
說明
- 組合繼承是我們實際使用中最常用的一種繼承方式。
- 在子類型的構(gòu)造函數(shù)中借調(diào)了父類型的構(gòu)造函數(shù),也就是說,子類型的原型(也就是Father的對象)中有的屬性,都會被子類對象中的屬性給覆蓋掉。