給程序員看的Javascript攻略 - Prototype (下)



原文發表在: 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複製代碼

好了,能夠按時下班了!

老鄉留步,還有最後一點沒說:

prototype.constructor

你可能已經留意到了,我在這裏偷偷加了一段代碼可是沒說明。我是故意的,由於能夠把上面的邏輯搞流暢一點。下面咱們來看看這個。。。好吧,一言難盡,我再畫個圖。

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上隨意啪啪啪個人其餘文章。感謝閱讀!👋

相關文章
相關標籤/搜索