JavaScript之原型與原型鏈

點(diǎn)擊此處訪問(wèn)我的github了解更多詳情

在基于類的語(yǔ)言中,對(duì)象是類的實(shí)例,并且類可以從另一個(gè)類繼承,如Java;JavaScript則是一門(mén)基于原型的語(yǔ)言,以原型鏈實(shí)現(xiàn)繼承,其對(duì)象可以直接繼承自另一對(duì)象,此篇詳細(xì)闡述JavaScript之原型與原型鏈。

原型

Javascript中創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,這個(gè)對(duì)象的作用即是包含可以由特定類型實(shí)例共享的屬性和方法,這個(gè)對(duì)象就是函數(shù)的原型對(duì)象。

默認(rèn)情況,所有的原型對(duì)象都會(huì)有一個(gè)constructor屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。

調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,實(shí)例的內(nèi)部將包含一個(gè)指向構(gòu)造函數(shù)原型對(duì)象的指針,在ECMA-262中定義此指針為[[Prototype]],并不能被顯式的訪問(wèn)到,而在Firefox,Safari和Chrome中每個(gè)對(duì)象上有一個(gè)__proto__屬性。

__proto__顯示的是實(shí)例與構(gòu)造函數(shù)原型對(duì)象間的關(guān)系,而非實(shí)例與構(gòu)造函數(shù)間的關(guān)系。


    function Animal(name) {
        this.name = name;
    }
    Animal.prototype.age = 3;
    Animal.prototype.getName = function() {
        return this.name;
    };
    var animal = new Animal('Dog');
    console.log(animal.getName());  //輸出Dog

以上代碼中,Animal為構(gòu)造函數(shù),Animal.prototype指向構(gòu)造函數(shù)原型對(duì)象;原型對(duì)象中constructor屬性指向構(gòu)造函數(shù),即Animal.prototype.constructor指向Animal;在構(gòu)造函數(shù)實(shí)例中,其__proto__屬性指向構(gòu)造函數(shù)原型對(duì)象。

    1. 原型與實(shí)例屬性訪問(wèn)
    
    function Animal(name) {
        this.name = name;
    }
    Animal.prototype.age = 3;
    Animal.prototype.getName = function() {
        return this.name;
    };
    var animal1 = new Animal('Dog');
    var animal2 = new Animal('Cat');
    console.log(animal1.age); //輸出3--原型屬性
    console.log(animal2.age); //輸出3--原型屬性
    animal2.age = 4;
    console.log(animal2.age); //輸出4--實(shí)例屬性
    delete animal2.age;
    console.log(animal2.age); //輸出3--原型屬性

獲取某對(duì)象屬性時(shí),首先從該對(duì)象實(shí)例本身開(kāi)始,若該實(shí)例中找到該屬性,則返回該屬性值;若未找到,則繼續(xù)查找該實(shí)例對(duì)象指向的構(gòu)造函數(shù)原型對(duì)象,若找到則返回值。

(1) hasOwnProperty()方法
hasOwnProperty()方法可以檢測(cè)一個(gè)屬性是在原型上還是實(shí)例上,只有當(dāng)給定屬性為對(duì)象實(shí)例屬性時(shí)返回true:


    function Animal(name) {
        this.name = name;
    }
    Animal.prototype.age = 3;
    Animal.prototype.getName = function() {
        return this.name;
    };
    var animal1 = new Animal('Dog');
    var animal2 = new Animal('Cat');
    console.log(animal1.hasOwnProperty('name'));  //true
    console.log(animal1.hasOwnProperty('age'));  //false
    animal1.age = 4;
    console.log(animal1.hasOwnProperty('age'));  //true

(2) 原型與in
單獨(dú)使用in操作符時(shí),只要通過(guò)對(duì)象能訪問(wèn)到給定屬性即返回true,無(wú)論屬性是在實(shí)例還是原型上定義:


    function Animal(name) {
        this.name = name;
    }
    Animal.prototype.age = 3;
    Animal.prototype.getName = function() {
        return this.name;
    };
    var animal1 = new Animal('Dog');
    var animal2 = new Animal('Cat');
    console.log(animal1.hasOwnProperty('name'));  //true
    console.log('name' in animal1); //true
    console.log(animal1.hasOwnProperty('age'));  //false
    console.log('age' in animal1); //true
    console.log('eat' in animal1); //false
  • 2.實(shí)例與原型的引用關(guān)系

實(shí)例在創(chuàng)建時(shí),其內(nèi)部指針[[Prototype]](在上文提到的__proto__)指向構(gòu)造函數(shù)原型對(duì)象,存在引用關(guān)系。


    function Animal(name) {
        this.name = name;
    }
    var animal = new Animal('Dog');
    Animal.prototype.age = 3;
    Animal.prototype.getName = function() {
        return this.name;
    };
    animal.getName(); //輸出Dog

