淺談javascript中的prototype

本人博客:【www.xiabingbao.comjavascript

在本文中,咱們講解prototype的內容主要由:什麼是prototype,prototype與函數之間的關係,prototype與實例對象之間的關係,使用proto實現一個簡單的繼承。java

1. prototype的簡要介紹

在javascript中,建立的每一個函數天生都自帶着一個prototype屬性。這裏咱們要強調的是:這個prototype屬性是一個指針,指向一個對象,在這裏,咱們稱指向的這個看不到但確實存在的對象爲原型對象。其實能夠用下面一個簡單的例子來講明:微信

var proto = {
    name : 'wenzi',
    age : 25
}
function Person(){

}
Person.prototype = proto;

這樣Person.protptype就指向到了proto,若是還有其餘的引用(A)也指向到了proto,那麼這個引用和Person.prototype就是相等的:A==Person.prototype函數

prototype指向的這個對象是真實存在的,可能不少的同窗對prototype屬性和原型對象有些混淆,咱們在這裏把原型對象叫作大SB大SB與普通對象的一個不一樣之處就是,他也有一個天生的屬性:constructor,這個constructor從新指回到了函數。不過,既然大SB也是一個對象,那麼它也是繼承於Object的,擁有Object全部的方法和屬性,好比toString()等。性能

大SB = {
    constructor : Person,
    say : function(){
        return "hello world";
    }
}

那麼如今咱們就能獲得兩個引用關係:this

大SB = Person.prototype; // 原型對象 = 函數.prototype;
Person = 大SB.constructor(Person.prototype.constructor); // 函數 = 原型對象.constructor(函數.prototype.constructor)spa

img

從運行的代碼中,能夠驗證咱們的觀點,Person.prototype的類型是object,Person.prototype.constructor從新指回到了Person。
其實在實例對象中,也存在着一個屬性指向到大SB中:prototype

var John = new Person();
大SB = John.__proto__;

即 John.__proto__ 和 Person.prototype 指向的是同一個引用:John.__proto__==Person.prototype指針

2. 對象實例和prototype中的屬性

在構造函數能設置屬性和方法,在prototype中也能設置屬性和方法,那new出的對象使用的是哪一個呢?咱們來看一個例子:code

function Person(){
    this.name = "wenzi";
}
Person.prototype.name = "bing";
Person.prototype.say = function(){
    return "hello world";
}
var John = new Person();
alert(John.name);  // "wenzi"
alert(John.say()); // "hello world"

從運行的結果咱們能夠看到,John.name輸出的是構造函數中的屬性值"wenzi",John.say()輸出的是"hello world"。這是由於,當讀取某個對象的屬性值時,會首先在實例對象中進行搜索,若搜索到則直接進行返回;若搜索不到則去原型對象中進行尋找。所以在使用John調用say()方法時是正確的,不會報錯。並且是進行了兩次的搜索。

雖然say()是掛載在prototype上,John也一樣能直接進行訪問。可是,咱們不能直接對John.say進行修改從而影響prototype的say()方法,以下:

John.say = function(){
    return "this has changed";
}
John.say(); // "this has changed"
var Tom = new Person();
Tom.say(); // "hello world"

從運行John.say()的結果來看,say()方法確實發生了變化。可是,當咱們再new出一個新對象Tom時,Tom.say()返回的仍是"hello world",這是爲何呢?

由於,對John.say()進行修改時,不是修改了prototype上的say(),而是給John這個實例對象添加了一個say()方法,從而屏蔽了prototype上的say()方法,由上面的尋找順序咱們能夠知道,若實例對象存在這個方法就直接返回了,再也不去原型對象上尋找。而new出的新對象Tom在調用say()方法時,Tom本身是沒有say()方法的,只能去原型對象上尋找,所以返回的仍是"hello world"。因此,對John.say進行修改時,不是修改了prototype上的say()方法,而是給John添加了實例方法,屏蔽掉了prototype上的方法而已。那如何才能修改prototype上的方法呢,好辦,直接在prototype上修改:

Person.prototype.say = function(){
    return "wenzi's blog";
}

這樣就能修改prototype的say()方法了。

3. 重寫prototype

