繼承
構造函數,原型和實例的關係
- 實例是通過構造函數創建的。實例一創造出來就具有constructor屬性(指向構造函數)和proto屬性(指向原型對象),
- 構造函數中有一個prototype屬性,這個屬性是一個指針,指向它的原型對象。
- 原型對象內部也有一個指針(constructor屬性)指向構造函數:Person.prototype.constructor = Person;
- 什麼是原型?每一個JavaScript對象(null除外)在創建的時候就會與之關聯另一個對象,這個對象就是我們所說的原型,每一個對象都會從原型”繼承”屬性。
function Person( ){
}
var person = new Person();
console.log(person._proto_ === Person.prototype); //true
console.log(Person === Person.prototype.constructor); //true
console.log(Object.getPrototypeOf(person) === Person.prototype) //true
- Object.prototype的原型是什麼?null,所以查到Object.prototype就可以停止查找了
原型鏈繼承
- 原型鏈:從一個實例對象往上找,找構造這個實例的相關聯的對象,然後這個關聯的對象再往上找,它又有創造它的上一級的原型對象,以此類推,最後找到Object.prototype原型對象終止。Object.prototype是原型鏈的頂端。
- 原型鏈的工作原理:通過原型鏈的方式,找到原型對象,原型對象上的方法是被不同的實例所共有的。
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
//繼承 Father
Son.prototype = new Father();//Son.prototype被重寫,導致Son.prototype.constructor也一同被重寫
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
alert(instance.getFatherValue());//true
由上例可得一條原型鏈:
instance
-->Son.prototype
--> Father.prototype
–> Object.prototype
**注:**所有函數的默認原型都是Object的實例,因此默認原型都會包含式內部指針,指向 Object.prototype
,因此自定義類型都會繼承一些類似toString的默認方法。
- 確定原型實例關係的兩種方法:
// instanceof
alert(instance instanceof Object);//true
alert(instance instanceof Father);//true
alert(instance instanceof Son);//true
// isPrototypeOf
alert(Object.prototype.isPrototypeOf(instance));//true
alert(Father.prototype.isPrototypeOf(instance));//true
alert(Son.prototype.isPrototypeOf(instance));//true
原型鏈繼承需要注意的點
- 繼承時,給原型添加方法或者重寫原型某個方法時應當把語句放在替換原型的語句之後
function SuperType () {this.property = true;}
SuperType.prototype.getSuper = function(){return this.property;}
function SubType () {this.subProperty = false;}
// 繼承
SubType.prototype = new SuperType();
// 給原型添加新方法
subType.prototype.getSub = function(){return this.subProperty;}
// 覆蓋重寫原型的某個方法
subType.prototype.getSuper = function(){return false;}
- 通過
SubType
的實例調用getSuper
時,方法是被重寫那個,但是如果通過SuperType
調用就是原來未重寫的方法。 - 做上述修改或者重寫的方法不能通過對象字面量的方法
subType.prototype = {getSuper:function(){...}.....}
來創建,因爲這樣會重寫原型鏈 - 原型鏈是有缺陷的,當原型鏈中包含有引用類型的原型時,該引用類型會被所以實例共享,而且創建子類也無法傳遞參數。因此使用一些手段來輔助解決原型鏈中上述兩個問題。
借用構造函數
基本思想:即在子類型構造函數的內部調用超類型構造函數,即用.call()和.apply()將父類構造函數引入子類函數
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){
Father.call(this);//繼承了Father,且向父類型傳遞參數
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" 可見引用類型值是獨立的
特點:
- 只繼承了父類構造函數的屬性,不能繼承父類原型的屬性。
- 可以繼承多個構造函數屬性(call)。
- 子實例可以向父實例傳參。
- 無法實現構造函數的複用,每個新實例都有父類構造函數的副本,十分臃腫。
組合繼承
基本思路: 使用原型鏈實現對原型屬性和方法的繼承,通過借用構造函數來實現對實例屬性的繼承
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);//繼承實例屬性,第一次調用Father()
this.age = age;
}
Son.prototype = new Father();//繼承父類原型上的方法,第二次調用Father()
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5
var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10
特點:
- 實現了複用和傳參兩個問題
- 每個新實例引入的構造函數屬性是私有的
- 調用了兩次父類構造函數(耗內存)
其餘繼承方法放在下一節,見js之各種繼承(二)