可以看到雖然實(shí)例早于原型中g(shù)etName方法創(chuàng)建,但其依然可以調(diào)用該方法,因?yàn)閷?shí)例通過(guò)引用指向原型對(duì)象,原型對(duì)象變化自然能被實(shí)例訪問(wèn)到。然而,對(duì)于如下這種情況:


    function Animal(name) {
        this.name = name;
    }
    var animal = new Animal('Dog');
    Animal.prototype = {
        constructor: Animal,  //設(shè)置constructor值為Animal,確保constructor屬性返回適當(dāng)值,詳細(xì)見(jiàn)上文關(guān)于constructor屬性說(shuō)明
        age: 3,
        getName: function() {
            return this.name;
        }
    };
    var animal2 = new Animal('Cat');
    console.log(animal2.getName());  //輸出Cat
    animal.getName();  //TypeError: undefined is not a function

此處,首先創(chuàng)建了一個(gè)實(shí)例,隨后重寫(xiě)了構(gòu)造函數(shù)原型對(duì)象,再在實(shí)例上調(diào)用getName方法時(shí)報(bào)錯(cuò);而重寫(xiě)構(gòu)造函數(shù)原型對(duì)象之后創(chuàng)建的實(shí)例調(diào)用getName方法可以正常返回對(duì)應(yīng)值。這是因?yàn)橹貙?xiě)原型對(duì)象之后,之前創(chuàng)建的實(shí)例引用的依然是之前的原型對(duì)象,其與現(xiàn)有原型之間并無(wú)聯(lián)系,而之后創(chuàng)建的實(shí)例[[Prototype]]指針引用的就是現(xiàn)有原型。

  • 3.原型對(duì)象存在問(wèn)題

    function Animal(name) {
        this.name = name;
    }
    Animal.prototype.age = 3;
    Animal.prototype.partner = ['one'];
    Animal.prototype.getName = function() {
        return this.name;
    };
    var animal1 = new Animal('Dog');
    var animal2 = new Animal('Cat');
    console.log(animal1.partner); //輸出["one"] 
    console.log(animal2.partner); //輸出["one"] 
    animal1.partner.push('two');
    console.log(animal1.partner); //輸出["one", "two"] 
    console.log(animal2.partner); //輸出["one", "two"] 
    console.log(animal1.partner === animal2.partner); //輸出true

可以看到原型中所有屬性都被實(shí)例共享,特別是對(duì)于引用類型的屬性,如上的partner屬性。

  • 4.默認(rèn)原型

所有引用類型默認(rèn)都繼承自O(shè)bject,所有構(gòu)造函數(shù)默認(rèn)原型都是Object的實(shí)例,默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針,指向Object.prototype。

  • 5.構(gòu)造函數(shù),原型與實(shí)例的關(guān)系

每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,由prototype屬性指向;原型對(duì)象包含一個(gè)指向構(gòu)造函數(shù)的指針constructor;而實(shí)例都包含一個(gè)指向構(gòu)造函數(shù)原型對(duì)象的內(nèi)部指針[[Prototype]]。

原型鏈

每一個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,當(dāng)我們讓某一原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例,此時(shí)該原型對(duì)象就包含一個(gè)指針,該指針指向這一構(gòu)造函數(shù)的原型對(duì)象,該指針指向的原型對(duì)象中包含一個(gè)指向這一構(gòu)造函數(shù)的指針,同樣我們可以令該指針指向的原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例,如此遞進(jìn),則形成一條實(shí)例與原型的鏈條,即原型鏈。


    function Parent() {
        this.name = 'parent';
    };
    Parent.prototype.getName = function() {
        return this.name;
    };
    function Child() {
        this.childname = 'child';
    }
    Child.prototype = new Parent();
    Child.prototype.getChildName = function() {
        return this.childname;
    };
    var child = new Child();
    console.log(child.getName()); //輸出parent
    console.log(child.getChildName());  //輸出child

如上代碼,child實(shí)例指向Child原型,Child原型等于Parent實(shí)例,即指向Parent原型。可見(jiàn)本質(zhì)即是以一個(gè)新類型(構(gòu)造函數(shù))的實(shí)例重寫(xiě)原型對(duì)象,形成原型鏈。

  • 原型鏈問(wèn)題

既然原型對(duì)象存在問(wèn)題,那么原型鏈自然也繼承了這個(gè)問(wèn)題,即原型屬性會(huì)被所有實(shí)例共享,對(duì)于原型屬性的改變將影響所有實(shí)例,而在原型鏈中,由于某一原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例,實(shí)例受影響,也就導(dǎo)致其他原型對(duì)象也受影響。

原型與原型鏈?zhǔn)荍avaScript實(shí)現(xiàn)繼承的基礎(chǔ),下一篇詳細(xì)介紹JavaScript之繼承。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容