首頁  >  文章  >  web前端  >  JS中的原型鏈詳解

JS中的原型鏈詳解

小云云
小云云原創
2018-03-22 17:19:081792瀏覽


JS雖然不是物件導向類型的語言,但這不並不代表JS就不能夠實現OOP的特性。 我相信大家在使用JS的時候,一定有用過Object的原型方法,像是call,apply,hasOwnProperty等等方法,可是這些方法是從哪裡來的呢?如果JS無法實作繼承的話,這些方法的使用就無從談起了。這裡我們就來談談在JS實現繼承的方法,原型鏈。

_proto_和prototype

首先我們要了解什麼是普通對象,什麼是函數物件。

  • 普通物件

    • #var a = {}

    • #var a = new Object();

    • var a = new f1();//與上一個建立物件的方式相同

  • 函數物件

    • var a = function(){};

    • var a = new Function() {};

    • f1()

#_proto_是每個普通物件都擁有的屬性,用來指向建構函數的prototype,也就是建構函式的原型物件。而建構子的原型物件一般來說也是一個普通物件(在建構子是Function的時候,它就變成了一個函數物件),所以它也有_proto_屬性。而它的_proto_則指向它的建構子的原型對象,也就是Object.prototype。最後的Object.prototype._proto_指向null,到了原型鏈的頂端。
prototype是函數物件都擁有的屬性,它在物件建立的時候被指定給新的物件實例。當然也可以動態修改。

   function Person(){};   var p = new Person();//创建一个普通对象
   //创建过程实际为
   var p={};
   p._proto_=Person.prototype;
   Person.apply(p,arguments);//或者是call...
   //执行构造函数,并返回创建的对象。

對上面程式碼的補充說明

正常來講建構函式中是不用寫return語句的,因為它會預設回傳新建立的物件。但是,如果在建構函式中寫了return語句,如果return的是一個對象,那麼函數就會覆寫新建立的對象,而傳回此物件;如果return的是基本型別如字串、數字、布林值等,那麼函數會忽略掉return語句,還是傳回新建立的物件。

而建構函數的原型物件的預設值為:

  Person.prototype={
    constructor://指向构造函数本身
    _proto_://指向构造函数Person的原型对象的构造函数的原型对象,这里是指Object.prototype
  }
  //这里有一个特殊情况——当构造函数为Function的时候
  Function.prototype._proto_===Object.prototype 
  //我们知道Function.prototype是一个函数对象,它的_proto_应该指向它的构造函数的原型,也就是Function.prototype。
  //可是这样下去就没完没了了,毕竟一条链总是有顶端的。这里约定Function.prototype._proto_===Object.prototype;  //这时,Object.prototype._proto_===null;完美结束原型链。

我們可以不斷修改建構子的原型物件的指向,這樣最終就可以形成一條鏈。而上面提到的一條鏈就是JS中的預設原型鏈。

談談程式碼實作

下面我們來看看程式碼:

  function Parent(name){
        this.name=name||"parent";
    }    function Son(name){
        this.name=name||"son";        this.property="initial Son name";
    }    function Grandson(name){
        this.name=name||"grandson";        this.ggs="initial Grandson name";
    }

    Son.prototype = new Parent("原型中的Parent");
    Grandson.prototype = new Son("原型中的Son");    let grandson = new Grandson("孙子");
    console.log(grandson instanceof Son);//true
    console.log(grandson instanceof Grandson);//true
    console.log(grandson instanceof Parent);//true

JS中的原型鏈詳解
很顯然,最後都會輸出true。但是我們改動一點程式碼:

    Grandson.prototype = new Son("原型中的Son");
    Son.prototype = new Parent("原型中的Parent");//其实上一步已经实例化了一个Son的对象给Grandson.prototype
    //这个时候Son的实例的_proto_已经确定指向那个时候的构造函数.prototype了(默认原型对象)
    let grandson = new Grandson("孙子");
    console.log(grandson instanceof Son);//false
    console.log(grandson instanceof Grandson);//true
    console.log(grandson instanceof Parent);//false

JS中的原型鏈詳解
為什麼結果會改變呢?原因也很簡單。我們之前有提到物件的創建的創建過程:物件在實例化的時候就已經給物件的_proto_賦了建構子的prototype了。也就是說上面程式碼中第一行已經確定了Grandson.prototype._proto_的值了,即使在第二行修改了Son.prototype也是無法修改Grandson.prototype._proto_的值。

Conclusion:JS中原型鏈的關係是由_proto_維持的,而不是prototype。

小測驗

var animal = function(){}; var dog = function(){};

 animal.price = 2000;
 dog.prototype = animal; var tidy = new dog();
 console.log(dog.price) 
 console.log(tidy.price)

答案是輸出什麼呢?是undefined和2000,我們分析一下:
首先我們清楚animal和dog都是函數對象,在第四行修改了dog的原型對象為animal。那我們接著往下看,console.log(dog.price) 這句話首先會尋找dog的price,沒有。然後到原型鏈上尋找。怎麼找的呢?我們之前提到是透過_proto_去到它建構函數的原型物件上,這裡因為dog是函數對象,那麼它的建構函式的原型物件就是Function.prototype,這是一個empty function。於是回傳undefined,沒有找到price這個屬性。
那麼console.log(tidy.price) 呢?
tidy是一個普通對象,首先也是尋找它本身的屬性price,也沒有。透過_proto_去到它建構函式的原型物件上,也就是dog.prototype。因為tidy實例化在dog.prototype = animal; 之後,所以tidy._proto_的指向已經指向了修改後的dog.prototype。也就是指向了animal,也就是能夠找到price這個屬性了,所以輸出2000。

原型物件上的所有屬性和方法都可以看成是Java中父類別的public(protected)屬性和方法,在這些方法內部使用this即可存取建構函式中的屬性和方法。至於為什麼,這又得提到JS中this的綁定問題了….總而言之,誰調用的函數,this就指向誰。箭頭函數除外…

  相關建議:

詳解JS原型與原型鏈(一)

詳解JS原型與原型鏈(二)

詳解JS原型與原型鏈(三)

以上是JS中的原型鏈詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn