javascript--prototype機制

  在Javascript對象有一個特殊的prototype內置屬性,它其實就是對於其餘對象的引用javascript

  當咱們試圖訪問一個對象下的某個屬性的時候,會在JS引擎觸發一個GET的操做,首先會查找這個對象是否存在這個屬性,若是沒有找的話,則繼續在prototype關聯的對象上查找,以此類推。若是在後者上也沒有找到的話,繼續查找的prototype,這一系列的連接就被稱爲原型鏈。java

  在javascript中對象都是可使用tostring(),valueOf()方法,函數都是可使用call(),apply(),由於普通對象都是經過prototype鏈最終指向了內置的Object.prototype,而函數都是指向了Function.prototype。app

 

基於prototype機制實現"繼承"函數

  在javascipt中實現繼承背後是依靠prototype機制的,javascript與其餘面向類的語言不一樣,它是沒有類做爲對象的抽象模式去實現繼承(在ES6中是有class這個關鍵字的,也是prototype機制的一種語法糖),可是咱們能夠在javascript中寫出模仿類的代碼,這裏來看看常見的構造函數模式+原型模式:this

function Animal(name,color){
        this.name = name;
        this.color = color;
    }
    Animal.prototype.type = "動物";
    Animal.prototype.msg = function(){
        alert( "I am "+this.name+" my color is "+this.color);
    }
    var cat = new Animal("cat","black");
    console.log(cat.name);              //cat
    console.log(cat.type);                 //動物
    console.log(cat.constructor === Animal);        //true

  

  函數Animal就能夠看作是一個'類',裏面定義了name和color屬性,主要看下劃線的代碼,cat是經過new關鍵字將Animal實現「構造函數調用」產生的新對象,並傳入兩個參數。spa

  通常咱們經過new來調用函數,會發生如下的操做:prototype

  一、建立一個全新的對象;日誌

  二、這個新對象會被執行prototyp連接;對象

  三、這個新對象會被綁定函數調用的this上;blog

  四、若是函數沒有返回對象,那麼new表達式中的函數調用會自動返回這個新對象

 

  能夠看到,使用getPrototypeOf獲取cat對象的原型是全等於Animal.prototype的,cat也如預期那樣繼承了Animal的name、color屬性,固然也繼承了type屬性和msg方法了,這段代碼展現了兩個技巧:

  ·this.name = name給每一個對象都添加了name屬性,有點像類實例封裝的數據值;

  ·Animal.prototype.type和Animal.prototype.msg會給Animal.prototype對象添加一個屬性和方法,如今cat.type和cat.msg()是能夠正常工做的,背後是依靠prototype機制,由於在建立cat對象的時候,內部的prototype都會關聯到Animal.prototype上了,當cat中沒法找到type(或者msg)的時候,就會到Animal.prototype上找到。

 

  它是函數,爲何會被咱們認爲是"類"?

  在javascript中有個約定俗成的慣例,就是「構造函數」(或者叫類函數)首字母須要大寫,其實這個Animal函數和其餘普通的函數是沒有任何區別的,只是咱們在調用這個函數的時候加上一個new的關鍵字,把這個函數調用變成一個「構造函數調用」。

  大概就是這個函數都是要經過new來調用了吧。

   其實還有個更重要的緣由,來看一段代碼:

function Animal(name,color){
        this.name = name;
        this.color = color;
    }
    Animal.prototype.type = "動物";
    Animal.prototype.msg = function(){
        alert( "I am "+this.name+" my color is "+this.color);
    }
    var cat = new Animal("cat","black");
    console.log(cat.constructor === Animal);    //true

  新建立的對象cat裏面有個constructor屬性,它是全等於函數Animal,看起來就像是cat對象是由Animal函數構造出來的

  但事實是cat.consructor引用一樣被關聯到Animal.prototype上,Animal.prototype.constructor默認指向了Animal,來看一段代碼驗證一下這個觀點

  

function Foo(){
            name : 'x'
    }
