適合的讀者:有經驗的開發員,專業前端人員。
原作者: Dmitry A. Soshnikov
發布時間: 2010-09-02
原文:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
參考1:http://ued.ctrip.com/blog/?p=2795
參考2:http://www.cnblogs.com/ifishing/archive/2010/12/08/1900594.html
主要是綜合了上面2位高手的中文翻譯,將兩篇文章的精華部分都結合在一起了。
我們首先來看一下對象[Object]的概念,這也是ECMASript中最基本的概念。
對象Object
ECMAScript是一門高度抽象的物件導向(object-oriented)語言,用以處理Objects對象. 當然,也有基本類型,但是必要時,也需要轉換成object對象來用。
An object is a collection of properties and has a single prototype object. The prototype may be either an object or the null value.
Object是一個屬性的集合,並且都擁有一個單獨的原型對象[prototype object]. 這個原型對象[prototype object]可以是一個object或者null值。
複製代碼
讓我們來舉一個基本Object的例子,首先我們要清楚,一個Object的prototype是一個內部的[[prototype]]屬性的引用。
不過一般來說,我們會使用__<內部屬性名稱>__ 底線來代替雙括弧,例如__proto__(這是某些指令碼引擎比如SpiderMonkey的對於原型概念的具體實現,儘管並非標準)。 複製代碼 代碼如下:var foo = {
x: 10,
y: 20
};
上述代碼foo對象有兩個顯式的屬性[explicit own properties]和一個內建隱式的 __proto__ 屬性[implicit __proto__ property],指向foo的原型。
圖 1. 一個含有原型的基本對象
為什麼需要原型呢,讓我們考慮 原型鏈 的概念來回答這個問題。
原型鏈(Prototype chain)
原型對象也是普通的對象,並且也有可能有自己的原型,如果一個原型對象的原型不為null的話,我們就稱之為原型鏈(prototype chain)。
A prototype chain is a finite chain of objects which is used to implemented inheritance and shared properties.原型鏈是一個由對象組成的有限對象鏈由於實現繼承和共用屬性。
想象一個這種情況,2個對象,大部分內容都一樣,只有一小部分不一樣,很明顯,在一個好的設計模式中,我們會需要重用那部分相同的,而不是在每個對象中重複定義那些相同的方法或者屬性。在基於類[class-based]的系統中,這些重用部分被稱為類的繼承 – 相同的部分放入class A,然後class B和class C從A繼承,並且可以聲明擁有各自的獨特的東西。
ECMAScript沒有類的概念。但是,重用[reuse]這個理念沒什麼不同(某些方面,甚至比class-更加靈活),可以由prototype chain原型鏈來實現。這種繼承被稱為delegation based inheritance-基於繼承的委託,或者更通俗一些,叫做原型繼承。
類似於類”A”,”B”,”C”,在ECMAScript中尼建立對象類”a”,”b”,”c”,相應地, 對象“a” 擁有對象“b”和”c”的共同部分。同時對象“b”和”c”只包含它們自己的附加屬性或方法。
複製代碼 代碼如下:var a = { x: 10, calculate: function (z) { return this.x + this.y + z }}; var b = { y: 20, __proto__: a}; var c = { y: 30, __proto__: a}; // 調用繼承過來的方法b.calculate(30); // 60c.calculate(40); // 80
這樣看上去是不是很簡單啦。b和c可以使用a中定義的calculate方法,這就是有原型鏈來[prototype chain]實現的。
原理很簡單:如果在對象b中找不到calculate方法(也就是對象b中沒有這個calculate屬性), 那麼就會沿著原型鏈開始找。如果這個calculate方法在b的prototype中沒有找到,那麼就會沿著原型鏈找到a的prototype,一直遍曆完整個原型鏈。記住,一旦找到,就返回第一個找到的屬性或者方法。因此,第一個找到的屬性成為繼承屬性。如果遍曆完整個原型鏈,仍然沒有找到,那麼就會返回undefined。
注意一點,this這個值在一個繼承機制中,仍然是指向它原本屬於的對象,而不是從原型鏈上找到它時,它所屬於的對象。例如,以上的例子,this.y是從b和c中擷取的,而不是a。當然,你也發現了this.x是從a取的,因為是通過原型鏈機制找到的。
如果一個對象的prototype沒有顯示的聲明過或定義過,那麼__prototype__的預設值就是object.prototype, 而object.prototype也會有一個__prototype__, 這個就是原型鏈的終點了,被設定為null。
下面的圖示就是表示了上述a,b,c的繼承關係
圖 2. 原型鏈
原型鏈通常將會在這樣的情況下使用:對象擁有 相同或相似的狀態結構(same or similar state structure) (即相同的屬性集合)與 不同的狀態值(different state values)。在這種情況下,我們可以使用 建構函式(Constructor) 在 特定模式(specified pattern) 下建立對象。
建構函式(Constructor)
除了建立對象,建構函式(constructor) 還做了另一件有用的事情—自動為建立的新對象設定了原型對象(prototype object) 。原型對象存放於 ConstructorFunction.prototype 屬性中。
例如,我們重寫之前例子,使用建構函式建立對象“b”和“c”,那麼對象”a”則扮演了“Foo.prototype”這個角色:
複製代碼 代碼如下:// 建構函式
function Foo(y) {
// 建構函式將會以特定模式建立對象:被建立的對象都會有"y"屬性
this.y = y;
}
// "Foo.prototype"存放了建立對象的原型引用
// 所以我們可以將之用於定義繼承和共用屬性或方法
// 所以,和上例一樣,我們有了如下代碼:
// 繼承屬性"x"
Foo.prototype.x = 10;
// 繼承方法"calculate"
Foo.prototype.calculate = function (z) {
return this.x + this.y + z;
};
// 使用foo模式建立 "b" and "c"
var b = new Foo(20);
var c = new Foo(30);
// 調用繼承的方法
b.calculate(30); // 60
c.calculate(40); // 80
// 讓我們看看是否使用了預期的屬性
console.log(
b.__proto__ === Foo.prototype, // true
c.__proto__ === Foo.prototype, // true
// "Foo.prototype"自動建立了一個特殊的屬性"constructor"
// 指向a的建構函式本身
// 執行個體"b"和"c"可以通過授權找到它並用以檢測自己的建構函式
b.constructor === Foo, // true
c.constructor === Foo, // true
Foo.prototype.constructor === Foo // true
b.calculate === b.__proto__.calculate, // true
b.__proto__.calculate === Foo.prototype.calculate // true
);
上述代碼可表示為如下的關係:
圖 3. 建構函式與對象之間的關係
上述圖示可以看出,每一個object都有一個prototype. 建構函式Foo也擁有自己的__proto__, 也就是Function.prototype, 而Function.prototype的__proto__指向了Object.prototype. 重申一遍,Foo.prototype只是一個顯式的屬性,也就是b和c的__proto__屬性。
這個問題完整和詳細的解釋可以在大叔即將翻譯的第18、19兩章找到。有兩個部分:物件導向編程.一般理論(OOP. The general theory),描述了不同的物件導向的範式與風格(OOP paradigms and stylistics),以及與ECMAScript的比較, 物件導向編程.ECMAScript實現(OOP. ECMAScript implementation), 專門講述了ECMAScript中的物件導向編程。
現在,我們已經瞭解了基本的object原理,那麼我們接下去來看看ECMAScript裡面的程式執行環境[runtime program execution]. 這就是通常稱為的“執行內容堆棧”[execution context stack]。每一個元素都可以抽象的理解為object。你也許發現了,沒錯,在ECMAScript中,幾乎處處都能看到object的身影。
執行內容棧(Execution Context Stack)
在ECMASscript中的代碼有三種類型:global, function和eval。
每一種代碼的執行都需要依賴自身的上下文。當然global的上下文可能涵蓋了很多的function和eval的執行個體。函數的每一次調用,都會進入函數執行中的上下文,並且來計算函數中變數等的值。eval函數的每一次執行,也會進入eval執行中的上下文,判斷應該從何處擷取變數的值。
注意,一個function可能產生無限的上下文環境,因為一個函數的調用(甚至遞迴)都產生了一個新的上下文環境。
複製代碼 代碼如下:function foo(bar) {}
// 調用相同的function,每次都會產生3個不同的上下文
//(包含不同的狀態,例如參數bar的值)
foo(10);
foo(20);
foo(30);
一個執行內容可以啟用另一個上下文,就好比一個函數調用了另一個函數(或者全域的上下文調用了一個全域函數),然後一層一層調用下去。邏輯上來說,這種實現方式是棧,我們可以稱之為上下文堆棧。
啟用其它內容相關的某個上下文被稱為 調用者(caller) 。被啟用的上下文被稱為被調用者(callee) 。被調用者同時也可能是調用者(比如一個在全域上下文中被調用的函數調用某些自身的內部方法)。
當一個caller啟用了一個callee,那麼這個caller就會暫停它自身的執行,然後將控制權交給這個callee. 於是這個callee被放入堆棧,稱為進行中的上下文[running/active execution context]. 當這個callee的上下文結束之後,會把控制權再次交給它的caller,然後caller會在剛才暫停地方繼續執行。在這個caller結束之後,會繼續觸發其他的上下文。一個callee可以用返回(return)或者拋出異常(exception)來結束自身的上下文。
如,所有的ECMAScript的程式執行都可以看做是一個執行內容堆棧[execution context (EC) stack]。堆棧的頂部就是處於啟用狀態的上下文。
圖 4. 執行內容棧
當一段程式開始時,會先進入全域執行內容環境[global execution context], 這個也是堆棧中最底部的元素。此全域程式會開始初始化,初始化產生必要的對象[objects]和函數[functions]. 在此全域上下文執行的過程中,它可能會啟用一些方法(當然是已經初始化過的),然後進入他們的上下文環境,然後將新的元素壓入堆棧。在這些初始化都結束之後,這個系統會等待一些事件(例如使用者的滑鼠點擊等),會觸發一些方法,然後進入一個新的上下文環境。
見圖5,有一個函數上下文“EC1″和一個全域上下文“Global EC”,展現了從“Global EC”進入和退出“EC1″時棧的變化:
圖 5. 執行內容棧的變化
ECMAScript運行時系統就是這樣管理代碼的執行。
關於ECMAScript執行內容棧的內容請查閱本系列教程的第11章執行內容(Execution context)。
如上所述,棧中每一個執行內容可以表示為一個對象。讓我們看看內容物件的結構以及執行其代碼所需的 狀態(state) 。
執行內容(Execution Context)
一個執行的上下文可以抽象的理解為object。每一個執行的上下文都有一系列的屬性(我們稱為上下文狀態),他們用來追蹤關聯代碼的執行進度。這個圖示就是一個context的結構。
圖 6. 上下文結構
除了這3個所需要的屬性(變數對象(variable object),this指標(this value),範圍鏈(scope chain) ),執行內容根據具體實現還可以具有任意額外屬性。接著,讓我們仔細來看看這三個屬性。
變數對象(Variable Object)
A variable object is a scope of data related with the execution context.
It's a special object associated with the context and which stores variables and function declarations are being defined within the context.
變數對象(variable object) 是與執行內容相關的 資料範圍(scope of data) 。
它是與上下文關聯的特殊對象,用於儲存被定義在上下文中的 變數(variables) 和 函式宣告(function declarations) 。
複製代碼
注意:函數運算式[function expression](而不是函式宣告[function declarations,區別請參考本系列第2章])是不包含在VO[variable object]裡面的。
變數對象(Variable Object)是一個抽象的概念,不同的上下文中,它表示使用不同的object。例如,在global全域上下文中,變數對象也是全域對象自身[global object]。(這就是我們可以通過全域對象的屬性來指向全域變數)。
讓我們看看下面例子中的全域執行內容情況:
複製代碼 代碼如下:var foo = 10;
function bar() {} // // 函式宣告
(function baz() {}); // 函數運算式
console.log(
this.foo == foo, // true
window.bar == bar // true
);
console.log(baz); // 引用錯誤,baz沒有被定義
全域上下文中的變數對象(VO)會有如下屬性:
圖 7. 全域變數對象
如上所示,函數“baz”如果作為函數運算式則不被不被包含於變數對象。這就是在函數外部嘗試訪問產生引用錯誤(ReferenceError) 的原因。請注意,ECMAScript和其他語言相比(比如C/C++),僅有函數能夠建立新的範圍。在函數內部定義的變數與內建函式,在外部非直接可見並且不汙染全域對象。使用 eval 的時候,我們同樣會使用一個新的(eval建立)執行內容。eval會使用全域變數對象或調用者的變數對象(eval的調用來源)。
那函數以及自身的變數對象又是怎樣的呢?在一個函數上下文中,變數對象被表示為使用中的物件(activation object)。
使用中的物件(activation object)
當函數被調用者啟用,這個特殊的使用中的物件(activation object) 就被建立了。它包含普通參數(formal parameters) 與特殊參數(arguments)對象(具有索引屬性的參數映射表)。使用中的物件在函數上下文中作為變數對象使用。
即:函數的變數對象保持不變,但除去儲存變數與函式宣告之外,還包含以及特殊對象arguments 。
考慮下面的情況:
複製代碼 代碼如下:function foo(x, y) {
var z = 30;
function bar() {} // 函式宣告
(function baz() {}); // 函數運算式
}
foo(10, 20);
“foo”函數內容相關的下一個啟用物件(AO)如所示:
圖 8. 啟用物件
同樣道理,function expression不在AO的行列。
對於這個AO的詳細內容可以通過本系列教程第9章找到。
我們接下去要講到的是第三個主要對象。眾所周知,在ECMAScript中,我們會用到內建函式[inner functions],在這些內建函式中,我們可能會引用它的父函數變數,或者全域的變數。我們把這些變數對象成為上下文範圍對象[scope object of the context]. 類似於上面討論的原型鏈[prototype chain],我們在這裡稱為範圍鏈[scope chain]。
範圍鏈(Scope Chains)
A scope chain is a list of objects that are searched for identifiers appear in the code of the context.
範圍鏈是一個 對象列表(list of objects) ,用以檢索上下文代碼中出現的 標識符(identifiers) 。
複製代碼
範圍鏈的原理和原型鏈很類似,如果這個變數在自己的範圍中沒有,那麼它會尋找父級的,直到最頂層。
標示符[Identifiers]可以理解為變數名稱、函式宣告和普通參數。例如,當一個函數在自身函數體內需要引用一個變數,但是這個變數並沒有在函數內部聲明(或者也不是某個參數名),那麼這個變數就可以稱為自由變數[free variable]。那麼我們搜尋這些自由變數就需要用到範圍鏈。
在一般情況下,一個範圍鏈包括父級變數對象(variable object)(範圍鏈的頂部)、函數自身變數VO和使用中的物件(activation object)。不過,有些情況下也會包含其它的對象,例如在執行期間,動態加入範圍鏈中的—例如with或者catch語句。[譯註:with-objects指的是with語句,產生的臨時範圍對象;catch-clauses指的是catch從句,如catch(e),這會產生異常對象,導致範圍變更]。
當尋找標識符的時候,會從範圍鏈的使用中的物件部分開始尋找,然後(如果標識符沒有在使用中的物件中找到)尋找範圍鏈的頂部,迴圈往複,就像範圍鏈那樣。
複製代碼 代碼如下:var x = 10;
(function foo() {
var y = 20;
(function bar() {
var z = 30;
// "x"和"y"是自由變數
// 會在範圍鏈的下一個對象中找到(函數”bar”的互動對象之後)
console.log(x + y + z);
})();
})();
我們假設範圍鏈的對象聯動是通過一個叫做__parent__的屬性,它是指向範圍鏈的下一個對象。這可以在Rhino Code中測試一下這種流程,這種技術也確實在ES5環境中實現了(有一個稱為outer連結).當然也可以用一個簡單的資料來類比這個模型。使用__parent__的概念,我們可以把上面的代碼示範成如下的情況。(因此,父級變數是被存在函數的[[Scope]]屬性中的)。
圖 9. 範圍鏈
在代碼執行過程中,如果使用with或者catch語句就會改變範圍鏈。而這些對象都是一些簡單對象,他們也會有原型鏈。這樣的話,範圍鏈會從兩個維度來搜尋。
- 首先在原本的範圍鏈
- 每一個連結點的範圍的鏈(如果這個連結點是有prototype的話)
我們再看下面這個例子:
複製代碼 代碼如下:Object.prototype.x = 10;
var w = 20;
var y = 30;
// 在SpiderMonkey全域對象裡
// 例如,全域內容相關的變數對象是從"Object.prototype"繼承到的
// 所以我們可以得到“沒有聲明的全域變數”
// 因為可以從原型鏈中擷取
console.log(x); // 10
(function foo() {
// "foo" 是局部變數
var w = 40;
var x = 100;
// "x" 可以從"Object.prototype"得到,注意值是10哦
// 因為{z: 50}是從它那裡繼承的
with ({z: 50}) {
console.log(w, x, y , z); // 40, 10, 30, 50
}
// 在"with"對象從範圍鏈刪除之後
// x又可以從foo的上下文中得到了,注意這次值又回到了100哦
// "w" 也是局部變數
console.log(x, w); // 100, 40
// 在瀏覽器裡
// 我們可以通過如下語句來得到全域的w值
console.log(window.w); // 20
})();
我們就會有如下結構圖示。這表示,在我們去搜尋__parent__之前,首先會去__proto__的連結中。
圖 10. with增大的範圍鏈
注意,不是所有的全域對象都是由Object.prototype繼承而來的。上述圖示的情況可以在SpiderMonkey中測試。
只要所有外部函數的變數對象都存在,那麼從內建函式引用外部資料則沒有特別之處——我們只要遍曆範圍鏈表,尋找所需變數。然而,如上文所提及,當一個上下文終止之後,其狀態與自身將會被 銷毀(destroyed) ,同時內建函式將會從外部函數中返回。此外,這個返回的函數之後可能會在其他的上下文中被啟用,那麼如果一個之前被終止的含有一些自由變數的上下文又被啟用將會怎樣?通常來說,解決這個問題的概念在ECMAScript中與範圍鏈直接相關,被稱為 (詞法)閉包((lexical) closure)。
閉包(Closures)
在ECMAScript中,函數是“第一類”對象。這個名詞意味著函數可以作為參數被傳遞給其他函數使用 (在這種情況下,函數被稱為“funargs”——“functional arguments”的縮寫[譯註:這裡不知翻譯為泛函參數是否恰當])。接收“funargs”的函數被稱之為 高階函數(higher-order functions) ,或者更接近數學概念的話,被稱為 運算子(operators) 。其他函數的運行時也會返回函數,這些返回的函數被稱為 function valued 函數 (有 functional value 的函數)。
“funargs”與“functional values”有兩個概念上的問題,這兩個子問題被稱為“Funarg problem” (“泛函參數問題”)。要準確解決泛函參數問題,需要引入 閉包(closures) 到的概念。讓我們仔細描述這兩個問題(我們可以見到,在ECMAScript中使用了函數的[[Scope]]屬性來解決這個問題)。
“funarg problem”的一個子問題是“upward funarg problem”[譯註:或許可以翻譯為:向上尋找的函數參數問題]。當一個函數從其他函數返回到外部的時候,這個問題將會出現。要能夠在外部內容結束時,進入外部內容的變數,內建函式 在建立的時候(at creation moment) 需要將之儲存進[[Scope]]屬性的父元素的範圍中。然後當函數被啟用時,內容相關的範圍鏈表現為啟用物件與[[Scope]]屬性的組合(事實上,可以在見到):
Scope chain = Activation object + [[Scope]]
範圍鏈 = 使用中的物件 + [[Scope]]
請注意,最主要的事情是——函數在被建立時儲存外部範圍,是因為這個 被儲存的範圍鏈(saved scope chain) 將會在未來的函數調用中用於變數尋找。
複製代碼 代碼如下:function foo() {
var x = 10;
return function bar() {
console.log(x);
};
}
// "foo"返回的也是一個function
// 並且這個返回的function可以隨意使用內部的變數x
var returnedFunction = foo();
// 全域變數 "x"
var x = 20;
// 支援返回的function
returnedFunction(); // 結果是10而不是20
這種形式的範圍稱為靜態範圍[static/lexical scope]。上面的x變數就是在函數bar的[[Scope]]中搜尋到的。理論上來說,也會有動態範圍[dynamic scope], 也就是上述的x被解釋為20,而不是10. 但是EMCAScript不使用動態範圍。
“funarg problem”的另一個類型就是自上而下[”downward funarg problem”].在這種情況下,父級的上下會存在,但是在判斷一個變數值的時候會有多義性。也就是,這個變數究竟應該使用哪個範圍。是在函數建立時的範圍呢,還是在執行時的範圍呢?為了避免這種多義性,可以採用閉包,也就是使用靜態範圍。
請看下面的例子:
複製代碼 代碼如下:// 全域變數 "x"
var x = 10;
// 全域function
function foo() {
console.log(x);
}
(function (funArg) {
// 局部變數 "x"
var x = 20;
// 這不會有歧義
// 因為我們使用"foo"函數的[[Scope]]裡儲存的全域變數"x",
// 並不是caller範圍的"x"
funArg(); // 10, 而不是20
})(foo); // 將foo作為一個"funarg"傳遞下去
從上述的情況,我們似乎可以斷定,在語言中,使用靜態範圍是閉包的一個強制性要求。不過,在某些語言中,會提供動態和靜態範圍的結合,可以允許開發員選擇哪一種範圍。但是在ECMAScript中,只採用了靜態範圍。所以ECMAScript完全支援使用[[Scope]]的屬性。我們可以給閉包得出如下定義:
A closure is a combination of a code block (in ECMAScript this is a function) and statically/lexically saved all parent scopes.
Thus, via these saved scopes a function may easily refer free variables.
閉包是一系列代碼塊(在ECMAScript中是函數),並且靜態儲存所有父級的範圍。通過這些儲存的範圍來搜尋到函數中的自由變數。
複製代碼
請注意,因為每一個普通函數在建立時儲存了[[Scope]],理論上,ECMAScript中所有函數都是閉包。
還有一個很重要的點,幾個函數可能含有相同的父級範圍(這是一個很普遍的情況,例如有好幾個內部或者全域的函數)。在這種情況下,在[[Scope]]中存在的變數是會共用的。一個閉包中變數的變化,也會影響另一個閉包的。 複製代碼 代碼如下:function baz() {
var x = 1;
return {
foo: function foo() { return ++x; },
bar: function bar() { return --x; }
};
}
var closures = baz();
console.log(
closures.foo(), // 2
closures.bar() // 1
);
上述代碼可以用這張圖來表示:
圖 11. 共用的[[Scope]]
在某個迴圈中建立多個函數時,會引發一個困惑。如果在建立的函數中使用迴圈變數(如”k”),那麼所有的函數都使用同樣的迴圈變數,導致一些程式員經常會得不到預期值。現在清楚為什麼會產生如此問題了——因為所有函數共用同一個[[Scope]],其中迴圈變數為最後一次複賦值。
複製代碼 代碼如下:var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2
有一些用以解決這類問題的技術。其中一種技巧是在範圍鏈中提供一個額外的對象,比如增加一個函數:
複製代碼 代碼如下:var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function (x) {
return function () {
alert(x);
};
})(k); // 將k當做參數傳遞進去
}
// 結果正確
data[0](); // 0
data[1](); // 1
data[2](); // 2
閉包理論的深入研究與具體實踐可以在本系列教程第16章閉包(Closures)中找到。如果想得到關於範圍鏈的更多資訊,可以參照本系列教程第14章範圍鏈(Scope chain)。
下一章節將會討論一個執行內容的最後一個屬性——this指標的概念。
This指標
A this value is a special object which is related with the execution context.
Therefore, it may be named as a context object (i.e. an object in which context the execution context is activated).
this適合執行的上下文環境息息相關的一個特殊對象。因此,它也可以稱為內容物件[context object](啟用執行內容的上下文)。
複製代碼
任何對象都可以作為內容相關的this值。我想再次澄清對與ECMAScript中,與執行內容相關的一些描述——特別是this的誤解。通常,this 被錯誤地,描述為變數對象的屬性。最近比如在這本書中就發現了(儘管書中提及this的那一章還不錯)。 請牢記:
a this value is a property of the execution context, but not a property of the variable object.
this是執行內容環境的一個屬性,而不是某個變數對象的屬性
複製代碼
這個特點很重要,因為和變數不同,this是沒有一個類似搜尋變數的過程。當你在代碼中使用了this,這個 this的值就直接從執行的上下文中擷取了,而不會從範圍鏈中搜尋。this的值只取決中進入上下文時的情況。
順便說一句,和ECMAScript不同,Python有一個self的參數,和this的情況差不多,但是可以在執行過程中被改變。在ECMAScript中,是不可以給this賦值的,因為,還是那句話,this不是變數。
在global context(全域上下文)中,this的值就是指全域這個對象,這就意味著,this值就是這個變數本身。 複製代碼 代碼如下:var x = 10;
console.log(
x, // 10
this.x, // 10
window.x // 10
);
在函數上下文[function context]中,this會可能會根據每次的函數調用而成為不同的值.this會由每一次caller提供,caller是通過調用運算式[call expression]產生的(也就是這個函數如何被啟用調用的)。例如,下面的例子中foo就是一個callee,在全域上下文中被啟用。下面的例子就表明了不同的caller引起this的不同。 複製代碼 代碼如下:// "foo"函數裡的alert沒有改變
// 但每次啟用調用的時候this是不同的
function foo() {
alert(this);
}
// caller 啟用 "foo"這個callee,
// 並且提供"this"給這個 callee
foo(); // 全域對象
foo.prototype.constructor(); // foo.prototype
var bar = {
baz: foo
};
bar.baz(); // bar
(bar.baz)(); // also bar
(bar.baz = bar.baz)(); // 這是一個全域對象
(bar.baz, bar.baz)(); // 也是全域對象
(false || bar.baz)(); // 也是全域對象
var otherFoo = bar.baz;
otherFoo(); // 還是全域對象
如果要深入思考每一次函數調用中,this值的變化(更重要的是怎樣變化),你可以閱讀本系列教程第10章This。上文所提及的情況都會在此章內詳細討論。
總結(Conclusion)
在此我們完成了一個簡短的概述。儘管看來不是那麼簡短,但是這些話題若要完整表述完畢,則需要一整本書。.我們沒有提及兩個重要話題:函數(functions) (以及不同類型的函數之間的不同,比如函式宣告與函數運算式)與ECMAScript的 求值策略(evaluation strategy) 。這兩個話題可以分別查閱本系列教程第15章函數(Functions) 與第19章求值策略(Evaluation strategy)。
如果你有任何評論,問題或者補充,我很歡迎在文章評論中討論。
祝大家學習ECMAScript順利。