深入理解javascript中的原型

原型prototype是javascript中極其重要的概念之一,但也是比較容易引起混淆的地方。我們需要花費一些時間和精力好好理解原型的概念,這對于我們學習javascript是必須的。


原型的概念

真正理解什么是原型是學習原型理論的關(guān)鍵。很多人在此產(chǎn)生了混淆,沒有真正理解,自然后續(xù)疑惑更多。

首先,我們明確原型是一個對象,其次,最重要的是,
** Every function has a prototype property and it contains an object **
這句話就是說,每個函數(shù)都有一個屬性叫做原型,這個屬性指向一個對象。
也就是說,原型是函數(shù)對象的屬性,不是所有對象的屬性,對象經(jīng)過構(gòu)造函數(shù)new出來,那么這個new出來的對象的構(gòu)造函數(shù)有一個屬性叫原型。明確這一點很重要。

** The prototype property is a property that is available to you as soon as you define the function. Its initial value is an "empty" object.
**
每次你定義一個函數(shù)的時候,這個函數(shù)的原型屬性也就被定義出來了,也就可以使用了,如果不對它進行顯示賦值的話,那么它的初始值就是一個空的對象Object。
所以,綜上我們知道我們討論原型的時候,都是基于函數(shù)的,有了一個函數(shù)對象,就有了原型。切記這一點,討論原型,不能脫離了函數(shù),它是原型真正歸屬的地方,** 原型只是函數(shù)的一個屬性 **!

function foo(a,b) {
    return a+b;
}
foo.prototype
foo.constructor

chrome控制臺測試結(jié)果

Paste_Image.png

我們可以看到函數(shù)foo的原型是空對象Object,所有函數(shù)的構(gòu)造函數(shù)都是Function。


使用原型給對象添加方法和屬性

不使用原型,使用構(gòu)造函數(shù)給對象添加屬性和方法的是通過this,像下面這樣。

function Gadget(name, color) {
    this.name = name;
    this.color = color;
    this.whatAreYou = function() {
        return 'I am ' + this.color + '  ' + this.name; 
    }
} 

Gadget是一個構(gòu)造函數(shù),作為一個函數(shù),它有一個屬性,這個屬性是原型,它指向一個對象,目前我們沒有設(shè)置這個屬性,所以它是一個空的對象。
** Adding methods and properties to the prototype property of the constructor
function is another way to add functionality to the objects this constructor produces **
當我們有了原型之后,我們可以給構(gòu)造函數(shù)的原型對象添加屬性和方法來。
像下面這樣

Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
Gadget.prototype.getInfo = function() {
    return 'Rating: ' + this.rating +', price: ' + this.price;
}

給原型添加了屬性和方法后,原型所指的對象也會更新

Paste_Image.png

使用原型對象的屬性和方法

我們使用原型的對象和方法不會在直接在構(gòu)造函數(shù)上使用,而是通過構(gòu)造函數(shù)new出一個對象,那么new出來的對象就會有構(gòu)造函數(shù)原型里的屬性和方法。

Paste_Image.png

這里很容易造成誤解,我們需要強調(diào)newtoy這個new出來的對象是沒有原型的,原型只是函數(shù)對象的一個屬性,newtoy是通過構(gòu)造函數(shù)new出來的對象,所以他不是函數(shù)對象,也沒有prototype屬性,我們在chrome的控制臺里自然也無法訪問他的prototype屬性。
但我們可以通過構(gòu)造函數(shù)訪問。
我們知道每個對象都有constructor屬性,newtoy的constructor屬性就指向Gadget,那么我們通過constructor可以訪問到prototype。

Paste_Image.png

到這里,我們對為什么要通過constructor.protptype訪問屬性應該清楚了。(筆者第一次接觸原型就沒看懂這個),切記,原型是函數(shù)對象的屬性,只有函數(shù)對象才有原型就容易理解了。

原型的實時性

這里特別需要提出,原型是實時的,意思就是原型對象的屬性和方法會實時更新。其實很好理解,javascript中對象是通過引用傳遞的,原型對象只有一份,不是new出一個對象就復制一份,所以我們對原型的操作和更新,會影響到所有的對象。這就是原型對象的實時性。

Paste_Image.png

自身屬性與原型屬性

這里涉及到j(luò)avascript是如何搜索屬性和方法的,javascript會先在對象的自身屬性里尋找,如果找到了就輸出,如果在自身屬性里沒有找到,那么接著到構(gòu)造函數(shù)的原型屬性里去找,如果找到了就輸出,如果沒找到,就null。
所以,如果碰到了自身屬性和原型屬性里有同名屬性,那么根據(jù)javascript尋找屬性的過程,顯然,如果我們直接訪問的話,會得到自身屬性里面的值。

