原文發表在: holmeshe.me , 本文是漢化重製版。javascript
本系列在 Medium上同步連載。java
還記得早先用ajax胡亂作項目的時候踩過好多坑,而後對JS留下了「很是詭異」的印象。最近換了一個工做,工做語言就是JS。而後發現這個語言真不得了,前面後面都能幹,基本成了全棧的同義詞。因此恰好就趁這個機會系統學習一下這個語言。由於是給程序員看的,這個系列不會講基本的「if-else」,循環,或者面向對象。相反,我會關注差別,但願能給您在Pull Request裏走查代碼式的學習體驗!程序員
類繼承在JavaScript裏面能夠用原型鏈實現。任何一個對象均可以用和本身綁定的原型,像親子鑑定同樣在原型鏈回溯到本身的親爹,以及親爺爺等等。原型鏈也自然支持繼承的最終目的,代碼複用:當訪問一個當前對象裏不存在的成員時,JS引擎會從當前對象開始,在原型鏈上,向上查找集成(子)樹的成員信息。
ajax
光用說的不太好理解,接下來我會一步一步實現這個原型鏈,因此這個閱讀更像一個模擬的真實開發過程,我以爲搞成這樣可能會比較容易記。
打上碼:bash
var LaoWang = function() {
this.aproperty = 'a';
this.amethod = function () {
return 'b';
};
};
var XiaoMing = function() {};
XiaoMing.prototype = LaoWang.prototype; //let's make the chain var subobj = new XiaoMing(); alert(subobj instanceof LaoWang); alert(subobj.aproperty); alert(subobj.amethod());複製代碼
運行結果:app
true
undefined
Uncaught TypeError: subobj.amethod is not a function複製代碼
雖然類型是正確識別了,可是成員訪問還有問題。因此按通常經驗,我猜是父類的構造函數沒調。試一下:函數
var LaoWang = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};
var XiaoMing = function() {
++ LaoWang();
};
XiaoMing.prototype = LaoWang.prototype;
++XiaoMing.prototype.constructor = XiaoMing;
var subobj = new XiaoMing();
alert(subobj instanceof LaoWang);
alert(subobj.aproperty);
alert(subobj.amethod());複製代碼
仍是不行。嗯嗯嗯,此次應該是父類的構造函數裏面this指針又指錯了。若是你沒猜到,請 ⬅到我以前的文章有講。
學習
正如上面提到的文章裏面說的,要修改這個問題,咱們能夠:ui
var tmpf = LaoWang.bind(this);
tmpf();複製代碼
也能夠:this
LaoWang.apply(this);複製代碼
我用第二種,由於能夠少寫一行代碼:
var LaoWang = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};
var XiaoMing = function() {
++ LaoWang.apply(this);
};
XiaoMing.prototype = LaoWang.prototype;
XiaoMing.prototype.constructor = XiaoMing;
var subobj = new XiaoMing();
alert(subobj instanceof LaoWang);
alert(subobj.aproperty);
alert(subobj.amethod());複製代碼
運行結果:
true
a
b複製代碼
搞定了!嗎?
var LaoWang = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};
var XiaoMing = function() {
LaoWang.apply(this);
};
XiaoMing.prototype = LaoWang.prototype;
XiaoMing.prototype.constructor = XiaoMing;
++XiaoMing.prototype.another_property = 'c';
var subobj = new XiaoMing();
++var superobj = new LaoWang();
++alert(subobj.another_property);
++alert(superobj.another_property);複製代碼
運行結果:
c
c複製代碼
事實上咱們剛纔搞出來的徹底不是繼承,而是其它不存在的東西。我姑且叫他「結對」吧。
方向徹底錯了。。。雖然方向錯了,但其實大部分的努力仍是正確的。正如我有時聽到一類故事,主角雖然創業失敗了,可是某一天發現,這個看似失敗的經歷積累的經驗和資源直接關係到後面的成功。因此咱們繼續。
正常的作法是用兩個實體,一箇中間類(其實也是一個對象,第一等)和一箇中間對象(普通),來解耦咱們剛纔寫出來不知所謂的東西,關係我畫一下:
Subclass.prototype
|---intermediate object
|---.__proto__
|---IntermediateClass.prototype === Superclass.prototype複製代碼
在這個裏面,中間對象是這個中間類的一個實例。
那咱們回到上篇最開始的一段代碼:
function inherits(ChildClass, ParentClass) {
function IntermediateClass() {}
IntermediateClass.prototype = ParentClass.prototype;
ChildClass.prototype = new IntermediateClass;
ChildClass.prototype.constructor = ChildClass;
return ChildClass;
};複製代碼
在看過具體問題和原理圖之後,是否是就讀得懂了呢?咱們驗證一下:
var LaoWang = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};
var XiaoMing = function() {
LaoWang.apply(this);
};
--XiaoMing.prototype = LaoWang.prototype;
++inherits(XiaoMing, LaoWang);
XiaoMing.prototype.another_property = 'c';
var subobj = new XiaoMing();
var superobj = new LaoWang();
alert(subobj instanceof LaoWang);
alert(subobj instanceof XiaoMing);
alert(subobj.aproperty);
alert(subobj.amethod());
alert(subobj.another_property);
alert(superobj instanceof LaoWang);
alert(superobj instanceof XiaoMing);
alert(superobj.aproperty);
alert(superobj.amethod());
alert(superobj.another_property);複製代碼
事實上,一個更好版本的inherits()
能夠用Object.create()
來實現:
function inherits(ChildClass, ParentClass) {
-- function IntermediateClass() {}
-- IntermediateClass.prototype = ParentClass.prototype;
-- ChildClass.prototype = new IntermediateClass;
++ ChildClass.prototype = Object.create(ParentClass.prototype);
ChildClass.prototype.constructor = ChildClass;
return ChildClass;
};複製代碼
寫了這麼多,驗證的時候還有點小緊張:)
true
true
a
b
c
// this line is artificial
true
false
a
b
undefined複製代碼
好了,能夠按時下班了!
老鄉留步,還有最後一點沒說:
你可能已經留意到了,我在這裏偷偷加了一段代碼可是沒說明。我是故意的,由於能夠把上面的邏輯搞流暢一點。下面咱們來看看這個。。。好吧,一言難盡,我再畫個圖。
class <----------------|
|------.prototype.constructor
|------......(I have drawn this part)複製代碼
prototype.constructor實際上是一個特殊的方法(最後再囉嗦一遍,第一類對象),這個方法指向的是這個類自己。正常狀況下這個指向是由JS引擎在運行時完成的:
var LaoWang = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};
alert(LaoWang.prototype.constructor === LaoWang);複製代碼
運行結果:
true
可是呢,我在這個函數
function inherits(ChildClass, ParentClass)
複製代碼
裏面作原型鏈的時候,把
ChildClass.prototype.constructor
這個指針的原始值給弄亂了,因此咱們須要把這個值給設回去:
ChildClass.prototype.constructor = ChildClass;複製代碼
好了,今天先寫到這。若是您以爲這篇不錯,能夠關注個人公衆號 - 全棧獵人。
也能夠去Medium上隨意啪啪啪個人其餘文章。感謝閱讀!👋