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

  javascript原型和原型鏈是js中的重點也是難點,理論上來講應該是屬於面向對象編程的基礎知識,那麼咱們今天爲何要來說這個呢?(由於我也忘了,最近看資料才揭開面紗……  哈哈哈)
javascript

  

  好了,直接進入正文。在js的編程世界中,萬物皆對象;無論你是數組仍是函數仍是對象,都是屬於對象類型;那麼這麼多對象,如何進行管理呢?js中把對象分爲實例對象、函數對象、原型對象三大類;java

  實例對象:編程

    經過構造函數(所謂構造函數咱們能夠簡單理解爲進行new操做的函數就是構造函數)所建立的對象都是實例對象;數組

 

 

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

 

 

 

  上面代碼中的people就是一個實例對象,Student 就是一個構造函數;瀏覽器

  

  函數對象:函數

    函數對象咱們能夠簡單的理解爲函數,由於在js中函數自己就屬於一個對象;而上述代碼中的Student是一個構造函數,構造函數是一種特殊的函數,構造函數每每都是在實例對象建立後才進行調用,做用是對實例對象進行初始化的操做;構造函數和普通函數的區分僅僅只是功能區分的一個稱呼,體如今JS代碼中的區別就是new和不new的區別;有new關鍵字就是新建一個構造函數,沒有new關鍵字就是新建一個普通函數;this

 

  原型對象:spa

    原型對象咱們能夠簡單的理解爲原型對象是實例對象和函數對象的父對象,俗稱:爸爸,而且經過實例對象和函數對象都能找到原型對象;prototype

  

  咱們先來看看實例對象和函數對象的關係:code

  

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來知道本身是屬於哪一個構造函數上面的實例對象;

相關文章
相關標籤/搜索