一、繼承的概念
繼承是所有面向?qū)ο笳Z言中最重要的一個(gè)特征。而執(zhí)行環(huán)境也是JS最為重要的一個(gè)概念。執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù)的范圍,決定了它們各自的行為。
主要繼承方式:接口繼承和實(shí)現(xiàn)繼承
JS主要支持實(shí)現(xiàn)繼承,而且是在原型鏈的基礎(chǔ)上實(shí)現(xiàn)的,繼承是發(fā)生在對象與對象之間
二、原型鏈的概念
原型鏈作為實(shí)現(xiàn)繼承的主要方法,是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法
構(gòu)造函數(shù)、原型(對象)和構(gòu)造函數(shù)創(chuàng)造的對象之間的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)屬性 prototype 指向一個(gè)原型對象,每個(gè)原型對象也有一個(gè)屬性constructor指向函數(shù),通過new 構(gòu)造函數(shù)()創(chuàng)建出來的對象內(nèi)部有一個(gè)不可見的屬性[[proto]]指向構(gòu)造函數(shù)的原型。
1、更換構(gòu)造函數(shù)的原型
原型其實(shí)就是一個(gè)對象,只是默認(rèn)情況下原型對象是瀏覽器會(huì)自動(dòng)幫我們創(chuàng)建的,而且自動(dòng)讓構(gòu)造函數(shù)的prototype屬性指向這個(gè)自動(dòng)創(chuàng)建的原型對象。我們完全可以將原型對象更換成我們自定義類型的對象。
//定義一個(gè)構(gòu)造函數(shù)。
function Father () {
// 添加name屬性. 默認(rèn)直接賦值了。當(dāng)然也可以通過構(gòu)造函數(shù)傳遞過來
this.name = "馬云";
}
//給Father的原型添加giveMoney方法
Father.prototype.giveMoney = function () {
alert("我是Father原型中定義的方法");
}
//再定義一個(gè)構(gòu)造函數(shù)。
function Son () {
//添加age屬性
this.age = 18;
}
//關(guān)鍵地方:把Son構(gòu)造方法的原型替換成Father的對象。 因?yàn)樵褪菍ο?,任何對象都可以作為原? Son.prototype = new Father();
//給Son的原型添加getMoney方法
Son.prototype.getMoney = function () {
alert("我是Son的原型中定義的方法");
}
//創(chuàng)建Son類型的對象
var son1 = new Son();
以上代碼即實(shí)現(xiàn)了Son繼承了Father的過程
注意:繼承過程中的訪問屬性和方法的過程為:
對象本身->原型->原型的原型->...->原型鏈的頂端(Object原型對象)這也是為什么我們隨意創(chuàng)建一個(gè)對象,就有很多方法可以調(diào)用,其實(shí)這些方法都是來自O(shè)bject的原型對象(Object原型對象的原型有人說是null,也有人說是它本身)
2、測試數(shù)據(jù)類型的四種方法
2.1、typeof:一般用來測試簡單數(shù)據(jù)類型和函數(shù)的類型。如果用來測試對象,則會(huì)一直返回object,沒有太大意義。
2.2、instanceof:用來測試一個(gè)對象是不是屬于某個(gè)類型。結(jié)果為boolean值。
function Father () {
}
function Son () {
}
Son.prototype = new Father();
var son = new Son();
alert(son instanceof Son); // true
// Son通過原型繼承了Father
alert(son instanceof Father); // true
//Father又默認(rèn)繼承了Objcet
alert(son instanceof Object); // true
2.3、isPrototypeOf( 對象 ) : 這是個(gè) 原型對象 的方法,參數(shù)傳入一個(gè)對象,判斷參數(shù)對象是不是由這個(gè)原型派生出來的。 即判斷這個(gè)原型是不是參數(shù)對象原型鏈中的一環(huán)。
function Father () {
}
function Son () {
}
Son.prototype = new Father();
var son = new Son();
alert(Son.prototype.isPrototypeOf(son)); // true
alert(Father.prototype.isPrototypeOf(son)); // true
alert(Son.prototype.isPrototypeOf(Father)); //false
alert(Object.prototype.isPrototypeOf(Father)) //true
alert(Object.prototype.isPrototypeOf(son)); // true
//從參數(shù)對象原型開始
2.4、Object.prototype.toString.call(對象或基本類型)
function Foo(){
}
var arr = new Array("a");
var s = arr.toString();
console.log(s);
console.log(Object.prototype.toString.call(foo)) //測試數(shù)據(jù)類型
三、 原型鏈在繼承中的缺陷
1、在原型鏈中,父類型的構(gòu)造函數(shù)創(chuàng)建的對象,會(huì)成為子類型的原型。則父類型中定義的實(shí)例屬性,就會(huì)成為子類型的原型屬性。對子類型來說,這和我們以前說的在原型中定義方法,構(gòu)造函數(shù)中定義屬性是違背的。子類型原型(父類型對象)中的屬性被所有的子類型的實(shí)例所共有,如果有一個(gè)實(shí)例(即構(gòu)造函數(shù)創(chuàng)建的對象)去更改,則會(huì)很快反應(yīng)的其他的實(shí)例上。
function Father () {
this.girls = ["志玲", "鳳姐"];
}
function Son () {
}
// 子類的原型對象中就有一個(gè)屬性 girls ,是個(gè)數(shù)組
Son.prototype = new Father();
var son1 = new Son();
var son2 = new Son();
//給son1的girls屬性的數(shù)組添加一個(gè)元素
son1.girls.push("亦非");
//這時(shí),發(fā)現(xiàn)son2中的girls屬性的數(shù)組內(nèi)容也發(fā)生了改變
alert(son2.girls); // "志玲", "鳳姐", "亦非"
2、向父類型的構(gòu)造函數(shù)中傳遞參數(shù)問題
如以上,父對象的屬性和方法會(huì)被子對象(實(shí)例所共享)則父類型傳遞的參數(shù)也會(huì)被子對象(實(shí)例所共享):父類型傳遞的地方:Son.prototype = new Father();
四、借用構(gòu)造函數(shù)調(diào)用"繼承"(函數(shù)借調(diào))
為解決以上屬性共享問題:
借用構(gòu)造函數(shù)調(diào)用繼承,又叫偽裝調(diào)用繼承或冒充調(diào)用繼承。雖然有了繼承兩個(gè)字,但是這種方法從本質(zhì)上并沒實(shí)現(xiàn)繼承,只是完成了構(gòu)造方法的調(diào)用而已。
1、借調(diào)方法
主要有call和apply方法:功能都是更改一個(gè)構(gòu)造方法內(nèi)部的this指向到指定的對象上。(后面詳細(xì)有介紹)
function Father(x , y ){
this.x = x;
this.y = y;
}
function Son(x , y , z){
Father.apply(this , [x , y]);//將數(shù)組展開,但是call不會(huì)
this.z = z;
}
var s1 = new Son(1 , 2 , 3);
console.log(s1);//Son {x: 1, y: 2, z: 3}
call和apply應(yīng)用
var name = "a"
function foo(a, b){
console.log(a + " " + b);
this.name = "d"
}
var obj = {
name : "c"
}
foo.call(obj, 1, 3); //不影響后面內(nèi)容
console.log(obj.name); //"d"
//計(jì)算一個(gè)數(shù)組中的最大值和最小值
var arr = [30, 6, 9, 30, 50];
var max = Math.max.apply(Math, arr)//50
console.log(max);
console.log(Math.min.apply(Math, arr));//6
注意:函數(shù)借調(diào)其實(shí)并沒有真的繼承,僅僅是調(diào)用了Father構(gòu)造函數(shù)而已。也就是說,son對象和Father沒有任何的關(guān)系。