原型鏈的缺陷
1.引用類型的值在原型鏈傳遞中存在的問題
js中有簡單數(shù)據(jù)類型和引用數(shù)據(jù)類型(復雜數(shù)據(jù)類型),引用類型的值有一個特點:在賦值的時候猴伶,傳遞給變量的其實是它在內(nèi)存當中的地址。被賦值完的變量相當于一個指針膛壹。這樣一來就有大問題了拣帽,上代碼:
function Father() {
this.name = 'father';
this.hobby = ['sport', 'read'];
}
function Son() {
}
Son.prototype = new Father();
let son1 = new Son();
let son2 = new Son();
console.log(son1.name);
console.log(son2.name);
console.log(son1.hobby);
console.log(son2.hobby);
打印如下:接著我們修改一下son1的hobby和name屬性看看:
son1.name = 'son';
son1.hobby.push('play games');
console.log(son1);
console.log(son2);
console.log(son2.name);
console.log(son1.hobby);
console.log(son2.hobby);
打印結(jié)果如下:
(1).son1和son2有自己的hobby屬性嗎跷车?
沒有棘利!只是Son的原型對象上有hobby屬性,因為我修改了指向朽缴,指向了Father的實例赡译,son1和son2其實是通過
__proto__
屬性訪問Son的原型對象上的hobby屬性,從而訪問和操作數(shù)組的不铆。(2).son1和son2有自己的name屬性嗎蝌焚?
開始都沒有,但是后來我們手動給son1添加了name屬性誓斥,相當于son1就有了只洒,但是son2依然沒有name屬性
所以在原型鏈中如果
Father
含有引用類型的值,那么子類型的實例共享這個引用類型的值劳坑,也就是上面的hobby數(shù)組毕谴,這就是原型鏈的第一個缺陷。
2.第二個缺陷是:在創(chuàng)建子類型的實例時距芬,是無法向父類型的構(gòu)造函數(shù)中傳遞參數(shù)的涝开,在上面的例子中,如果Father
的name
屬性是要傳遞數(shù)的框仔,而不是寫死的舀武,那么我們在實例化son1和son2的時候根本沒辦法傳參。
借用構(gòu)造函數(shù)繼承
為了解決引用類型值帶來的問題离斩,我們采用借用構(gòu)造函數(shù)繼承的方式银舱,它的核心思路是:在子類型的構(gòu)造函數(shù)中調(diào)用父類型的構(gòu)造函數(shù),這里需要借助call()
或者apply()
方法跛梗,具體代碼如下:
function Father() {
this.name = 'father';
this.hobby = ['sport', 'read'];
}
function Son() {
Father.call(this);
}
let son1 = new Son();
let son2 = new Son();
console.log(son1.name);
console.log(son2.name);
console.log(son1.hobby);
console.log(son2.hobby);
son1.name = 'son';
son1.hobby.push('play games');
console.log('------------------');
console.log(son1);
console.log(son2);
console.log(son2.name);
console.log(son1.hobby);
console.log(son2.hobby);
打印結(jié)果:
call()
方法,修改父構(gòu)造函數(shù)的指向核偿,來實現(xiàn)在子構(gòu)造函數(shù)中借用父構(gòu)造函數(shù)诚欠,完成繼承,這樣一來son1和son2都分別擁有自己的name
和hobby
屬性,也就是打印結(jié)果的6,7行轰绵,son1和son2是完全獨立的家乘。這樣就解決了原型鏈繼承的第一個缺陷。我們再稍微修改一下Father構(gòu)造函數(shù):
function Father(name) {
this.name = name;
this.hobby = ['sport', 'read'];
}
function Son(name) {
Father.call(this, name);
}
let son1 = new Son('Jack');
let son2 = new Son('Bob');
console.log(son1.name);
console.log(son2.name);
console.log(son1.hobby);
console.log(son2.hobby);
打印結(jié)果如下:
sayF
方法并繼承业崖,那么只能在Father的構(gòu)造函數(shù)里面寫,Father.prototype
上的方法蓄愁,沒辦法通過這種方式繼承双炕,也就是說,如果把所有的屬性和方法都在構(gòu)造函數(shù)中定義的話撮抓,就不能對函數(shù)方法進行復用妇斤。 雖然我們依然可以通過實例對象來調(diào)用這個方法,但是卻是獨立存在于實例當中的丹拯,不屬于共享的方法站超。(你可以在Father構(gòu)造函數(shù)中添加一個方法,然后打印實例對象乖酬,可以看到實例對象中包含了這個方法死相,而這個方法不是存在于原型鏈上的)
組合繼承
我們了解了原型鏈繼承和借用構(gòu)造函數(shù)繼承后,我們可以發(fā)現(xiàn)這兩種繼承是互補的一個關(guān)系咬像。
- 原型鏈繼承可以把方法定義在原型上算撮,從而復用方法。
- 借用構(gòu)造函數(shù)繼承可以解決引用類型值的繼承問題和傳遞參數(shù)的問題县昂。
因此肮柜,我們可以結(jié)合這兩種方法,就誕生了組合繼承倒彰,具體代碼實現(xiàn)如下:
function Father(name) {
this.name = name;
this.hobby = ['sport', 'read'];
}
Father.prototype.sayF = function () {
console.log('father');
}
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = new Father();
Son.prototype.constructor = Son;
Son.prototype.sayS = function () {
console.log('son');
}
let son1 = new Son('Jack', 20);
let son2 = new Son('Bob', 24);
console.log(son1);
console.log(son2);
son1.sayF();
son2.sayS();
打印結(jié)果如下:
sayF()
方法审洞,在Son原型對象上定義了sayS()
方法,增加了Son.prototype = new Father();
原型鏈狸驳。最終實現(xiàn)了效果是预明,son1和son2都有各自的屬性缩赛,同時方法還綁定在兩個原型對象上耙箍,就達到了我們的目的:屬性獨立,方法復用
原型鏈負責原型對象上的方法酥馍,call借用構(gòu)造函數(shù)負責讓子類型擁有各自的屬性辩昆,組合繼承是js中最常用的繼承方式。