JavaScript面向物件三大特性之一:繼承性
文章目錄
一.什麼是繼承性
通常來說繼承就類似於兒子繼承父親的財產,但這樣說明並不準確,因為這代表父親將財產給了兒子,父親自己就沒有了,但在JavaScript面向物件中,繼承更像師傅與徒弟的關係,師傅將一生所學教給了徒弟,徒弟學會了,但是師傅也沒有失去什麼,這就是JavaScript面向物件中的繼承性。
二.繼承方法
接下來看有幾種繼承方式,和它們的利弊:
1.原型鏈繼承
function GaoBW() {
this.fang = '大別也' ;
this.che = '五菱巨集光';
}
function Parent(n, m) {
this.name = n
this.age = m
}
Parent.prototype = new GaoBW()
var tc = new Parent('王鐵錘', 30)
console.log(tc.name, tc.age, tc.fang, tc.che);
console.log(tc.constructor) ;
原理:將Parent的原型物件指向GaoBW ,這樣Parent的例項化物件也能繼承GaoBW的屬性
缺點:這麼做,會存在繼承鏈紊亂,tc是通過建構函式Farent()創建出來的。但是繼承過以後,會發現,tc的建構函式,不在Farent,而是GaoBW
解決辦法:手動的糾正繼承鏈
Parent.prototype.constructor = Parent
最終得到的結果:
Parent的例項化物件繼承到了GaoBW的屬性,並且也證明了是Parent的例項化物件
2.使用prototype直接繼承
function GaoBW() {
}
GaoBW.prototype.fang = '大別也';
GaoBW.prototype.che = '五菱巨集光';
function Parent(n, m) {
this.name = n
this.age = m
}
Parent.prototype = GaoBW.prototype
Parent.prototype.constructor = Parent
var tc = new Parent('王鐵錘', 30)
var gao = new GaoBW()
Parent.prototype.fang = '貧民窟'
console.log(tc.name, tc.age, tc.fang, tc.che);
console.log(gao.fang);
console.log(tc.constructor);
</script>
</body>
效果:可以讓Parent()跳過 GaoBW(),直接繼承GaoBW.prototype
優點:效率比較高(不用執行和建立Person的例項了)
缺點:
1、依然會產型繼承鏈紊亂,需要修改繼承鏈
2、因為Paretn的原型物件指向了GaoBw的原型物件,所以Paretn修改原型物件的值,GaoBw原型物件的值也被修改了,(繼承者修改繼承的屬性時,被繼承者的屬性也會被篡改,非常不合理)
3.利用空物件作為中介
function GaoBW() {}
GaoBW.prototype.fang = '大別也';
GaoBW.prototype.che = '五菱巨集光';
// 建立一個空的建構函式 讓這個空的建構函式的原型物件接受GaoBW原型物件的屬性
// 然後在讓Parent的原型物件指向F的例項化物件,這樣Parent在改變繼承的內容時,GaoBW的屬性就不在收影響了
function F() {}
F.prototype = GaoBW.prototype
function Parent(n, m) {
this.name = n
this.age = m
}
Parent.prototype = new F()
Parent.prototype.constructor = Parent
var tc = new Parent('王鐵錘', 30)
var gao = new GaoBW()
tc.fang = '貧民窟'
console.log(tc.name, tc.age, tc.fang, tc.che);
console.log(gao.fang);
效果:建立一個空的建構函式 讓這個空的建構函式的原型物件接受GaoBW原型物件的屬性
然後在讓Parent的原型物件指向F的例項化物件,這樣Parent在改變繼承的內容時,GaoBW的屬性就不在收影響了
封裝方法
將利用空建構函式當中介的方法封裝方便使用
function GaoBW() {}
GaoBW.prototype.fang = '大別也';
GaoBW.prototype.che = '五菱巨集光';
function Parent(n, m) {
this.name = n
this.age = m
}
// 將建立空建構函式的方法封裝為一個函式,直接呼叫 使用
function fz(Gao, Par) {
function F() {}
F.prototype = Gao.prototype
Par.prototype = new F()
Par.prototype.constructor = Par
}
fz(GaoBW, Parent)
var tc = new Parent('王鐵錘', 30)
var gao = new GaoBW()
tc.fang = '貧民窟'
console.log(tc.name, tc.age, tc.fang, tc.che);
console.log(gao.fang);
三.建構函式繫結 call&&apply
1.call方法
語法: 被繼承者.call(this,如果屬性直接用有不需要傳值,這裡就只寫this就行,否則就寫需要傳的值)
function GaoBW() {
this.name = '高百萬';
this.age = 100;
}
// 不能繼承原型物件的屬性
GaoBW.prototype.chezi = '五菱巨集光'
function Son() {
this.fang = '租房';
this.che = '寶馬';
GaoBW.call(this)
}
var son1 = new Son()
console.log(son1.name, son1.age, son1.chezi);
缺點:call只能繼承被繼承者自身的屬性,不能繼承prototype的屬性
2.apply
apply與call的繼承方法和機制相同,不同點就在於apply在傳值時需要在寫一個[],在[]內傳值,而call直接用,隔開傳值
function GaoBW() {
this.name = '高百萬';
this.age = 100;
}
// 不能繼承原型物件的屬性
GaoBW.prototype.chezi = '五菱巨集光'
function Son() {
this.fang = '租房';
this.che = '寶馬';
GaoBW.apply(this)
}
var son1 = new Son()
console.log(son1.name, son1.age, son1.chezi);
3.組合繼承
1.組合繼承也叫偽經典繼承
2.將原型鏈繼承和建構函式繼承組合在一塊
3.原型鏈實現對原型屬性和方法的繼承
4.借用建構函式實現對例項屬性的繼承
解決了call和apply只能繼承建構函式本身的屬性的問題
function GaoBW() {
this.name = '高百萬';
this.age = 30;
}
GaoBW.prototype.value = 100
function Parent() {
this.name1 = '閻濤';
this.age1 = 19;
GaoBW.call(this)
}
Parent.prototype = new GaoBW()
Parent.prototype.constructor = Parent
var par = new Parent()
par.name = '高千萬'
var gao = new GaoBW()
console.log(gao.name);
console.log(par.name, par.age, par.value);
console.log(par.constructor);
四.原型鏈
1.當物件訪問屬性和方法的時候,會往自身查詢,如果沒有才會去原型中找。(一級一級傳遞 形成了原型鏈)
2.先從自身找,自身沒有像父元素找,父元素沒有像object找,object沒有報錯
3.原型鏈遵循就近原則,會先從離得近的找
function Person() {
}
Person.prototype.sayname = function() {
console.log('我是person原型物件的sayname')
}
Object.prototype.sayname = function() {
console.log('我是object原型物件的sayname')
}
var p1 = new Person();
p1.sayname = function() {
console.log('我是person例項物件物件的sayname')
}
p1.sayname();