DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> javascript--prototype機制
javascript--prototype機制
編輯:JavaScript基礎知識     

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

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

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

 

基於prototype機制實現"繼承"

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

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實現“構造函數調用”產生的新對象,並傳入兩個參數。

  一般我們通過new來調用函數,會發生以下的操作:

  1、創建一個全新的對象;

  2、這個新對象會被執行prototyp鏈接;

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

  4、如果函數沒有返回對象,那麼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》

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved