然而再接下來(lái),我們發(fā)現(xiàn)MyObject2()的實(shí)例obj2的constructor仍然指向function MyObject()。 盡管這很說(shuō)不通,然而現(xiàn)實(shí)的確如此。——這到底是為什么呢?
事實(shí)上,僅下面的代碼: -------- function MyObject2() { }
obj2 = new MyObject2(); document.writeln(MyObject2.prototype.constructor === MyObject2); -------- 構(gòu)造的obj2.constructor將正確的指向function MyObject2()。事實(shí)上,我們也會(huì)注意到這 種情況下,MyObject2的原型屬性的constructor也正確的指向該函數(shù)。然而,由于JavaScript 要求指定prototype對(duì)象來(lái)構(gòu)造原型鏈: -------- function MyObject2() { } MyObject2.prototype = new MyObject();
obj2 = new MyObject2(); -------- 這時(shí),再訪(fǎng)問(wèn)obj2,將會(huì)得到新的原型(也就是MyObject2.prototype)的constructor屬性。 因此,一切很明了:原型的屬性影響到構(gòu)造過(guò)程對(duì)對(duì)象的constructor的初始設(shè)定。
作為一種補(bǔ)充的解決問(wèn)題的手段,JavaScript開(kāi)發(fā)規(guī)范中說(shuō)“need to remember to reset the constructor property',要求用戶(hù)自行設(shè)定該屬性。
所以你會(huì)看到更規(guī)范的JavaScript代碼要求這樣書(shū)寫(xiě): //--------------------------------------------------------- // 維護(hù)constructor屬性的規(guī)范代碼 //--------------------------------------------------------- function MyObject2() { } MyObject2.prototype = new MyObject(); MyObject2.prototype.constructor = MyObject2;
obj2 = new MyObject2();
更外一種解決問(wèn)題的方法,是在function MyObject()中去重置該值。當(dāng)然,這樣會(huì)使 得執(zhí)行效率稍低一點(diǎn)點(diǎn): //--------------------------------------------------------- // 維護(hù)constructor屬性的第二種方式 //--------------------------------------------------------- function MyObject2() { ?? this.constructor = arguments.callee; ?? // or, this.constructor = MyObject2;
?? // ... } MyObject2.prototype = new MyObject();
obj2 = new MyObject2();
5). 析構(gòu)問(wèn)題 ------ JavaScript中沒(méi)有析構(gòu)函數(shù),但卻有“對(duì)象析構(gòu)”的問(wèn)題。也就是說(shuō),盡管我們不 知道一個(gè)對(duì)象什么時(shí)候會(huì)被析構(gòu),也不能截獲它的析構(gòu)過(guò)程并處理一些事務(wù)。然而, 在一些不多見(jiàn)的時(shí)候,我們會(huì)遇到“要求一個(gè)對(duì)象立即析構(gòu)”的問(wèn)題。
問(wèn)題大多數(shù)的時(shí)候出現(xiàn)在對(duì)ActiveX Object的處理上。因?yàn)槲覀兛赡茉贘avaScript 里創(chuàng)建了一個(gè)ActiveX Object,在做完一些處理之后,我們又需要再創(chuàng)建一個(gè)。而 如果原來(lái)的對(duì)象供應(yīng)者(Server)不允許創(chuàng)建多個(gè)實(shí)例,那么我們就需要在JavaScript 中確保先前的實(shí)例是已經(jīng)被釋放過(guò)了。接下來(lái),即使Server允許創(chuàng)建多個(gè)實(shí)例,而 在多個(gè)實(shí)例間允許共享數(shù)據(jù)(例如OS的授權(quán),或者資源、文件的鎖),那么我們?cè)谛?br />實(shí)例中的操作就可能會(huì)出問(wèn)題。
可能還是有人不明白我們?cè)谡f(shuō)什么,那么我就舉一個(gè)例子:如果創(chuàng)建一個(gè)Excel對(duì)象, 打開(kāi)文件A,然后我們save它,然后關(guān)閉這個(gè)實(shí)例。然后我們?cè)賱?chuàng)建Excel對(duì)象并打開(kāi) 同一文件。——注意這時(shí)JavaScript可能還沒(méi)有來(lái)得及析構(gòu)前一個(gè)對(duì)象。——這時(shí)我們 再想Save這個(gè)文件,就發(fā)現(xiàn)失敗了。下面的代碼示例這種情況: //--------------------------------------------------------- // JavaScript中的析構(gòu)問(wèn)題(ActiveX Object示例) //--------------------------------------------------------- <script> var strSaveLocation = 'file:///E:/1.xls'
function createXLS() { ?? var excel = new ActiveXObject("Excel.Application"); ?? var wk = excel.Workbooks.Add(); ?? wk.SaveAs(strSaveLocation); ?? wk.Saved = true;
?? excel.Quit(); }
function writeXLS() { ?? var excel = new ActiveXObject("Excel.Application"); ?? var wk = excel.Workbooks.Open(strSaveLocation); ?? var sheet = wk.Worksheets(1); ?? sheet.Cells(1, 1).Value = '測(cè)試字符串'; ?? wk.SaveAs(strSaveLocation); ?? wk.Saved = true;
?? excel.Quit(); } </script> <body> ?? <button onclick="createXLS()">創(chuàng)建</button> ?? <button onclick="writeXLS()">重寫(xiě)</button> </body>
在這個(gè)例子中,在本地文件操作時(shí)并不會(huì)出現(xiàn)異常。——最多只是有一些內(nèi)存垃 圾而已。然而,如果strSaveLocation是一個(gè)遠(yuǎn)程的URL,這時(shí)本地將會(huì)保存一個(gè) 文件存取權(quán)限的憑證,而且同時(shí)只能一個(gè)(遠(yuǎn)程的)實(shí)例來(lái)開(kāi)啟該excel文檔并存 儲(chǔ)。于是如果反復(fù)點(diǎn)擊"重寫(xiě)"按鈕,就會(huì)出現(xiàn)異常。
——注意,這是在SPS中操作共享文件時(shí)的一個(gè)實(shí)例的簡(jiǎn)化代碼。因此,它并非 “學(xué)術(shù)的”無(wú)聊討論,而且工程中的實(shí)際問(wèn)題。
解決這個(gè)問(wèn)題的方法很復(fù)雜。它涉及到兩個(gè)問(wèn)題: ?? - 本地憑證的釋放 ?? - ActiveX Object實(shí)例的釋放
下面我們先從JavaScript中對(duì)象的“失效”問(wèn)題說(shuō)起。簡(jiǎn)單的說(shuō): ?? - 一個(gè)對(duì)象在其生存的上下文環(huán)境之外,即會(huì)失效。 ?? - 一個(gè)全局的對(duì)象在沒(méi)有被執(zhí)用(引用)的情況下,即會(huì)失效。
例如: //--------------------------------------------------------- // JavaScript對(duì)象何時(shí)失效 //--------------------------------------------------------- function testObject() { ?? var _obj1 = new Object(); }
function testObject2() { ?? var _obj2 = new Object(); ?? return _obj2; }
// 示例1 testObject();
// 示例2 testObject2()
// 示例3 var obj3 = testObject2(); obj3 = null;
// 示例4 var obj4 = testObject2(); var arr = [obj4]; obj3 = null; arr = [];
在這四個(gè)示例中: ?? - “示例1”在函數(shù)testObject()中構(gòu)造了_obj1,但是在函數(shù)退出時(shí), ???? 它就已經(jīng)離開(kāi)了函數(shù)的上下文環(huán)境,因此_obj1失效了; ?? - “示例2”中,testObject2()中也構(gòu)造了一個(gè)對(duì)象_obj2并傳出,因 ???? 此對(duì)象有了“函數(shù)外”的上下文環(huán)境(和生存周期),然而由于函數(shù) ???? 的返回值沒(méi)有被其它變量“持有”,因此_obj2也立即失效了; ?? - “示例3”中,testObject2()構(gòu)造的_obj2被外部的變量obj3持用了, ???? 這時(shí),直到“obj3=null”這行代碼生效時(shí),_obj2才會(huì)因?yàn)橐藐P(guān)系 ???? 消失而失效。 ?? - 與示例3相同的原因,“示例4”中的_obj2會(huì)在“arr=[]”這行代碼 ???? 之后才會(huì)失效。
但是,對(duì)象的“失效”并不等會(huì)“釋放”。在JavaScript運(yùn)行環(huán)境的內(nèi)部,沒(méi) 有任何方式來(lái)確切地告訴用戶(hù)“對(duì)象什么時(shí)候會(huì)釋放”。這依賴(lài)于JavaScript 的內(nèi)存回收機(jī)制。——這種策略與.NET中的回收機(jī)制是類(lèi)同的。
在前面的Excel操作示例代碼中,對(duì)象的所有者,也就是"EXCEL.EXE"這個(gè)進(jìn)程 只能在“ActiveX Object實(shí)例的釋放”之后才會(huì)發(fā)生。而文件的鎖,以及操作 系統(tǒng)的權(quán)限憑證是與進(jìn)程相關(guān)的。因此如果對(duì)象僅是“失效”而不是“釋放”, 那么其它進(jìn)程處理文件和引用操作系統(tǒng)的權(quán)限憑據(jù)時(shí)就會(huì)出問(wèn)題。
——有些人說(shuō)這是JavaScript或者COM機(jī)制的BUG。其實(shí)不是,這是OS、IE 和JavaScript之間的一種復(fù)雜關(guān)系所導(dǎo)致的,而非獨(dú)立的問(wèn)題。
Microsoft公開(kāi)了解決這種問(wèn)題的策略:主動(dòng)調(diào)用內(nèi)存回收過(guò)程。
在(微軟的)JScript中提供了一個(gè)CollectGarbage()過(guò)程(通常簡(jiǎn)稱(chēng)GC過(guò)程), GC過(guò)程用于清理當(dāng)前IE中的“失效的對(duì)象失例”,也就是調(diào)用對(duì)象的析構(gòu)過(guò)程。
在上例中調(diào)用GC過(guò)程的代碼是: //--------------------------------------------------------- // 處理ActiveX Object時(shí),GC過(guò)程的標(biāo)準(zhǔn)調(diào)用方式 //--------------------------------------------------------- function writeXLS() { ?? //(略...)
?? excel.Quit(); ?? excel = null; ?? setTimeout(CollectGarbage, 1); }
第一行代碼調(diào)用excel.Quit()方法來(lái)使得excel進(jìn)程中止并退出,這時(shí)由于JavaScript 環(huán)境執(zhí)有excel對(duì)象實(shí)例,因此excel進(jìn)程并不實(shí)際中止。
第二行代碼使excel為null,以清除對(duì)象引用,從而使對(duì)象“失效”。然而由于 對(duì)象仍舊在函數(shù)上下文環(huán)境中,因此如果直接調(diào)用GC過(guò)程,對(duì)象仍然不會(huì)被清理。
第三行代碼使用setTimeout()來(lái)調(diào)用CollectGarbage函數(shù),時(shí)間間隔設(shè)為'1',只 是使得GC過(guò)程發(fā)生在writeXLS()函數(shù)執(zhí)行完之后。這樣excel對(duì)象就滿(mǎn)足了“能被 GC清理”的兩個(gè)條件:沒(méi)有引用和離開(kāi)上下文環(huán)境。
GC過(guò)程的使用,在使用了ActiveX Object的JS環(huán)境中很有效。一些潛在的ActiveX Object包括XML、VML、OWC(Office Web Componet)、flash,甚至包括在JS中的VBArray。 從這一點(diǎn)來(lái)看,ajax架構(gòu)由于采用了XMLHTTP,并且同時(shí)要滿(mǎn)足“不切換頁(yè)面”的 特性,因此在適當(dāng)?shù)臅r(shí)候主動(dòng)調(diào)用GC過(guò)程,會(huì)得到更好的效率用UI體驗(yàn)。
事實(shí)上,即使使用GC過(guò)程,前面提到的excel問(wèn)題仍然不會(huì)被完全解決。因?yàn)镮E還 緩存了權(quán)限憑據(jù)。使頁(yè)的權(quán)限憑據(jù)被更新的唯一方法,只能是“切換到新的頁(yè)面”, 因此事實(shí)上在前面提到的那個(gè)SPS項(xiàng)目中,我采用的方法并不是GC,而是下面這一 段代碼: //--------------------------------------------------------- // 處理ActiveX Object時(shí)采用的頁(yè)面切換代碼 //--------------------------------------------------------- function writeXLS() { ?? //(略...)
?? excel.Quit(); ?? excel = null;
?? // 下面代碼用于解決IE call Excel的一個(gè)BUG, MSDN中提供的方法: ?? //??? setTimeout(CollectGarbage, 1); ?? // 由于不能清除(或同步)網(wǎng)頁(yè)的受信任狀態(tài), 所以將導(dǎo)致SaveAs()等方法在 ?? // 下次調(diào)用時(shí)無(wú)效. ?? location.reload(); }
最后之最后,關(guān)于GC的一個(gè)補(bǔ)充說(shuō)明:在IE窗體被最小化時(shí),IE將會(huì)主動(dòng)調(diào)用一次 CollectGarbage()函數(shù)。這使得IE窗口在最小化之后,內(nèi)存占用會(huì)有明顯改善。
八、JavaScript面向?qū)ο蟮闹С?br />~~~~~~~~~~~~~~~~~~ (續(xù))
4. 實(shí)例和實(shí)例引用 -------- 在.NET Framework對(duì)CTS(Common Type System)約定“一切都是對(duì)象”,并分為“值類(lèi)型”和“引用類(lèi)型”兩種。其中“值類(lèi)型”的對(duì)象在轉(zhuǎn)換成“引用類(lèi)型”數(shù)據(jù)的過(guò)程中,需要進(jìn)行一個(gè)“裝箱”和“拆箱”的過(guò)程。
在JavaScript也有同樣的問(wèn)題。我們看到的typeof關(guān)鍵字,返回以下六種數(shù)據(jù)類(lèi)型: "number"、"string"、"boolean"、"object"、"function" 和 "undefined"。
我們也發(fā)現(xiàn)JavaScript的對(duì)象系統(tǒng)中,有String、Number、Function、Boolean這四種對(duì)象構(gòu)造器。那么,我們的問(wèn)題是:如果有一個(gè)數(shù)字A,typeof(A)的結(jié)果,到底會(huì)是'number'呢,還是一個(gè)構(gòu)造器指向function Number()的對(duì)象呢?
//--------------------------------------------------------- // 關(guān)于JavaScript的類(lèi)型的測(cè)試代碼 //--------------------------------------------------------- function getTypeInfo(V) { ?? return (typeof V == 'object' ??? 'Object, construct by '+V.constructor ??? : 'Value, type of '+typeof V); }
var A1 = 100; var A2 = new Number(100);
document.writeln('A1 is ', getTypeInfo(A1), '<BR>'); document.writeln('A2 is ', getTypeInfo(A2), '<BR>'); document.writeln([A1.constructor === A2.constructor, A2.constructor === Number]);
測(cè)試代碼的執(zhí)行結(jié)果如下: ----------- A1 is Value, type of number A2 is Object, construct by function Number() { [native code] } true,true -----------
我們注意到,A1和A2的構(gòu)造器都指向Number。這意味著通過(guò)constructor屬性來(lái)識(shí)別對(duì)象,(有時(shí))比typeof更加有效。因?yàn)椤爸殿?lèi)型數(shù)據(jù)”A1作為一個(gè)對(duì)象來(lái)看待時(shí),與A2有完全相同的特性。
——除了與實(shí)例引用有關(guān)的問(wèn)題。
參考JScript手冊(cè),我們對(duì)其它基礎(chǔ)類(lèi)型和構(gòu)造器做相同考察,可以發(fā)現(xiàn): ?? - 基礎(chǔ)類(lèi)型中的undefined、number、boolean和string,是“值類(lèi)型”變量 ?? - 基礎(chǔ)類(lèi)型中的array、function和object,是“引用類(lèi)型”變量 ?? - 使用new()方法構(gòu)造出對(duì)象,是“引用類(lèi)型”變量
下面的代碼說(shuō)明“值類(lèi)型”與“引用類(lèi)型”之間的區(qū)別: //--------------------------------------------------------- // 關(guān)于JavaScript類(lèi)型系統(tǒng)中的值/引用問(wèn)題 //--------------------------------------------------------- var str1 = 'abcdefgh', str2 = 'abcdefgh'; var obj1 = new String('abcdefgh'), obj2 = new String('abcdefgh');
document.writeln([str1==str2, str1===str2], '<br>'); document.writeln([obj1==obj2, obj1===obj2]);
測(cè)試代碼的執(zhí)行結(jié)果如下: ----------- true, true false, false -----------
我們看到,無(wú)論是等值運(yùn)算(==),還是全等運(yùn)算(===),對(duì)“對(duì)象”和“值”的理解都是不一樣的。
更進(jìn)一步的理解這種現(xiàn)象,我們知道: ?? - 運(yùn)算結(jié)果為值類(lèi)型,或變量為值類(lèi)型時(shí),等值(或全等)比較可以得到預(yù)想結(jié)果 ?? - (即使包含相同的數(shù)據(jù),)不同的對(duì)象實(shí)例之間是不等值(或全等)的 ?? - 同一個(gè)對(duì)象的不同引用之間,是等值(==)且全等(===)的
但對(duì)于String類(lèi)型,有一點(diǎn)補(bǔ)充:根據(jù)JScript的描述,兩個(gè)字符串比較時(shí),只要有一個(gè)是值類(lèi)型,則按值比較。這意味著在上面的例子中,代碼“str1==obj1”會(huì)得到結(jié)果true。而全等(===)運(yùn)算需要檢測(cè)變量類(lèi)型的一致性,因此“str1===obj1”的結(jié)果返回false。
JavaScript中的函數(shù)參數(shù)總是傳入值參,引用類(lèi)型(的實(shí)例)是作為指針值傳入的。因此函數(shù)可以隨意重寫(xiě)入口變量,而不用擔(dān)心外部變量被修改。但是,需要留意傳入的引用類(lèi)型的變量,因?yàn)閷?duì)它方法調(diào)用和屬性讀寫(xiě)可能會(huì)影響到實(shí)例本身。——但,也可以通過(guò)引用類(lèi)型的參數(shù)來(lái)傳出數(shù)據(jù)。
最后補(bǔ)充說(shuō)明一下,值類(lèi)型比較會(huì)逐字節(jié)檢測(cè)對(duì)象實(shí)例中的數(shù)據(jù),效率低但準(zhǔn)確性高;而引用類(lèi)型只檢測(cè)實(shí)例指針和數(shù)據(jù)類(lèi)型,因此效率高而準(zhǔn)確性低。如果你需要檢測(cè)兩個(gè)引用類(lèi)型是否真的包含相同的數(shù)據(jù),可能你需要嘗試把它轉(zhuǎn)換成“字符串值”再來(lái)比較。
6. 函數(shù)的上下文環(huán)境 -------- 只要寫(xiě)過(guò)代碼,你應(yīng)該知道變量是有“全局變量”和“局部變量”之分的。絕大多數(shù)的 JavaScript程序員也知道下面這些概念: //--------------------------------------------------------- // JavaScript中的全局變量與局部變量 //--------------------------------------------------------- var v1 = '全局變量-1'; v2 = '全局變量-2';
function foo() { ?? v3 = '全局變量-3';
?? var v4 = '只有在函數(shù)內(nèi)部并使用var定義的,才是局部變量'; }
按照通常對(duì)語(yǔ)言的理解來(lái)說(shuō),不同的代碼調(diào)用函數(shù),都會(huì)擁有一套獨(dú)立的局部變量。 因此下面這段代碼很容易理解: //--------------------------------------------------------- // JavaScript的局部變量 //--------------------------------------------------------- function MyObject() { ?? var o = new Object;
?? this.getValue = function() { ???? return o; ?? } }
var obj1 = new MyObject(); var obj2 = new MyObject(); document.writeln(obj1.getValue() == obj2.getValue());
結(jié)果顯示false,表明不同(實(shí)例的方法)調(diào)用返回的局部變量“obj1/obj2”是不相同。
變量的局部、全局特性與OOP的封裝性中的“私有(private)”、“公開(kāi)(public)”具有類(lèi)同性。因此絕大多數(shù)資料總是以下面的方式來(lái)說(shuō)明JavaScript的面向?qū)ο笙到y(tǒng)中的“封裝權(quán)限級(jí)別”問(wèn)題: //--------------------------------------------------------- // JavaScript中OOP封裝性 //--------------------------------------------------------- function MyObject() { ?? // 1. 私有成員和方法 ?? var private_prop = 0; ?? var private_method_1 = function() { ???? // ... ???? return 1 ?? } ?? function private_method_2() { ???? // ... ???? return 1 ?? }
?? // 2. 特權(quán)方法 ?? this.privileged_method = function () { ???? private_prop++; ???? return private_prop + private_method_1() + private_method_2(); ?? }
?? // 3. 公開(kāi)成員和方法 ?? this.public_prop_1 = ''; ?? this.public_method_1 = function () { ???? // ... ?? } }
// 4. 公開(kāi)成員和方法(2) MyObject.prototype.public_prop_1 = ''; MyObject.prototype.public_method_1 = function () { ?? // ... }
var obj1 = new MyObject(); var obj2 = new MyObject();
document.writeln(obj1.privileged_method(), '<br>'); document.writeln(obj2.privileged_method());
在這里,“私有(private)”表明只有在(構(gòu)造)函數(shù)內(nèi)部可訪(fǎng)問(wèn),而“特權(quán)(privileged)”是特指一種存取“私有域”的“公開(kāi)(public)”方法。“公開(kāi)(public)”表明在(構(gòu)造)函數(shù)外可以調(diào)用和存取。
除了上述的封裝權(quán)限之外,一些文檔還介紹了其它兩種相關(guān)的概念: ?? - 原型屬性:Classname.prototype.propertyName = someValue ?? - (類(lèi))靜態(tài)屬性:Classname.propertyName = someValue
然而,從面向?qū)ο蟮慕嵌壬蟻?lái)講,上面這些概念都很難自圓其說(shuō):JavaScript究竟是為何、以及如何劃分出這些封裝權(quán)限和概念來(lái)的呢?
——因?yàn)槲覀儽仨氉⒁獾较旅孢@個(gè)例子所帶來(lái)的問(wèn)題: //--------------------------------------------------------- // JavaScript中的局部變量 //--------------------------------------------------------- function MyFoo() { ?? var i;
?? MyFoo.setValue = function (v) { ????? i = v; ?? } ?? MyFoo.getValue = function () { ????? return i; ?? } } MyFoo();
var obj1 = new Object(); var obj2 = new Object();
// 測(cè)試一 MyFoo.setValue.call(obj1, 'obj1'); document.writeln(MyFoo.getValue.call(obj1), '<BR>');
// 測(cè)試二 MyFoo.setValue.call(obj2, 'obj2'); document.writeln(MyFoo.getValue.call(obj2)); document.writeln(MyFoo.getValue.call(obj1)); document.writeln(MyFoo.getValue());
在這個(gè)測(cè)試代碼中,obj1/obj2都是Object()實(shí)例。我們使用function.call()的方式來(lái)調(diào)用setValue/getValue,使得在MyFoo()調(diào)用的過(guò)程中替換this為obj1/obj2實(shí)例。
然而我們發(fā)現(xiàn)“測(cè)試二”完成之后,obj2、obj1以及function MyFoo()所持有的局部變量都返回了“obj2”。——這表明三個(gè)函數(shù)使用了同一個(gè)局部變量。
由此可見(jiàn),JavaScript在處理局部變量時(shí),對(duì)“普通函數(shù)”與“構(gòu)造器”是分別對(duì)待的。這種處理策略在一些JavaScript相關(guān)的資料中被解釋作“面向?qū)ο笾械乃接杏颉眴?wèn)題。而事實(shí)上,我更愿意從源代碼一級(jí)來(lái)告訴你真相:這是對(duì)象的上下文環(huán)境的問(wèn)題。——只不過(guò)從表面看去,“上下文環(huán)境”的問(wèn)題被轉(zhuǎn)嫁到對(duì)象的封裝性問(wèn)題上了。
(在閱讀下面的文字之前,)先做一個(gè)概念性的說(shuō)明: ?? - 在普通函數(shù)中,上下文環(huán)境被window對(duì)象所持有 - 在“構(gòu)造器和對(duì)象方法”中,上下文環(huán)境被對(duì)象實(shí)例所持有
在JavaScript的實(shí)現(xiàn)代碼中,每次創(chuàng)建一個(gè)對(duì)象,解釋器將為對(duì)象創(chuàng)建一個(gè)上下文環(huán)境鏈,用于存放對(duì)象在進(jìn)入“構(gòu)造器和對(duì)象方法”時(shí)對(duì)function()內(nèi)部數(shù)據(jù)的一個(gè)備份。JavaScript保證這個(gè)對(duì)象在以后再進(jìn)入“構(gòu)造器和對(duì)象方法”內(nèi)部時(shí),總是持有該上下文環(huán)境,和一個(gè)與之相關(guān)的this對(duì)象。由于對(duì)象可能有多個(gè)方法,且每個(gè)方法可能又存在多層嵌套函數(shù),因此這事實(shí)上構(gòu)成了一個(gè)上下文環(huán)境的樹(shù)型鏈表結(jié)構(gòu)。而在構(gòu)造器和對(duì)象方法之外,JavaScript不提供任何訪(fǎng)問(wèn)(該構(gòu)造器和對(duì)象方法的)上下文環(huán)境的方法。
簡(jiǎn)而言之: ?? - 上下文環(huán)境與對(duì)象實(shí)例調(diào)用“構(gòu)造器和對(duì)象方法”時(shí)相關(guān),而與(普通)函數(shù)無(wú)關(guān) ?? - 上下文環(huán)境記錄一個(gè)對(duì)象在“構(gòu)造函數(shù)和對(duì)象方法”內(nèi)部的私有數(shù)據(jù) ?? - 上下文環(huán)境采用鏈?zhǔn)浇Y(jié)構(gòu),以記錄多層的嵌套函數(shù)中的上下文
由于上下文環(huán)境只與構(gòu)造函數(shù)及其內(nèi)部的嵌套函數(shù)有關(guān),重新閱讀前面的代碼: //--------------------------------------------------------- // JavaScript中的局部變量 //--------------------------------------------------------- function MyFoo() { ?? var i;
?? MyFoo.setValue = function (v) { ????? i = v; ?? } ?? MyFoo.getValue = function () { ????? return i; ?? } } MyFoo();
var obj1 = new Object(); MyFoo.setValue.call(obj1, 'obj1');
我們發(fā)現(xiàn)setValue()的確可以訪(fǎng)問(wèn)到位于MyFoo()函數(shù)內(nèi)部的“局部變量i”,但是由于setValue()方法的執(zhí)有者是MyFoo對(duì)象(記住函數(shù)也是對(duì)象),因此MyFoo對(duì)象擁有MyFoo()函數(shù)的唯一一份“上下文環(huán)境”。
接下來(lái)MyFoo.setValue.call()調(diào)用雖然為setValue()傳入了新的this對(duì)象,但實(shí)際上擁有“上下文環(huán)境”的仍舊是MyFoo對(duì)象。因此我們看到無(wú)論創(chuàng)建多少個(gè)obj1/obj2,最終操作的都是同一個(gè)私有變量i。
全局函數(shù)/變量的“上下文環(huán)境”持有者為window,因此下面的代碼說(shuō)明了“為什么全局變量能被任意的對(duì)象和函數(shù)訪(fǎng)問(wèn)”: //--------------------------------------------------------- // 全局函數(shù)的上下文 //--------------------------------------------------------- /* function Window() { */ ?? var global_i = 0; ?? var global_j = 1;
?? function foo_0() { ?? }
?? function foo_1() { ?? } /* }
window = new Window(); */
因此我們可以看到foo_0()與foo_1()能同時(shí)訪(fǎng)問(wèn)global_i和global_j。接下來(lái)的推論是,上下文環(huán)境決定了變量的“全局”與“私有”。而不是反過(guò)來(lái)通過(guò)變量的私有與全局來(lái)討論上下文環(huán)境問(wèn)題。
更進(jìn)一步的推論是:JavaScript中的全局變量與函數(shù),本質(zhì)上是window對(duì)象的私有變量與方法。而這個(gè)上下文環(huán)境塊,位于所有(window對(duì)象內(nèi)部的)對(duì)象實(shí)例的上下文環(huán)境鏈表的頂端,因此都可能訪(fǎng)問(wèn)到。
用“上下文環(huán)境”的理論,你可以順利地解釋在本小節(jié)中,有關(guān)變量的“全局/局部”作用域的問(wèn)題,以及有關(guān)對(duì)象方法的封裝權(quán)限問(wèn)題。事實(shí)上,在實(shí)現(xiàn)JavaScript的C源代碼中,這個(gè)“上下文環(huán)境”被叫做“JSContext”,并作為函數(shù)/方法的第一個(gè)參數(shù)傳入。——如果你有興趣,你可以從源代碼中證實(shí)本小節(jié)所述的理論。
另外,《JavaScript權(quán)威指南》這本書(shū)中第4.7節(jié)也講述了這個(gè)問(wèn)題,但被叫做“變量的作用域”。然而重要的是,這本書(shū)把問(wèn)題講反了。——作者試圖用“全局、局部的作用域”,來(lái)解釋產(chǎn)生這種現(xiàn)象的“上下文環(huán)境”的問(wèn)題。因此這個(gè)小節(jié)顯得凌亂而且難以自圓其說(shuō)。
|