繼承是OOP中你們最喜歡談論的內容之一,通常來講,繼承都兩種方式:接口繼承和實現繼承而JavaScript中沒有接口繼承須要的方法,所以只能依靠實現繼承。
在講繼承的實現以前,咱們首先來回顧一下什麼是繼承?繼承的做用是什麼?數組
它可使用現有類的全部功能,並在無需從新編寫原來的類的狀況下對這些功能進行擴展。
經過繼承建立的新類稱爲「子類」或「派生類」。
被繼承的類稱爲「基類」、「父類」或「超類」。
在清楚繼承的做用以後,下面咱們來探討一下JS中的幾種繼承實現的方式:
//混入式繼承(拷貝)//obj2繼承到obj1中的成員,能夠直接將obj1中的成員拷貝到obj2中便可var obj1 = {name:"zs",age:10};var obj2 = {};// 將obj1中的成員拷貝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}console.log(obj1);console.log(obj2);
最終獲得的obj2中的成員和obj1中的成員徹底一致,固然,咱們須要清除的是,此時的obj1和obj2是兩個不一樣的對象。
混入式繼承方式看似很簡單,可是存在共享數據安全的問題。
var obj1 = {name:"zs",age:10,car:{name:"mini"}};var obj2 = {};// 將obj1中的成員拷貝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}//修改obj1對象中的car屬性
obj1.car.name = "bus";
console.log(obj1);console.log(obj2);
當咱們須要修改某些對象中的引用類型對象的時候,會形成其餘相關的對象也被修改,這是咱們不但願看到的。
原型式繼承的實現
回想一下,當咱們在訪問一個對象中的成員的時候,首先是在當前對象中查找,若是找不到,就往上,在原型鏈中依次查找,若是在整個原型鏈中也不存在該成員,那麼就返回undefined。
因此,咱們想要在A對象中訪問到B對象中的成員,除了將B中的成員添加到A中,如:混入式,咱們也能夠考慮將B中成員添加到A的原型鏈中,實現對象成員的共享。
function Animal() {
}
Animal.prototype.name="animal";function Person() {
}//修改Person的原型對象
Person.prototype= Animal.prototype;安全
Person.prototype.useTool = function () {
console.log("use fire");
}var p = new Person();console.log(p);var ani = new Animal();console.log(ani);
畫圖分析:markdown
- 最初,Animal和Person的兩個對象沒有任何關係,因此各自只能訪問各自的成員
- 如今,Person對象若是想要繼承Animal對象,只須要將Person的原型對象修改成Animal的原型對象便可
這種方式實現的繼承稱之爲原型繼承,實現也是比較方便的,當時和混入式同樣,存在數據共享的問題。
原型鏈繼承的實現
原型式繼承
在前面的課程中,咱們講了原型式的繼承,這種繼承方式是修改子類對象原型指向父類對象的原型,如前面的MyArray執行的是Array的原型對象。
這種方式存在問題是,只能繼承父類對象原型上的成員,但沒法繼承父類對象中的成員。
function Animal() {
this.color="red";
}
Animal.prototype.weight=100;function Person() {
}
Person.prototype = Animal.prototype;var p = new Person();console.log(p);
Person.prototype = Animal.prototype;原型式的繼承
此時建立出來的Person對象p能夠訪問Animal中的weight屬性,可是沒法訪問color屬性。看圖理解
那麼,若是咱們既要繼承原型對象中的成員,也要繼承實例對象中的成員,就應該使用這一節中的原型鏈繼承
function Animal() {
this.color="red";
}
Animal.prototype.weight=100;function Person() {
}
Person.prototype = new Animal();var p = new Person();console.log(p.color);//red
使用原型鏈繼承方式可以繼承到更多的成員。可是依然存在問題:app
- 共享的問題
- 沒法向構造器傳參
複雜的原型鏈繼承示例---
Object-Animal-Person-Boy
Object.create方法的基本使用
在Object的構造函數上存在一個create方法,該方法的做用是用來建立對象的。
該方法能夠接收的參數有一下兩種
-
nullide
-
建立一個空對象,這個空對象中連最基本的原型對象都沒有的函數
-
-
對象this
-
建立傳遞進來的對象,並設置該對象的原型對象爲當前的參數prototype
-
該方法的使用率比較低,要求你們知道存在這樣的一種建立對象的方式便可。
call方法和apply方法的基本使用
call和apply方法做用:對象
- 方法借用2. 設置方法中this的指向
var obj1 = {
name:"Neld",
age:10,
showInfo:function () {
console.log("name:"+this.name,"age:"+this.age);
}
}var obj2 = {
name:"Lily",
age:12
}
obj1.showInfo();//name:Neld age:10
obj2.showInfo();//obj2.showInfo is not a function
obj1對象中有showInfo方法,而obj2對象中沒有,因此若是直接使用obj2調用showInfo方法的時候拋出錯誤信息。
若是咱們臨時須要使用obj2調用showInfo方法來打印出name和age屬性的值,此時可使用這裏的call或者apply方法來實現。
obj1.showInfo.call(obj2);//name:Lily age:12
obj1.showInfo.apply(obj2);//name:Lily age:12
這就是把obj1中的showInfo方法借用給obj2使用。
同時咱們觀察到,在showInfo方法中使用到了this關鍵字,在obj2借用該方法的時候,其中的this已經指向了obj2對象,這就要達到修改方法中this關鍵字的指向的目的。
call和apply方法的做用是徹底同樣的,那麼他們的區別是什麼呢?繼續往下分析。
var obj1 = {
name:"Neld",
age:10,
add : function (a, b) {
return a + b;
}
}var obj2 = {
name:"Lily",
age:12
}console.log(obj1.add.call(obj2, 1, 2));//3//console.log(obj1.add.apply(obj2, 1, 2));//CreateListFromArrayLike called on non-objectconsole.log(obj1.add.apply(obj2, [2, 2]));//4
在obj1中定義一個帶有兩個參數的方法,obj2中沒有,問題同樣,obj2也要使用到obj1中的add方法,此時使用call或者apply借用便可。
此時新的問題是,在調用add方法的時候參數應該如何傳遞?
call方法:
將this指向的對象做爲第一個參數,其餘參數依次傳遞便可
apply方法:
將this指向的對象做爲第一個參數,其餘參數封裝到數組中傳遞
若是沒有使用數組,程序報錯。
以上就是call和apply的基本使用,這兩個方法在後續的課程中會大量的使用到,因此必須引發重視。
借用構造函數繼承說明
所謂借用構造函數,就是在子構造函數中調用父構造函數,達到繼承並向父構造函數傳參的目的。
function SuperClass(name,age) {
this.name = name;
this.age = age;
}
SuperClass.prototype.fun1 = function () {
console.log("name:",this.name,"age:",this.age);
}function SubClass(color) {
this.color = color;
}
SubClass.prototype = new SuperClass("xxx",10);//繼承父構造函數並設置name和age的值
SubClass.prototype.fun2 = function () {
console.log("color:",this.color);
}var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
上面是原型鏈的繼承,這種方式存在一個問題是,在建立不一樣對象的時候,沒法爲其繼承過來的成員賦值。
這裏的sub和sub2兩個對象的name和age屬性值都是「xxx」和10,很明顯是不知足咱們需求的。
那麼咱們來看看借用構造函數是否能解決這個問題呢?
function SuperClass(name,age) {
this.name = name;
this.age = age;
}
SuperClass.prototype.fun1 = function () {
console.log("name:",this.name,"age:",this.age);
}function SubClass(color, name, age) {
this.newMethod = SuperClass;//①
this.newMethod(name, age);//②
this.color = color;
delete this.newMethod;//③
}//SubClass.prototype = new SuperClass();
SubClass.prototype.fun2 = function () {
console.log("color:",this.color);
}var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
①、②、③處代碼是實現借用構造函數的關鍵。下面一一做出解釋:
①:將父對象的構造函數設置爲子對象的成員
②:調用這個方法,想過相似於將父構造函數的代碼放在子構造函數中來執行
function SubClass(color, name, age) {
this.newMethod = SuperClass;
this.name = name;//父構造函數中的代碼
this.age = age;//父構造函數中的代碼
this.color = color;
delete this.newMethod;
}
這樣看應該更直觀一點,執行以後就是在爲當前建立出來的對象封裝name和age屬性。
③:在子構造函數中,newMethod僅僅爲了在②調用父構造函數使用,用完以後也就沒了價值,因此,直接刪除該方法便可
能夠看到,借用構造函數繼承方式解決了原型鏈及繼承的問題。
下面再看看另一種借用構造函數的實現方式(使用call或apply):
function SubClass(color, name, age) {
//SuperClass.call(this,name,age);
SuperClass.apply(this,[name,age]);
this.color = color;
}
咱們可使用call或apply更簡單快捷的實現和上面同樣的效果。
以上就是借用構造函數繼承(也要對象冒充)的兩種實現方式。固然,這種繼承方式都存在下面兩個問題:
- 若是父子構造函數存在相同的成員,那麼子構造函數會覆蓋父構造函數中的成員
- 不能繼承原型鏈中的成員
組合繼承的基本結構
基於原型鏈繼承和借用構造函數繼承兩種方式存在的問題,咱們提出一個解決方案---組合繼承,就是兩種一塊兒使用。
function SubClass(color, name, age) {
//SuperClass.call(this,name,age);
SuperClass.apply(this,[name,age]);//繼承構造函數中的成員
this.color = color;
}
SubClass.prototype = new SuperClass();//繼承原型鏈上的成員
總結: ECMAScript 實現繼承的方式不止一種。這是由於 JavaScript 中的繼承機制並非明確規定的,而是經過模仿實現的。這意味着全部的繼承細節並不是徹底由解釋程序處理。做爲開發者,你有權決定最適用的繼承方式。
繪製完整的原型鏈結構圖
這一節重點探討函數對象的原型鏈結構。完整的結構圖以下:
說了這麼多,其實核心只有一個:屬性共享和獨立的控制,當你的對象實例須要獨立的屬性,全部作法的本質都是在對象實例裏面建立屬性。若不考慮太多,你大能夠在Person裏面直接定義你所須要獨立的屬性來覆蓋掉原型的屬性。總之,使用原型繼承的時候,要對於原型中的屬性要特別注意,由於他們都是牽一髮而動全身的存在。blog