原型(prototype)是每一個JavaScript開發人員必須理解的基本概念,本文的目標是通俗而又詳細地解釋JavaScript的原型。若是你讀完這篇博文之後仍是不理解JavaScript的原型,請將你的問題寫在下面的評論裏,我本人會回答全部的問題。 javascript
爲了理解JavaScript中的原型,你必須理解JavaScript的對象。若是你對對象還不熟悉,你須要閱讀個人文章JavaScript Objects in Detail(譯文:詳解JavaScript對象)。並且你要知道屬性就是函數中定義的變量。 java
在JavaScript中有兩個互相之間有關聯的原型的概念: 編程
1.首先,每個JavaScript函數有一個原型屬性,當你須要實現繼承的時候你就給這個原型屬性附加屬性和方法。注意這個原型屬性是不能夠枚舉的:它在for/in循環中是不可獲取的。可是FireFox和大多數版本的Safari和Chrome瀏覽器有一個__proto__「僞」屬性(一種選擇方式)容許你訪問對象原型的屬性。你可能歷來沒有用過這個_proto__僞屬性,但你得知道它的存在而且它是在某些瀏覽器中訪問對象的原型屬性的一種簡單的方法。 數組
對象原型主要用於繼承:你爲對象的原型屬性增長方法和屬性,使這些原型和屬性存在於該函數的實例。 瀏覽器
如下是一個簡單的使用原型屬性繼承的例子(後面還有更多關於繼承的內容): 函數
function PrintStuff (myDocuments) { this.documents = myDocuments; } //咱們爲PrintStuff的原型屬性增長方法print (),這樣的話其餘實例(對象)能夠繼承這個方法 PrintStuff.prototype.print = function () { console.log(this.documents); } //用構造函數PrintStuff ()建立一個新的對象,由此讓這個新對象繼承PrintStuff 的屬性和方法。 var newObj = new PrintStuff ("I am a new Object and I can print."); // newObj繼承了函數PrintStuff全部的屬性和方法,包括方法print。如今newObj能夠直接調用print,即便咱們歷來沒有爲它定義過方法print()。 newObj.print (); //I am a new Object and I can print.
2. 第二個關於JavaScript原型的概念是原型特性。把原型特性想成該對象的一個性質;這個性質代表了該對象的「父母」。簡而言之:對象的原型特性能夠當作是對象的「父母」--該對象得到它屬性的地方。對象特性經常被稱爲原型對象,而且它是在你建立對象時就自動創建的。對此的解釋是:每個對象從某個其餘的對象那裏繼承屬性,而這裏的「某個其餘的對象」就是該對象的原型特性或者「父母」。(你能夠把原型特性想象成血緣關係或者父母)。在上面例子的代碼中,newObj的原型是PrintStuff.prototype。 學習
注意:全部的對象都有特性,就和對象屬性有本身的特性同樣。對象特性是原型、類和擴展特性。這些是咱們在第二個例子中要討論的原型特性。 ui
還有一點須要注意, 「僞」屬性__proto__包含一個對象的原型對象(被該對象繼承方法和屬性的父對象)。 this
//重要提示 //構造函數 //在咱們繼續往下閱讀以前,讓咱們來簡單的考查一下構造函數。構造函數是一個用來初始化新對象的函數,而且使用新的關鍵字來調用構造函數。 //例如: function Account () { } //這是利用構造函數Account來建立對象userAccount var userAccount = new Account (); 並且,全部繼承自另外一個對象的對象,也繼承了那個對象的構造函數屬性。而這個構造函數屬性就是一個保存或者指向該對象的構造函數的屬性(和任何變量同樣)。 //本例的構造函數是Object () var myObj = new Object (); //而若是你以後想要知道myObj的構造函數: console.log(myObj.constructor); // Object() // 另外一個例子: Account ()是構造函數 var userAccount = new Account (); //查看對象userAccount的構造函數 console.log(userAccount.constructor); // Account()
用new Object()或對象式建立的對象的原型特性 .net
全部用對象式或者構造函數Object建立的對象都繼承自Object.prototype。所以Object.prototype是全部用Object()或者{}所建立的對象的原型特性(或原型對象)。Object.prototype自己沒有從其餘任何對象那裏繼承任何的方法或者屬性。
// 對象userAccount 繼承自Object 而且所以它的原型特性就是Object.prototype. var userAccount = new Object (); // 這個聲明用了對象式來創造對象userAccount;該對象userAccount繼承自Object;所以,就和上面的對象userAccount同樣,它的原型特性是Object.prototype。 var userAccount = {name: 「Mike」}
用構造函數所建立的對象的原型特性
用新關鍵字以及任何一種非Object()的構造函數所建立的對象,從該構造函數中得到它們的構造函數。
例如:
function Account () { } var userAccount = new Account () // 用構造函數Account ()初始化userAccount而且所以它的原型特性(原型對象)就是 Account.prototype。
相似的,任何數組,好比var myArray = new Array (),從Array.prototype得到原型而且繼承Array.prototype的屬性。
因此,當對象被建立時有兩種通用的方式來創建對象的原型特性:
1.若是對象是使用對象式(var newObj = {})建立的,那麼它從Object.prototype繼承屬性,而且咱們說它的原型對象(或者原型特性)是Object.prototype。
2.若是對象是使用構造函數,好比 new Object ()或者new Fruit ()或者new Array ()或者 new Anything ()建立的,那麼它繼承自構造函數 (Object (), Fruit (), Array (), or Anything ())。例如,用一個函數,好比Fruit (),每次咱們建立一個新的水果的實例(var aFruit = new Fruit ()),那麼該新實例的原型就來自於構造函數Fruit,也就是 Fruit.prototype。任何用new Array ()所建立的對象都會將Array.prototype做爲它的原型。任何用構造函數Object(Obj (), 好比 var anObj = new Object() )建立的對象繼承自Object.prototype。
還有一點你須要知道的,在ECMAScript 5中,你能夠用一個容許你指定新對象的原型的方法Object.create()來建立對象。咱們會在後續的文章中學習ECMAScript 5。
原型爲何重要以及什麼時候使用原型?
在JavaScript中原型有兩種重要的用途,就像前文中提到的那樣:
1.原型屬性:基於原型的繼承
在JavaScript中原型之因此重要是由於JavaScript沒有(大多數面向對象的語言全部的)經典的基於類的繼承,所以JavaScript全部的繼承是經過原型屬性來實現的。JavaScript有一套基於原型繼承的機制。繼承是一種能讓對象(或者是其它語言中的類)繼承其它對象(或類)的屬性和方法的編程規範。在JavaScript中,經過原型來實現繼承。例如,你能夠建立一個Fruit函數(也就是對象,由於全部JavaScript中的函數都是對象)而且給這個Fruit的原型屬性添加屬性和方法,那麼全部Fruit函數的實例會繼承Fruit所有的屬性和方法。
JavaScript中的繼承示例:
function Plant () { this.country = "Mexico"; this.isOrganic = true; } //把方法showNameAndColor添加到Plant原型屬性 Plant.prototype.showNameAndColor =function () { console.log("I am a " + this.name + " and my color is " + this.color); } // 把方法amIOrganic添加到Plant原型屬性 Plant.prototype.amIOrganic = function () { if (this.isOrganic) console.log("I am organic, Baby!"); } function Fruit (fruitName, fruitColor) { this.name = fruitName; this.color = fruitColor; } //將Fruit的原型設爲Plant的構造函數,所以繼承了Plant.prototype所有的方法和屬性 Fruit.prototype = new Plant (); // 用構造函數Fruit建立一個新的aBanana var aBanana = new Fruit ("Banana", "Yellow"); // 這裏aBanana用了來自aBanana對象原型Fruit.prototype的name屬性: console.log(aBanana.name); // Banana //用來自Fruit對象原型Plant.prototype的方法showNameAndColor。該aBanana對象繼承了來自函數Plant和Fruit的所有屬性和方法 console.log(aBanana.showNameAndColor()); // I am a Banana and my color is yellow.
注意到,儘管方法showNameAndColor是在對象Plant.prototype的原型鏈上定義的,可是此方法仍是被對象aBanana所繼承。
實際上,任何使用構造函數Fruit ()的對象,都將繼承Fruit.prototype所有的屬性和方法以及來自Fruit的原型Plant.prototype的所有的屬性和方法。這就是JavaScript中實現繼承的主要方式以及原型鏈在這一過程當中所扮演的整合角色。
2.原型特性:獲取對象的屬性
原型對於獲取對象的方法和屬性也是很重要的。原型特性(或原型對象)是那些可繼承的屬性的「父母」對象,這些可繼承的屬性本來就是爲這些「父母」對象定義的。這就有點相似於你能夠從你的父親--他是你的「原型父母」,那裏繼承姓。若是咱們想知道你的姓是從哪裏來的,咱們會先看看是不是你本身給本身取了這個姓;若是不是,咱們會繼續查看是否你是從你的父親那裏繼承了這個姓。若是這個姓不是你父親的,那麼咱們會繼續查看你父親的父親的姓(你父親的原型父親)。
與之相似的,若是你想要獲取一個對象的原型,你將直接從該對象的屬性開始尋找。若是JS運行時不能再那裏找到該屬性,那麼它會去該對象的原型--該對象獲得屬性的地方,去查看這個屬性。
若是在對象的原型中沒有發現該屬性,那麼對於該屬性的搜尋會轉移到對象的原型的原型(對象的父親的父親--爺爺)那裏去。就這樣一直持續到沒有原型爲止(沒有更多的曾祖父;沒有更多有遺傳來的血緣關係)。這其實就是原型鏈:從對象的原型到對象原型的原型不斷向上的一條鏈。而且JavaScript就用這條原型鏈來搜尋對象的屬性和方法。
若是某個屬性在它的整條原型鏈上的任何一個對象的原型中均不存在,那麼這個屬性就是不存在而且會返回undefined。
這種原型鏈機制本質上和咱們上面討論的基於原型的繼承是同樣的概念,只是在這裏咱們更注重於JavaScript如何經過對象原型獲取對象的屬性和方法。
這個例子演示了對象的原型對象的原型鏈:
var myFriends = {name: "Pete"}; //爲了找到下面的屬性name,搜尋會直接從對象myFriends開始,而且會馬上找到屬性name,由於咱們爲對象myFriends定義了屬性name。這個能夠被想像成有一條連接的原型鏈。 console.log(myFriends.name); //在這個例子中,將會從對象myFriends開始搜尋方法toString (),可是由於咱們歷來沒有爲對象myFriends建立過方法toString,編譯器會接着去myFriends的原型(被myFriends繼承屬性的那個對象)搜尋。 //而且由於全部用對象式建立的對象都繼承自Object.prototype,方法toString將在 Object.prototype中被發現--關於全部繼承自Object.prototype的屬性,請看下面的重要提示 myFriends.toString ();
重要提示
全部對象都會繼承的Object.prototype的屬性
在JavaScript中全部對象的屬性和方法繼承自Object.prototype。這些繼承來的屬性和方法有構造函數,hasOwnProperty (), isPrototypeOf (), propertyIsEnumerable (), toLocaleString (), toString (), and valueOf ()。ECMAScript 5中還新增了四種訪問Object.prototype的方法。
下面是另外一個原型鏈的例子:
function People () { this.superstar = "Michael Jackson"; } // 爲People原型定義屬性"athlete"以便"athlete" 能夠被全部使用構造函數People () 的對象所訪問。 People.prototype.athlete = "Tiger Woods"; var famousPerson = new People (); famousPerson.superstar = "Steve Jobs"; //對於superstar的搜尋將首先查看對象famousPerson是否有屬性superstar,而由於就是在那裏定義的這個屬性,這就是須要用到的屬性。由於咱們已經爲對象famousPerson重複定義鏈famousPerson的屬性superstar,因此對於superstar的搜尋就不會在原型鏈上繼續上升。 console.log (famousPerson.superstar); // Steve Jobs // 注意在ECMAScript 5中你能夠將屬性設置爲只讀,這樣的話你就不能像咱們剛纔那樣重複定義該屬性。 //這裏展現了來自famousPerson原型(People.prototype)的屬性,由於屬性athlete沒有爲對象famousPerson自己所定義。 console.log (famousPerson.athlete); // Tiger Woods //在這個例子中,在原型鏈上向上搜尋而且在Object.prototype中找到了方法toString,這個方法來自對象Fruit的繼承--像咱們前面提到的那樣,全部的對象最終繼承自Object.prototype console.log (famousPerson.toString()); // [object Object]
全部已經創建的構造函數 (Array (), Number (), String (), etc.)都是由構造函數Object所建立的,所以它們的原型是Object.prototype。