1. 程式人生 > >大白話通俗易懂的講解javascript原型與原型鏈(__proto__、prototype、constructor的區別)

大白話通俗易懂的講解javascript原型與原型鏈(__proto__、prototype、constructor的區別)

  javascript原型和原型鏈是js中的重點也是難點,理論上來說應該是屬於面向物件程式設計的基礎知識,那麼我們今天為什麼要來講這個呢?(因為我也忘了,最近看資料才揭開面紗……  哈哈哈)

  

  好了,直接進入正文。在js的程式設計世界中,萬物皆物件;不管你是陣列還是函式還是物件,都是屬於物件型別;那麼這麼多物件,如何進行管理呢?js中把物件分為例項物件、函式物件、原型物件三大類;

  例項物件:

    通過建構函式(所謂建構函式我們可以簡單理解為進行new操作的函式就是建構函式)所建立的物件都是例項物件;

 

 

var people = new Student();
console.log(people)

 

 

 

  上面程式碼中的people就是一個例項物件,Student 就是一個建構函式;

  

  函式物件:

    函式物件我們可以簡單的理解為函式,因為在js中函式本身就屬於一個物件;而上述程式碼中的Student是一個建構函式,建構函式是一種特殊的函式,建構函式往往都是在例項物件建立後才進行呼叫,作用是對例項物件進行初始化的操作;建構函式和普通函式的區分僅僅只是功能山區分的一個稱呼,體現在JS程式碼中的區別就是new和不new的區別;有new關鍵字就是新建一個建構函式,沒有new關鍵字就是新建一個普通物件;

 

  原型物件:

    原型物件我們可以簡單的理解為原型物件是例項物件和函式物件的父物件,俗稱:爸爸,並且通過例項物件和函式物件都能找到原型物件;

  

  我們先來看看例項物件和函式物件的關係:

  

function people(name){
     this.name=name;
     console.log(this.name);
}

var Student=new people("鹹魚");

 

  上面程式碼中我們可以看到people是建構函式,Student是例項物件,二者之間的關係體現在constructor屬性中;

  

Student.constructor==people;//true

  我們的例項物件物件中是可以通過constructor屬性來訪問到建構函式的,那麼反過來可以實現嗎?

  答案是:不行!!!  但是我們可以變通一下通過instanceof方法來進行檢查

  

Student instanceof people  //true

 

  通過這種方式,我們可以間接的檢查一個物件是否是建構函式的例項物件

 

 

  new關鍵字做了什麼?

    上面講了建構函式和普通函式在JS中沒有什麼太大的區別,所以我們可以想到那麼直接使用函式的話,this的指向肯定是瀏覽器全域性物件window,那麼我們new一下之後,this的指向將會改變為新的例項物件,並且還會將這個新的例項物件返回回來;

    所以我們可以總結new做了什麼:

       1.新建了一個空的函式物件;

       2.改變this的指向,將this的指向改為接收新函式物件的例項物件

          3.返回這個新的例項物件

 

  

   大家來看一個小demo:

    

function people(name){
     this.name=name;
     this.say=function(){
             console.log(this.name);
     }

}

var Student=new people("鹹魚");
var man=new people("張三")
Student.say();  //鹹魚
man.say();    //張三
console.log(Student.say === man.say);  //false

  從上面程式碼中我們可以看到我們有兩個例項物件分別是Student、man;有兩個建構函式people;我們可以看到最後兩個結果進行比較是不相等的,這就說明兩個函式物件不是同一個,這就驗證了上面的new關鍵字,每new一下,就會新建一個空白的函式物件;如果我們要做的事情都一樣,每一次都需要去重新new一個物件,如果是大量重複的做100次同樣的事情呢?難道我們要去new100次嗎?這會給記憶體帶來極大的浪費;

  那現在怎麼解決呢?JS官方都已經替我們想好了辦法,我們可以把這個方法放到原型物件上,每次我們需要用的時候去呼叫就可以了;那麼我們怎麼放到原型物件上呢?

  JS中每一個建構函式都有一個prototype屬性,這個屬性指向的是另一個物件,這個物件就是原型物件;通過這個屬性我們可以直接找到原型物件,當然原型物件也是一個物件,畢竟萬物皆物件嘛;同時原型物件中也有一個constructor屬性,這個屬性也是指向建構函式;

  對了,JS中每一個物件都有一個__proto__屬性(注意是左右兩邊各兩個下劃線),我們可以通過這個物件直接訪問到原型物件;建構函式可以直接通過prototype直接訪問到原型物件,例項物件可以通過__proto__直接訪問到原型物件,這之間就有了聯絡;所以得到結論:例項物件.__proto__===函式物件.prototype,因為這兩者都是訪問到原型物件,共同的爸爸了;

   我們可以將上面的程式碼改造一下:

