淺談JS的繼承
JS繼承
繼承是OO語言中最為人津津樂道的概念,許多OO語言都支持兩種方式的繼承:接口繼承;實現繼承。
接口繼承:只繼承方法簽名。
實現繼承:繼承實際的方法。
由於ES裏函數沒有簽名,所以在ES裏面無法實現接口繼承,ES只支持實現繼承。
——《js高程》
根據高程上面的介紹,結合自己的理解,來談一談js中的繼承。
1.原型鏈
基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
function car() { this.name = name || ‘車‘; this.Oncar=[‘小明‘,‘小紅‘];//車上的人 this.drive =function(){ console.log(this.name +‘在行駛‘); } } function dazhong() {this.V = 200; this.chesu = function() { console.log(‘車速是‘+this.V+‘km/h‘); } } dazhong.prototype = new car();//繼承 var Dz1 = new dazhong(); Dz1.name = ‘大眾suv‘; Dz1.drive();//大眾suv在行駛 Dz1.Oncar.push(‘大白‘);//為大眾suv加一個乘客 console.log(Dz1.Oncar);//["小明", "小紅", "大白"] var Dz2 =new dazhong(); Dz2.name = ‘冒牌大眾‘; Dz2.drive();//冒牌大眾在行駛 console.log(Dz2.Oncar);//["小明", "小紅", "大白"] 冒牌大眾上居然也共享了這個乘客!
利用原型鏈的方法,我們可以方便的實現繼承,但是有一個問題,那就是 所有的實例共享了引用類型的值!
只是引用類型的值共享了,實例中非引用類型的值並沒有共享。(Dz1.drive和Dz2.dirve 不一樣);
為了解決原型中包含引用類型值所帶來的問題,又推出了另外一種方法: 借用構造函數(constructor stealing)
2.借用構造函數
基本思想是 在子類型構造函數的內部調用超類型構造函數。
函數只不過是在特定環境中執行代碼的對象,因此通過使用apply()和call()方法也可以在新創建的對象上執行構造函數。
function dazhong() { car.call(this);//繼承 this.V = 200; this.chesu = function() { console.log(‘車速是‘+this.V+‘km/h‘); } } var Dz1 = new dazhong(); Dz1.name = ‘大眾suv‘; Dz1.drive();//大眾suv在行駛 Dz1.Oncar.push(‘大白‘);//為大眾suv加一個乘客 console.log(Dz1.Oncar);//["小明", "小紅", "大白"] var Dz2 =new dazhong(); Dz2.name = ‘冒牌大眾‘; Dz2.drive();//冒牌大眾在行駛 console.log(Dz2.Oncar);//["小明", "小紅"]
通過這種方法,可以在子類型構造函數裏向超類型構造函數裏傳值啦。
function car(driver) { this.name = name || ‘車‘; this.Oncar=[‘小明‘,‘小紅‘];//車上的人 this.driver = driver; this.drive =function(){ console.log(this.name +‘在行駛‘); } } function dazhong() { car.call(this,‘小明‘);//繼承 this.V = 200; this.chesu = function() { console.log(‘車速是‘+this.V+‘km/h‘); } } var dz1 = new dazhong(); console.log(‘司機是‘+ dz1.driver);//司機是小明 var dz2 =new dazhong(); dz2.driver = ‘小紅‘; console.log(‘司機是‘+ dz1.driver);//司機是小明 console.log(‘司機是‘+ dz2.driver);//司機是小紅 console.log(dz1 instanceof car);//false dz1 並不是 car 的實例 console.log(dz1 instanceof dazhong);//true dz1 是 dazhong 的實例
借用構造函數的辦法雖然解決了傳值和引用類型不共享的問題,但是他依然存在著構造函數模式存在的問題——函數無法復用。
方法都寫到了構造函數中,對內存是一種損耗。 所以這時候又引出了另外一種方法: 組合繼承
3.組合繼承
組合繼承其實是將之前的兩種繼承方式結合起來,各取其長的繼承方式。
核心思想是 使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性(每個實例各自不同的東西)的繼承。
function car(driver) { this.name = name || ‘車‘; this.Oncar=[‘小明‘,‘小bai‘];//車上的人 this.driver = driver; } car.prototype.drive = function() { console.log(‘司機‘+this.driver+‘駕駛著‘+this.name+‘,在高速公路上‘); }; function dazhong(driver,v) { car.call(this,driver);//繼承 this.V = v || 0; } // 繼承 dazhong.prototype = new car(); dazhong.prototype.constructor = dazhong; dazhong.prototype.chesu = function() { console.log(‘車速是‘+this.V+‘km/s‘); }; var dz1 = new dazhong(‘小明‘,100); dz1.name = ‘斯柯達‘; dz1.Oncar.push(‘小黑‘);//["小明", "小bai", "小黑"] dz1.drive();//司機小明駕駛著斯柯達,在高速公路上 dz1.chesu();//車速是100km/s console.log(dz1.Oncar); var dz2 =new dazhong(‘小紅‘,40); dz2.name = ‘高爾夫‘; dz2.Oncar.push(‘小紅‘); dz2.drive();//司機小紅駕駛著高爾夫,在高速公路上 dz2.chesu();//車速是40km/s console.log(dz2.Oncar);//["小明", "小bai", "小紅"] console.log(dz1 instanceof car);//true console.log(dz1 instanceof dazhong);//true console.log(dz2 instanceof car);//true console.log(dz2 instanceof dazhong);//true
組合繼承的方式是現在js裏常用的繼承方式。
4.原型式繼承
function object(o){ function F(){ } F.prototype = o; return new F(); }在object()函數內部,先創建了一個臨時性的構造函數,然後將傳入的對象作為這個構造函數的原型,最後返回了這個臨時類型的一個新實例。從本質上講,object()對傳入其中的對象執行了一次淺復制。 ——《js高程》
在高程裏面講的原型式的繼承就是這個原理。而ES5新增了 Object.create() 方法 規範化了原型式繼承。
var car ={
name:"車",
Oncar:[‘小明‘,‘小白‘],
driver:‘人‘,
drive: function() {
console.log(‘司機‘+this.driver+‘駕駛著‘+this.name+‘,在高速公路上‘);
}
}
var dz1 = Object.create(car);//繼承
dz1.name = ‘斯柯達‘;
dz1.driver = ‘小明‘;
dz1.V = 200;
dz1.Oncar.push(‘小黑‘);
dz1.chesu = function() {
console.log(‘車速為‘+this.V+‘km/h‘);
}
dz1.drive();//司機小明駕駛著斯柯達,在高速公路上
dz1.chesu();//車速為200km/h
console.log(dz1.Oncar);//["小明", "小白", "小黑"]
var dz2 = Object.create(car);
dz2.name = ‘高爾夫‘;
dz2.driver = ‘小紅‘;
dz2.V = 40;
dz2.Oncar.push(‘小白‘);
dz2.chesu = function() {
console.log(‘車速為‘+this.V+‘m/s‘);
}
dz2.drive();//司機小紅駕駛著高爾夫,在高速公路上
dz2.chesu();//車速為40km/h
console.log(dz2.Oncar);//["小明", "小白", "小黑", "小白"]
和原型鏈的方式一樣,原型式繼承依然存在 引用類型變量共享的問題。
5.寄生式繼承
寄生式繼承是與原型式繼承緊密相關的一種思路,創建一個僅用於封裝繼承過程的函數,該函數在內部用某種方式來增強對象,最後再像其他真地是它做了所有工作一樣返回對象
function object(oo){ function F(){} F.prototype = oo; return new F(); } function jicheng(o) { var clone = object(o); clone.drive =function() { console.log(‘司機‘+this.driver+‘駕駛著‘+this.name+‘,在高速公路上‘); //強化... }; return clone; } var car ={ name:"車", Oncar:[‘小明‘,‘小白‘], driver:‘人‘, } var dz1 = jicheng(car);//繼承 dz1.name = ‘斯柯達‘; dz1.driver = ‘小明‘; dz1.V = 200; dz1.Oncar.push(‘小黑‘); dz1.chesu = function() { console.log(‘車速為‘+this.V+‘km/h‘); } dz1.drive();//司機小明駕駛著斯柯達,在高速公路上 dz1.chesu();//車速為200km/h console.log(dz1.Oncar);//["小明", "小白", "小黑"]
可是我覺得...這個方法好像和原型式繼承差不多(沒啥用)....希望大佬們能給出這個方法存在的意義...
6.寄生組合式繼承
據說是很棒的繼承方式,就是寫法比較復雜...寄生組合繼承是對組合繼承的改進.為什麽要對組合繼承進行改進呢?
因為在組合繼承裏面,無論什麽情況下,都會調用兩次超類構造函數。
function a(name) { this.name = name; this.type = [1,2,3]; } a.prototype.sayName = function() { console.log(this.name); }; function b(name,age){ a.call(this,name);//第二次調用a() this.age = age; } b.prototype = new a();//第一次調用a() b.prototype.constructor = b; b.prototype.sayAge =function(){ console.log(this.age); }
註釋部分,第一次調用a構造函數的時候 b.prototype 會得到兩個屬性:name 和 type,他們位於b的原型裏面。
第二次調用a的時候 則是在新對象上面創建了屬性name 和type。新實例上面的name 和 type 屏蔽了原型中的name 和 type。因此就引出了 寄生組合式繼承
其基本思想是 通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。
function object(o){ function F(){}; F.prototype = o; return new F(); } function jsInherit(zi,chao){ var proto = object(chao.prototype);//創建對象 proto.constructor = zi;//增強對象 zi.prototype = proto;//指定對象 } function car(driver) { this.name = name || ‘車‘; this.Oncar=[‘小明‘,‘小bai‘];//車上的人 this.driver = driver; } car.prototype.drive = function() { console.log(‘司機‘+this.driver+‘駕駛著‘+this.name+‘,在高速公路上‘); }; function dazhong(driver,v) { car.call(this,driver);//繼承 this.V = v || 0; } jsInherit(dazhong,car); dazhong.prototype.chesu = function () { console.log(‘車速是‘+this.V+‘km/h‘); } var a = new dazhong(‘大白‘,69); a.name = ‘suv‘; a.drive(); a.chesu(); a.Oncar.push(‘大白‘);
以上就是我根據 js高程 裏的內容結合自己的實踐談的js的繼承,如果有什麽地方不合理的希望大佬們指出來。
結合著這篇文章來看繼承,會有更多的收獲!
xiexiedajia
淺談JS的繼承