JavaScript中有幾種‘類’的構建模式:對象修飾、函數(shù)、原型绍赛、偽類,都相對比較好理解溃槐,總結構建偽類子類的幾種錯誤方法。
首先科吭,模擬一個場景昏滴,比如汽車,汽車有很多種对人,基于汽車的通用部分谣殊,我們創(chuàng)建父類(也可以叫做基類或者超類,以下都稱父類)Car牺弄,子類BMW姻几,實例mini和bus:
var Car = funciton(loc){
this.loc = loc;
};
Car.prototype.move = function(){
this.loc++;
};
var BMW = function(loc){
//如何構造?下文代碼替換處
};
//調用
var bus = new Car(30);
bus.move();
var mini = new BMW(80);
mini.move();
mini.speedUp();
```
那么势告,該如何構造子類捏蛇捌?
### 不建議:直接重復
替換代碼:
this.loc = loc;
這會在每一個實例中創(chuàng)建一個loc屬性,并且會指向對應的數(shù)字咱台。雖然可以達到效果络拌,但是有弊端:
1.失去了父類和子類的關系;
2.實際代碼中代碼邏輯要復雜的多回溺,那么需要重復的部分也很多盒音,冗余;
3.一旦需要修改馅而,那么需要修改多處,很容易遺忘&出錯譬圣;
### 錯誤:僅調用Car函數(shù)
替換代碼:
new Car(loc);
除了在調用的時候創(chuàng)建的對象外(bus)瓮恭,這行代碼將會產(chǎn)生一個新的對象。
換一個角度厘熟,在BMW用new調用Car函數(shù)時屯蹦,Car函數(shù)中的this指向的是Car的一個全新的對象维哈。
帶關鍵字new運行任何函數(shù)的作用是在函數(shù)體中’隱藏的’添加了兩行代碼:
在第一行添加了:this = Object.create(Car.prototype);將this等同于一個全新的對象(規(guī)則上不允許在代碼中直接對this賦值,但在解釋器中可以運行)登澜;
在最后一行添加了:return this;
在這里阔挠,首先我們用new函數(shù)調用了BMW,已經(jīng)有一行’隱藏的‘:this = Object.create(BMW.prototype);這樣就有了兩個新對象脑蠕,然而我們不需要第二個购撼。
那么試著賦值給關鍵字this能否解決?
### 錯誤:給this賦值
替換代碼:
this = new Car(loc);
規(guī)則錯誤谴仙,上面說過了~~在代碼里不能直接給this關鍵字賦值迂求。即使能,也依舊不能解決有兩個不同的對象的問題晃跺,有一個是不需要的揩局;
那么如果直接調用捏?
Car(loc);
依舊不行~~
在調用的時候掀虎,Car中的this會指向global,也就是說凌盯,Car函數(shù)將會在全局作用域的上下文環(huán)境中運行(Car函數(shù)被作為一個自由函數(shù)調用,實際上將會綁定一個指向數(shù)字80的全局變量loc)烹玉,mini實例將完全不會被Car函數(shù)里的代碼影響驰怎;
### 推薦:使用call
替換代碼:
Car.call(this,loc);
調用Car函數(shù),并且把參數(shù)綁定到mini上春霍,這樣只會創(chuàng)造一個實例砸西,并且使BMW在創(chuàng)建實例mini的時候調用了Car的構造器~~
### 原型鏈的處理
使用call雖然完成了子類的構造,但是此時可以發(fā)現(xiàn)址儒,mini.move()無法運行芹枷,這是因為mini是BMW的實例,原型鏈是委托在BMW.prototype上的莲趣,而BMW.prototype上并沒有定義move函數(shù)鸳慈,move函數(shù)是定義在car.prototype上的。(注:BMW.prototype是委托在Object.prototype上的)
因此喧伞,需要將BMW的原型委托到Car的原型上,同時不要忘了constructor~~
在代碼中添加:
BMW.prototype = Object.create(Car.prototype);
BMW.prototype.constructor = BMW; //否則實例的constructor會指向Car
這里有一個最常見的錯誤走芋,就是嘗試實例化父類構造器,作為委托給它原型的方法:
```javascript
var Car = function(loc){
this.loc = loc;
};
Car.prototype.move = function(){
this.loc++;
};
var BMW = function(){
Car.call(this,loc);
};
BMW.prototype = new Car();
```
這幾乎和Object.create方法實現(xiàn)了相同的效果潘鲫,但是唯一的區(qū)別是它在創(chuàng)建這個新對象的時候運行了Car函數(shù)翁逞,這點不太好~~
Object.create并不是一個很新的語言特性。
常見做法規(guī)定設置BMW.prototype等于一個新的Car實例溉仑,但是這樣會引起很多問題挖函,每次我們構造一個類似BMW的子類或者其他Car的子類,都會調用Car這個函數(shù)作為整個過程的一部分浊竟,父類構造函數(shù)在執(zhí)行時也許要求一些參數(shù)(比如loc)怨喘,但是沒有任何方式傳遞他們津畸,因為新的Car的原型并沒有實際意義上的位置屬性,唯一擁有有意義的位置屬性的實物是Car或者BMW的實例必怜,所有的BMW在抽象概念上沒有一個有意義的位置肉拓,因此在這里傳遞loc輸入變量是沒什么用的,這個函數(shù)運行的時候會把他的所有輸入綁定為undefined梳庆。
如果代碼行需要處理這些輸入暖途,比如這個點訪問:
```javascript
var Car = function(loc){
this.loc = loc.valueOf();
}
```
那么loc是undefined,所以undefined.valueOf()將會導致一個錯誤……
構造一個足夠強健的構造器去避免這種錯誤是非常難的靠益,實際工作中的代碼邏輯也很復雜丧肴,著實沒必要,用Object.create就行~
最后完整的代碼:
```javascript
var Car = funciton(loc){
this.loc = loc;
};
Car.prototype.move = function(){
this.loc++;
};
var BMW = function(loc){
Car.call(this,loc);
};
BMW.prototype = Object.create(Car.prototype);
BMW.prototype.constructor = BMW;
//調用
var bus = new Car(30);
bus.move();
var mini = new BMW(80);
mini.move();
mini.speedUp();
```
就醬~