Paste_Image.png

我們加下來做一個小實驗,尋找toString方法是誰的屬性,一步步尋找

Paste_Image.png

通過實驗我們可以發(fā)現(xiàn),原來toString方法是object的原型對象的方法。

isPrototypeOf()

Object的原型里還有這樣一個方法isPrototypeOf(),這個方法可以返回一個特定的對象是不是另一個對象的原型,實際這里不準確,因為我們知道只有函數(shù)對象有原型屬性,普通對象通過構(gòu)造函數(shù)new出來,自動繼承了構(gòu)造的函數(shù)原型的屬性方法。但這個方法是可以直接判斷,而不需要先取出constructor對象再訪問prototype??聪旅娴睦樱?/p>

function Human(name) {
    this.name = name;
}

var monkey = {
    hair:true,
    feeds:'banana',
}

Human.prototype = monkey;

var chi = new Human('chi');
Paste_Image.png

我們知道chi這個對象是沒有原型屬性的,它有的是他的構(gòu)造函數(shù)的原型屬性monkey。但isPrototypeOf直接判斷,實際上是省略了獲取構(gòu)造函數(shù)的過程,搞清楚這里面的區(qū)別。
object還有一個getPrototypeOf方法,基本用法和isPrototype一樣,參考下面的代碼:

Paste_Image.png

神秘的proto鏈接

我們之前訪問對象的原型,都要先取得構(gòu)造函數(shù)然后訪問prototype

chi.constructor.prototype;
newtoy.constructor.prototype;

這樣是不是特別別扭,所以各個瀏覽器一般都會給出一個proto屬性,前后分別有雙下劃線,對象的這個屬性可以直接訪問到構(gòu)造函數(shù)的原型。這就很方便了。所以proto與prototype是有很大區(qū)別的。區(qū)別就在此。proto是實例對象用來直接訪問構(gòu)造函數(shù)的屬性,prototype是函數(shù)對象的原型屬性。

Paste_Image.png
chi.constructor.prototype == chi.__proto__
Paste_Image.png

顯然現(xiàn)在已經(jīng)很容易弄清楚了proto和prototype的區(qū)別了。

原型的陷阱

原型在使用的時候有一個陷阱:
** 在我們完全替換掉原型對象的時候,原型會失去實時性,同時原型的構(gòu)造函數(shù)屬性不可靠,不是理論上應該的值。**
這個陷進說的是什么呢?好像不太明白
舉個例子我們就懂了

function Dog() {
    this.tail = true;
}

var benji = new Dog();
var rusty = new Dog();

Dog.prototype.say = function () {
    return 'Woof!';
};

我們進行測試:

Paste_Image.png

直到這里一切都是正常的
接下來我們將原型對象整個替換掉

Dog.prototype = {
    paws: 4,
    hair: true
};
Paste_Image.png

通過測試我們發(fā)現(xiàn),我們沒法訪問剛剛更新的原型對象,卻能訪問之前的原型對象,這說明沒有實現(xiàn)實時性。

我們繼續(xù)測試

Paste_Image.png

我們發(fā)現(xiàn)這時新建的對象可以訪問更新后的原型,但是構(gòu)造方法又不對了,本來constructor屬性應該指向dog,結(jié)果卻指向了Object。這就是javascript中的原型陷阱。

我們很容易解決這個問題,只要在更新原型對象后面,重新指定構(gòu)造函數(shù)即可。

Dog.prototype.constructor = Dog;
Paste_Image.png

這樣所有就按正常的運行了

** 所以我們切記在替換掉原型對象之后,切記重新設(shè)置constructor.prototype **

小結(jié)

我們大概介紹了原型中容易混淆的問題,主要有以下幾方面:

  • 所有函數(shù)都有一個屬性prototype,這就是我們指的原型,他的初始值是一個空的對象
  • 你可以原型對象添加屬性和方法,甚至直接用另一個對象替換他
  • 當你用構(gòu)造函數(shù)new出一個對象之后,這個對象可以訪問構(gòu)造函數(shù)的原型對象的屬性和方法
  • 對象的自身屬性搜索的優(yōu)先級比原型的屬性要高
  • proto屬性的神秘連接及其同prototype的區(qū)別
  • prototype使用中的陷阱
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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