在上面的例子中,咱們都是大部分給prototype添加屬性或方法,原本prototype指向的是大SB,若咱們給prototype添加屬性或方法時,就是給大SB添加屬性或方法:

Person.prototype.name = "wenzi";

大SB裏其餘的屬性和方法是不受影響的,constructor依然指回到Person。可是,若咱們這樣寫:

Person.prototype = {
    name : "wenzi",
    say : function(){
        return "my name is name"
    }
}

就是對Person的prototype重寫了,讓prototype進行了從新的指向,原本Person.prototype指向的是大SB,但是如今卻指向了CTM,而CTM裏也沒有constructor屬性指回到Person,如果想從新指回到Person,還得手動添加一個constructor屬性:

Person.prototype = {
    constructor : Person, // 從新指回到Person
    name : "wenzi",
    say : function(){
        return "my name is name"
    }
}

這樣就能手動構造出新的原型對象了,new出的實例對象也能使用這個prototype上的屬性和方法。

可是這裏還存在一個問題,若以前已經有new出的實例對象,而後再修改Person.prototype,以前的實例對象是沒法使用新的原型對象(CTM)上的屬性和方法的,由於以前的實例對象的__proto__指向的依然是大SB。所以,請慎重重寫prototype

4. 原型繼承

說到prototype就不得說prototype繼承,咱們經過給prototype上添加屬性和方法,就能使該構造函數全部的實例對象擁有屬性和方法。咱們先來看下面的代碼:

function Father(){
    this.name = "father";
    this.age = 43;
}
Father.prototype.job = "Doctor";
Father.prototype.getName = function(){
    return this.name;
}

function Son(){
    this.name = "son";
}
Son.prototype = new Father(); // Son的prototype指向Father的實例對象
var John = new Son();
for(var k in John){
    console.log(k+' : '+john[k]);
}
/*
    輸出結果:
    name : son
    age : 43
    job : Doctor
    getName : function (){
        return this.name;
    }
*/

從上面的例子中能夠看到,Son的實例對象繼承了Father中全部的屬性和方法。固然,若Son的對象實例中存在的,還依然保留。不過Son原型中的屬性和方法是會被完全覆蓋的。咱們來分析一下這是爲何?

Son的prototype指向的是Father的一個實例,咱們把這拆成兩步:

var father = new Father();
Son.prototype = father;

在實例father中既有name,age屬性,也有Father.prototype中的屬性和方法,咱們對father循環看一下:

for(var k in father){
    console.log(k, father[k]);
}
/*
    name father
    age 43
    job Doctor
    getName Father.getName()
*/

因爲constructor是不可枚舉的類型,在for~in循環裏是輸出不了constructor的,其實father是這樣的:

father = {
    constructor : Father,
    name : father
    age : 43
    job : Doctor
    getName : Father.getName()
}

所以Son的prototype指向的就是上面father的內容。到此,Son的prototype中的constructor的指向也發生了改變,原本Son.prototype是指向到Son的,如今指向到了Father。即徹底重寫了Son的prototype,從新指向了另外一個引用。因此,咱們就能知道爲何對象實例中的屬性可以保存,而prototype中的會完全被刪除了。

若是想給Son添加原型方法或屬性,那隻能是在Son.prototype = new Father();以後進行添加:

Son.prototype = new Father();
Son.prototype.getJob = function(){
    return this.job;
}

這樣就能添加上getJob方法了。

到這裏,會有人問,若是Son.prototype = Father.prototype會怎麼樣呢?咱們剛纔也說了Father.prototype指向的是大SB,而大SB也是一個實例對象,是Object的一個實例,這就至關於:

Son.prototype == 大SB; // 指向了大SB

所以,若是是Son.prototype = Father.prototype的話,那麼Son只能繼承到Father.prototype上的jobgetName(),其餘的屬性是獲取不到的。

5. 總結

本文簡單的介紹了一下prototype,讓咱們能對prototype有個淺顯的認識。固然,博主才疏學淺,文章裏會有不少疏漏和不完善的地方,歡迎你們批評指正。

本人博客:【www.xiabingbao.com

歡迎你們關注個人微信公衆號:wenzichel
wenzichel

相關文章
相關標籤/搜索