function people(name){
     this.name=name;
     people.prototype.say=function(){
             console.log(this.name);
     }

}

var Student=new people("鹹魚");
var man=new people("張三")
Student.say();  //鹹魚
man.say();    //張三
console.log(Student.say === man.say);  //true

  這樣我們就可以直接將say方法直接掛到原型物件上面了;

  

  這裡有個圖例可以對照著看,更方便理解:

  

 

 

   

function Student(name){
        this.name=name;
}
var xianyu = new Student('xianyu');
console.log(xianyu.__proto__ === Student.prototype); // true
console.log(xianyu.constructor === Student); // true
console.log(xianyu.constructor === Student.prototype.constructor); // true

    得出的結果如下:

 

 

   所以我們可以總結一下結論:

       1.例項物件通過__proto__和函式物件通過prototype都是能夠訪問到原型物件的;

       2.例項物件通過constructor屬性可以訪問到建構函式,因為例項物件的constructor屬性直接指向建構函式;

               3.原型物件上面的constructor屬性跟例項物件一樣,都是指向其建構函式

 

  原型鏈:

    物件可以通過“.”操作獲取到一個屬性的值,首先會在物件自身開始查詢,如果差不到會到原型物件(__proto__)中去查詢,如果原型物件中還沒有就會把當前得到的原型物件當作例項物件,繼續通過(__proto__)去查詢當前原型物件的原型物件中去找,也就是去爸爸的爸爸那裡找,直到__proto__為null時停止;

    

    上面可能有點拗口,我們換個生活中的例子來理解:就是你有個物件,你丈母孃問你要20W彩禮,你自己拿不出來你沒有(物件本身沒有屬性),丈母孃讓你問你爸爸(原型物件)要,如果你爸爸也沒有,你爸爸就得問你爸爸的爸爸也就是你爺爺要(原型物件的原型物件,這裡的第一個原型物件實質上成了一個例項物件,因為你爸爸在你爺爺那裡永遠是兒子),如果你爺爺也沒有就繼續往上要……如此反覆,實在拿不出來(null),丈母孃大手一揮,拿不出來就不嫁了(直到為null時停止);

 

  原型鏈實現繼承:

    我們知道所有的物件都有個toString()方法,上述程式碼中例項物件xianyu其實也是一個物件,這就是JS中的萬物皆物件的道理,我們沒有給xianyu加任何toString()方法,它是哪裡來的?繼承來的!因為xianyu.__proto__最終指向的是Function原型物件(Function函式物件一直往上查詢原型物件最終是Function原型物件,Object函式物件一直往上查詢原型物件最終是null),因為Function原型物件中有,所以xianyu作為它的例項會繼承上面的方法,這就是JS繼承的本質;

    

    也就是說你爹(原型物件)那有20W,沒花在銀行存著,你(例項物件)也可以理解為你有20W在銀行存著,因為你爹的錢遲早會給你,你爹的錢間接就是你的錢,你要是還存著,你的兒子(例項物件的例項物件)也就間接有了20W;

 

 

    總結:

      1.例項物件可以通過__proto__訪問原型物件,函式物件可以通過prototype訪問原型物件;

      2.原型物件上面的方法一定會繼承給下屬的例項物件,反之如果要給原型物件上新增方法需要通過   函式物件.prototype.方法名  或  例項物件.__proto__.方法名  進行新增;

         3.原型物件實質上也是一個物件,原型物件上面也會有__proto__、constructor等屬性,所以原型物件可以通過  原型物件.__proto__來訪問原型物件的原型物件,也可以通過constructor來知道自己是屬於哪個建構函式上面的例項物件;

&n