Foo.prototype = { } var foo = new Foo(); console.log(foo.constructor === Foo);    //false console.log(foo.constructor === Object); //true

  上面代碼定義了一個函數Foo同時修改了原型對象prototype,foo是經過new調用Foo產生的新對象,可是foo.constructor並不等於Foo,緣由是foo上並不存在constructor屬性,因此它會委託原型鏈到Foo.prototpe上查找,可是這個對象上也沒有constructor屬性,因此會繼續在原型鏈找,直到原型鏈的終點--Object.prototype,這個對象有constructor屬性,並指向了內置的Object()函數。

  foo是由Foo構造出來這個觀點是錯誤的。

 

  原型繼承和類繼承的區別?

  在面向類的語言中,類能夠被複制屢次,就像模具製做東西同樣,實例一個類的時候就會將類的行爲複製一份到物理對象中,可是在javascript中,是不存在這種複製機制的,在建立實例對象時,它們的prototype會被關聯到「構造函數」原型對象上。拿以前的栗子來講就是cat對象的prototype被關聯到Animal.prototype上,當咱們想訪問cat.type時,就會在原型鏈上去查找,而不是另外複製一份出來保存到新對象上

  來看一段代碼:

function Foo(){
                name : 'x'
        }
        Foo.prototype = {
                friends : ['y','z','c']
        }
        var foo = new Foo();   
        console.log(foo.friends);                 //["y", "z", "c"]
        Foo.prototype.friends.push('a');          //向Foo.prototype.friends添加一個a
        console.log(foo.friends);                 //["y", "z", "c", "a"]     

 

  繼承的打開方式

  在javascript中有許許多多的繼承方式:原型繼承、借用構造函數、寄生繼承等等。

  假設咱們如今有兩個「類」,SuperType和SubType,咱們想要SubType去繼承SuperType,就要修改Subtype的原型了,常見的寫法有:

  ·SubType.prototype = SuperType.protype

  ·Subtype.protype = new SuperType()

   安利另外一種寫法,來看代碼:

function Foo(name){
                this.name = name;
        }
        Foo.prototype.sayName = function(){
                console.log(this.name);
        }
        function Bar(name,label){
                Foo.call(this,name);            //借用Foo函數
                this.label = label;
        }
        Bar.prototype = Object.create(Foo.prototype);       //建立Bar.prototype對象並關聯到Foo.prototype上
        //在Bar.prototype添加一個方法
        Bar.prototype.sayLabel = function(){
                console.log(this.label);
        }
    
        var a = new Bar('x','person');
        a.sayName();      //x
        a.sayLabel();       //person

  這段代碼的核心語句就是下劃線的Bar.prototype = Object.create(Foo.prototype),調用Object.create會憑空建立一個新對象並把新對象內部的prototype關聯到你指定的對象上。

  爲何要安利這種寫法呢?

  ·Bar.prototype = Foo.prototype並不會建立一個關聯到Bar.prototype的新對象,這樣只是讓Bar.prototype直接引用Foo.prototype對象,當咱們試圖在Bar.prototype添加(或者修改)屬性或者方法時,就至關於修改了Foo.prototype對象自己了,這會影響到後面建立的後代,顯然不是咱們想要的

  ·Bar.prototype = new Foo()的確會建立一個關聯到Bar.prototype的新對象,這樣寫法會調用Foo函數,假如Foo裏面有一些討厭的操做:好比向this添加屬性、寫日誌、修改狀態等等也會影響到後代,這也不是咱們想看到的

  所以,須要建立一個合適的關聯對象,經過使用 Object.create

  在ES6新增了一個輔助函數Object.setPrototypeOf,它能夠修改對象的prototype關聯,用法以下:

  Object.setPrototypeOf(Bar.prototype,Foo.prototype);

 

  結束語:這篇博文醞釀了挺久,關於原型鏈能夠展開來細講的點不少,也很凌亂,一直想把這些點串起來,也是本身對於這些知識點理解不夠透徹、掌握不熟練,若是文中出現錯誤的地方,歡迎你們指正,若是這篇博文有些許幫助,點下右下角的推薦哈:)

 

參考資料:《你不知道的javascript》

相關文章
相關標籤/搜索