這篇文章講解的很清楚,因此轉載到自己的博客上,如有侵權,請聯系我刪除。
原文地址:https://www.jianshu.com/p/eff5e130fc28
原型prototype是javascript中極其重要的概念之一,但也是比較容易引起混淆的地方。我們需要花費一些時間和精力好好理解原型的概念,這對於我們學習javascript是必須的。
原型的概念
真正理解什么是原型是學習原型理論的關鍵。很多人在此產生了混淆,沒有真正理解,自然后續疑惑更多。
首先,我們明確原型是一個對象,其次,最重要的是,
** Every function has a prototype property and it contains an object **
這句話就是說,每個函數都有一個屬性叫做原型,這個屬性指向一個對象。
也就是說,原型是函數對象的屬性,不是所有對象的屬性,對象經過構造函數new出來,那么這個new出來的對象的構造函數有一個屬性叫原型。明確這一點很重要。
** 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.
**
每次你定義一個函數的時候,這個函數的原型屬性也就被定義出來了,也就可以使用了,如果不對它進行顯示賦值的話,那么它的初始值就是一個空的對象Object。
所以,綜上我們知道我們討論原型的時候,都是基於函數的,有了一個函數對象,就有了原型。切記這一點,討論原型,不能脫離了函數,它是原型真正歸屬的地方,** 原型只是函數的一個屬性 **!
function foo(a,b) { return a+b; } foo.prototype foo.constructor
chrome控制台測試結果

我們可以看到函數foo的原型是空對象Object,所有函數的構造函數都是Function。
使用原型給對象添加方法和屬性
不使用原型,使用構造函數給對象添加屬性和方法的是通過this,像下面這樣。
function Gadget(name, color) { this.name = name; this.color = color; this.whatAreYou = function() { return 'I am ' + this.color + ' ' + this.name; } }
Gadget是一個構造函數,作為一個函數,它有一個屬性,這個屬性是原型,它指向一個對象,目前我們沒有設置這個屬性,所以它是一個空的對象。
** Adding methods and properties to the prototype property of the constructor
function is another way to add functionality to the objects this constructor produces **
當我們有了原型之后,我們可以給構造函數的原型對象添加屬性和方法來。
像下面這樣
Gadget.prototype.price = 100; Gadget.prototype.rating = 3; Gadget.prototype.getInfo = function() { return 'Rating: ' + this.rating +', price: ' + this.price; }
給原型添加了屬性和方法后,原型所指的對象也會更新

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

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

到這里,我們對為什么要通過constructor.protptype訪問屬性應該清楚了。(筆者第一次接觸原型就沒看懂這個),切記,原型是函數對象的屬性,只有函數對象才有原型就容易理解了。
原型的實時性
這里特別需要提出,原型是實時的,意思就是原型對象的屬性和方法會實時更新。其實很好理解,javascript中對象是通過引用傳遞的,原型對象只有一份,不是new出一個對象就復制一份,所以我們對原型的操作和更新,會影響到所有的對象。這就是原型對象的實時性。

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

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

通過實驗我們可以發現,原來toString方法是object的原型對象的方法。
isPrototypeOf()
Object的原型里還有這樣一個方法isPrototypeOf(),這個方法可以返回一個特定的對象是不是另一個對象的原型,實際這里不准確,因為我們知道只有函數對象有原型屬性,普通對象通過構造函數new出來,自動繼承了構造的函數原型的屬性方法。但這個方法是可以直接判斷,而不需要先取出constructor對象再訪問prototype。看下面的例子:
function Human(name) { this.name = name; } var monkey = { hair:true, feeds:'banana', } Human.prototype = monkey; var chi = new Human('chi');

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

神秘的proto鏈接
我們之前訪問對象的原型,都要先取得構造函數然后訪問prototype
chi.constructor.prototype; newtoy.constructor.prototype;
這樣是不是特別別扭,所以各個瀏覽器一般都會給出一個proto屬性,前后分別有雙下划線,對象的這個屬性可以直接訪問到構造函數的原型。這就很方便了。所以proto與prototype是有很大區別的。區別就在此。proto是實例對象用來直接訪問構造函數的屬性,prototype是函數對象的原型屬性。

chi.constructor.prototype == chi.__proto__

顯然現在已經很容易弄清楚了proto和prototype的區別了。
原型的陷阱
原型在使用的時候有一個陷阱:
** 在我們完全替換掉原型對象的時候,原型會失去實時性,同時原型的構造函數屬性不可靠,不是理論上應該的值。**
這個陷進說的是什么呢?好像不太明白
舉個例子我們就懂了
function Dog() { this.tail = true; } var benji = new Dog(); var rusty = new Dog(); Dog.prototype.say = function () { return 'Woof!'; };
我們進行測試:

直到這里一切都是正常的
接下來我們將原型對象整個替換掉
Dog.prototype = {
paws: 4,
hair: true };

通過測試我們發現,我們沒法訪問剛剛更新的原型對象,卻能訪問之前的原型對象,這說明沒有實現實時性。
我們繼續測試

我們發現這時新建的對象可以訪問更新后的原型,但是構造方法又不對了,本來constructor屬性應該指向dog,結果卻指向了Object。這就是javascript中的原型陷阱。
我們很容易解決這個問題,只要在更新原型對象后面,重新指定構造函數即可。
Dog.prototype.constructor = Dog;

這樣所有就按正常的運行了
** 所以我們切記在替換掉原型對象之后,切記重新設置constructor.prototype **
小結
我們大概介紹了原型中容易混淆的問題,主要有以下幾方面:
- 所有函數都有一個屬性prototype,這就是我們指的原型,他的初始值是一個空的對象
- 你可以原型對象添加屬性和方法,甚至直接用另一個對象替換他
- 當你用構造函數new出一個對象之后,這個對象可以訪問構造函數的原型對象的屬性和方法
- 對象的自身屬性搜索的優先級比原型的屬性要高
- proto屬性的神秘連接及其同prototype的區別
- prototype使用中的陷阱
作者:六尺帳篷
鏈接:https://www.jianshu.com/p/eff5e130fc28
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。