1. 程式人生 > >一個例子讓你徹底明白原型物件和原型鏈

一個例子讓你徹底明白原型物件和原型鏈

  function Person () {
        this.name = 'John';
    }
    var person = new Person();
    Person.prototype.say = function() {
        console.log('Hello,' + this.name);
    };
    person.say();//Hello,John
上述程式碼非常簡單,Person原型物件定義了公共的say方法,雖然此舉在構造例項之後出現,但因為原型方法在呼叫之前已經宣告,因此之後的每個例項將都擁有該方法。從這個簡單的例子裡,我們可以得出:

原型物件的用途是為每個例項物件儲存共享的方法和屬性,它僅僅是一個普通物件而已。並且所有的例項是共享同一個原型物件,因此有別於例項方法或屬性,原型物件僅有一份。所有就會有如下等式成立:

 person.say == new Person().say

可能我們也會這麼寫

   function Person () {
        this.name = 'John';
    }
    var person = new Person();
    Person.prototype = {
        say: function() {
            console.log('Hello,'
+ this.name); } }; person.say();//person.say is not a function
很不幸,person.say方法沒有找到,所以報錯了。其實這樣寫的初衷是好的:因為如果想在原型物件上新增更多的屬性和方法,我們不得不每次都要寫一行Person.prototype,還不如提煉成一個Object來的直接。但是此例子巧就巧在構造例項物件操作是在新增原型方法之前,這樣就會造成一個問題:

當var person = new Person()時,Person.prototype為:Person {}(當然了,內部還有constructor屬性),即Person.prototype指向一個空的物件{}。而對於例項person而言,其內部有一個原型鏈指標proto,該指標指向了Person.prototype指向的物件,即{}。接下來重置了Person的原型物件,使其指向了另外一個物件,即
Object {say: function}

這時person.proto的指向還是沒有變,它指向的{}物件裡面是沒有say方法的,因為報錯。
從這個現象我們可以得出:
在js中,物件在呼叫一個方法時會首先在自身裡尋找是否有該方法,若沒有,則去原型鏈上去尋找,依次層層遞進,這裡的原型鏈就是例項物件的_proto_屬性

 function Person () {
        this.name = 'John';
    }
    Person.prototype = {
        say: function() {
            console.log('Hello,' + this.name);
        }
    };
    var person = new Person();
    person.say();//person.say is not a function

這裡寫圖片描述
其實,只需要明白原型物件的結構即可:

 Function.prototype = {
        constructor : Function,
        __proto__ : parent prototype,
        some prototype properties: ...
    };

總結:函式的原型物件constructor預設指向函式本身,原型物件除了有原型屬性外,為了實現繼承,還有一個原型鏈指標_proto_,該指標指向上一層的原型物件,而上一層的原型物件的結構依然類似,這樣利用_proto_一直指向Object的原型物件上,而Object的原型物件用Object.prototype._proto_ = null表示原型鏈的最頂端,如此變形成了javascript的原型鏈繼承,同時也解釋了為什麼所有的javascript物件都具有Object的基本方法。