對象 ECMAScript,一個高度抽象的物件導向語言。它按原始方式處理對象,但當我們需要時,它也可以轉化對象。 對象是只有一個原型對象的屬性集合。這個原型是object或者null 讓我們看一個對象的基本執行個體,對象的原型是通過內建屬性[[Prototype]]引用的。然而專門為了理解原型對象,在圖中我們將會用_<internal-property>_底線:_proto_代替雙括弧。代碼: 1 var foo={2 x:10,3 y:204 }; 我們有了含有兩個顯式的自身屬性和一個隱式的_proto_屬性的結構,這個_proto_屬性是foo原型的一個引用: 這些原型需要什嗎?讓我們思考完原型鏈的概念後來回答這個問題。 原型鏈 原型對象也僅僅是一個簡單的對象,且他們也有自己的原型。如果一個原型對象有一個非null的引用指向它的原型,這樣迴圈下去(譯者註:到null終止)。這就形成所謂的原型鏈。 原型鏈是一個有限長的對象鏈,用來實現繼承和共用屬性的。 考慮這種情況,當我們有兩個僅僅只在一些小的部分存在差異而其他絕大部分都相同的對象。為了設計一個更好的系統,很明顯的,在單一的對象中我們會重用相似功能/代碼,而不用去重複它。在基於類的系統中,這種代碼重用方式叫基於類的繼承-你把相似的功能放在類A中,然後構造從類A繼承而來的類B和類C,他們都有自身的微小變化。 ECMAScript沒有類的概念,然而代碼重用方式也沒有太大不同(儘管在某些方面它比基於類的方式更靈活),是通過原型鏈來實現代碼重用的。這種繼承被稱為仿類繼承(或者為了符合ECMAScript,叫原型鏈繼承)。 類似在例子中用classesA,B和C。在ECMAScript中,你可以建立對象:a,b和c。所以對象a含有對象b和c的相同部分。b和c僅僅包含他們自身的屬性或者方法。 複製代碼 1 var a={ 2 x:10, 3 calculate:function(z){ 4 return this.x+this.y+z 5 } 6 }; 7 var b={ 8 y:20, 9 _proto_:a10 };11 var c={12 y:30,13 _proto_:a14 };15 //調用繼承的方法16 b.calculate(30);//6017 c.calculate(40);//80複製代碼 很簡單吧。我們發現b和c擷取了定義在對象a中的calculate方法。準確的說是通過原型鏈擷取的。 原理很簡單:如果一個屬性或者方法在對象自身中找不到(也就是說對象中沒有一個這樣的自身屬性),然後就試圖在原型鏈中尋找這個屬性/方法。如果這個屬性 在原型中沒有找到,則考慮到這個原型對象的原型中尋找,遞推下去,也就是說貫穿整個原型鏈(在基於類的繼承中是完全一樣的,在解析一個繼承的方法時-遍曆 整個類鏈)。第一個找到的同名屬性/方法被使用。所以,被找到的屬性被稱為繼承屬性。如果遍曆完整個原型鏈,這個屬性還是沒找到,返回 undefined。 注意,在使用繼承方法時,方法中的this值被設定為初始對象(譯者註:調用對象),而不是方法存在的原型對象。也就是說,在上面例子中的this.y是從b和c擷取的,而不是從a擷取。然而,this.x是通過原型鏈機制從a中擷取。 如果一個對象沒有顯式的定義其原型,預設的_proto_值將指向Object.prototype。Object.prototype對象也有自身的_proto_,就是這個原型鏈的最終串連,其值為null。下面這幅圖展示了a,b和c對象的繼承結構: 注意:ES5中標準化了一個基於原型繼承的替代方法Object.create函數: 1 var b=Object.create(a,{y:{value:20}});2 var c=Object.create(a,{y:{value:30}});ES6中標準化了__proto__,可以在對象初始化時使用 經常需要得到有相同或者類似結構(也就是有相同的屬性集合),而其值又不同的一些對象。在這種情況下我們應該使用建構函式在特定模式下來建立對象。 建構函式 除了通過特定模式建立對象之外,建構函式還幹了另外一件有用的事情-為新建立的對象自動化佈建一個原型對象。這個原型對象放在ConstructorFunction.prototype的屬性中。例如我們用建構函式重寫前面例子中的b和c,因此,對象Foo.prototype扮演著a(原型)的角色: 複製代碼 1 //一個建構函式 2 function Foo(y){ 3 //將會通過特定模式建立對象:他們會建立自身的"y"屬性 4 this.y=y; 5 } 6 //"Foo.prototype"存放著新構建對象的原型引用。因此我們可以用它來定義共用/繼承的屬性和方法。和前面的例子一樣,我們有: 7 //可繼承屬性"x" 8 Foo.prototype.x=10; 9 //可繼承方法"calculate"10 Foo.prototype.calculate=function(z){11 return this.y+this.x+z;12 };13 //使用"pattern"Foo來建立我們的b,c對象14 var b=new Foo(20);15 var c=new Foo(30);16 //調用繼承的方法17 b.calculate(30);//6018 c.calculate(40);//8019 //我們展示所期望的引用屬性20 console.log(21 b._proto_===Foo.prototype,//true22 c._proto_===Foo.prototype,//true23 //"Foo.prototype"自動構造了特殊的"constructor"屬性,它是建構函式自身的引用;執行個體b和c可以通過委託和檢測他的建構函式來找到她24 b.constructor===Foo,//true25 c.constructor===Foo,//true26 Foo.prototype.constructor===Foo,//true27 b.calculate===b._proto_.calculate,//true28 b.__proto__.calculate === Foo.prototype.calculate//true29 ); 再一次表明每一個對象都有一個原型。建構函式的_proto_ 為Function.prototype,通過它(譯者註:Function.prototype)的 _proto_屬性再次引用到Object.prototype。再次重申,Foo.prototype僅僅只是Foo的一個顯式屬性指向b和c對象的原型。 這個主題的完整和詳細的解釋在這個系列的第七節能找到。這裡你會找到不同OOP語言的範例和理論以及和ECMAScript的比較。Chapter 7.2. OOP. ECMAScript implementation致力於ECMAScript的OOP。 現在,我們瞭解了對象的基礎知識,然我們看看在ECMAScript中代碼執行時如何?的。這就是所謂的執行內容堆棧,這裡面的每一部分也可以抽象的表示為一個對象。是的,在ECMAScript的操作中處處充斥著對象的理念。 執行內容堆棧 有三種類型的ECMAScript代碼:全域代碼,函數代碼和eval代碼。每一種代碼都在其執行內容中運行。只有一個全域上下文,可能有許多函數和eval執行內容的執行個體。每次調用函數,進入函數的執行內容並執行器函數代碼。每次調用eval函數,進入eval的執行內容並運行它的代碼。 記住一個函數可能產生無限多個上下文環境,因為每調用一次函數(即使函數遞迴的調用自己)都會產生一個新的上下文: 複製代碼1 function foo(bar){2 //調用同一個函數,由於不同的上下文狀態(比如"bar"參數值)產生不同的上下文3 foo(10);4 foo(20);5 foo(30);6 }複製代碼 一個執行內容可能啟用另一個上下文,例如。一個函數調用另一個函數(或者全域上下文調用全域函數),等等。邏輯上這是有一個堆棧來實現的,就是所謂的執行內容堆棧。啟用了另一個內容相關的上下文叫做調用者。被啟用的上下文叫做被調用者。同時被調用者也可以是其他被調用者的調用者(例如,一個函數在全域上下文調用,又調用了它的一些內建函式)。 當一個調用者啟用(調用)一個被調用者,調用者暫停它的上下文並傳遞控制流程進入被調用者。被調用者被壓入棧中並成為當前(啟用)的執行內容。當被調用者 的上下文結束後,控制流程回到調用者中,運行調用者的上下文處理(此時他可能有啟用了其他上下文),直到結束。被調用者可能由於錯誤返回或調出。一個拋出二 沒有沒捕獲的錯誤會跳出(從堆棧中彈出)一個或多個上下文。 例如。所有的ECMAScript程式運行時可以表示為一個執行內容堆棧(EC),棧的頂部是一個啟用上下文: 程式開始時便進入全域執行內容,棧的第一個元素也是最底部的元素。然後全域代碼進行了一些初始化,建立了必要的對象和函數。在執行全域上下文時,它的代 碼會啟用其他(已經建立)函數,並進入到他們的執行內容,向執行內容堆棧匯總推入新的元素,一直下去。當初始化結束後,啟動並執行系統會等待一些事件(例 如使用者點擊滑鼠),這些事件會啟用一些函數並進入一個新的執行內容。 在中,有函數上下文EC1和全域上下文Global EC,當全域上下文進入和跳出EC1時堆棧變化如下: 這準確的描述了ECMAScript中的運行程式是如何管理代碼的執行的。關於ECMAScript中執行內容更詳細的資訊請閱讀Chapter 1. Execution context. 執行內容 執行內容可以抽象的表示為一個簡單的對象。每一個執行內容都有一些必要的屬性(可以叫做上下文狀態)去追蹤它對應代碼的執行進程。展示了一個內容相關的結構: Figure 6. An execution context structure. 除了這三個必須的屬性(變數對象,this值和範圍鏈),執行內容依據具體實現可能有其他附加狀態。讓我們詳細的考慮內容相關的這些重要屬性。