摘要:它主要扮演被稱(chēng)作活躍對(duì)象簡(jiǎn)稱(chēng)的角色。的個(gè)數(shù)對(duì)象的的值和當(dāng)前實(shí)際傳遞的形參是共享的。處理執(zhí)行上下文代碼分為兩個(gè)階段進(jìn)入執(zhí)行上下文執(zhí)行代碼對(duì)變量對(duì)象的修改和這兩個(gè)階段密切相關(guān)。在中,以相同的方式獲取活躍對(duì)象是允許的
概要
我們總是會(huì)在程序中定義一些函數(shù)和變量,之后會(huì)使用這些函數(shù)和變量來(lái)構(gòu)建我們的系統(tǒng)。
然而,對(duì)于解釋器來(lái)說(shuō),它又是如何以及從哪里找到這些數(shù)據(jù)的(函數(shù),變量)?當(dāng)引用一個(gè)對(duì)象的時(shí)候,在解釋器內(nèi)部又發(fā)生了什么?
許多ECMA腳本程序員都知道,變量和執(zhí)行上下文是密切相關(guān)的:
var a = 10; // 全局上下文中的變量 (function () { var b = 20; // 函數(shù)上下文中的局部變量 })(); alert(a); // 10 alert(b); // "b" is not defined
不僅如此,許多程序員也都知道,ECMAScript標(biāo)準(zhǔn)中指出獨(dú)立的作用域只有通過(guò)“函數(shù)代碼”(可執(zhí)行代碼類(lèi)型中的一種)才能創(chuàng)建出來(lái)。比方說(shuō),與C/C++不同的是,在ECMAScript中for循環(huán)的代碼塊是無(wú)法創(chuàng)建本地上下文的:
for (var k in {a: 1, b: 2}) { alert(k); } alert(k); // 盡管循環(huán)已經(jīng)結(jié)束,但是變量“k”仍然在作用域中下面就來(lái)詳細(xì)介紹下,當(dāng)聲明變量和函數(shù)的時(shí)候,究竟發(fā)生了什么。
數(shù)據(jù)聲明既然變量和執(zhí)行上下文有關(guān),那它就該知道數(shù)據(jù)存儲(chǔ)在哪里以及如何獲取。這種機(jī)制就稱(chēng)作變量對(duì)象:
A variable object (in abbreviated form — VO) is a special object related with an execution context and which stores:
variables (var, VariableDeclaration);
function declarations (FunctionDeclaration, in abbreviated form FD);
and function formal parameters
declared in the context.舉個(gè)例子,可以用ECMAScript的對(duì)象來(lái)表示變量對(duì)象:
VO = {};VO同時(shí)也是一個(gè)執(zhí)行上下文的屬性:
activeExecutionContext = { VO: { // 上下文中的數(shù)據(jù) (變量聲明(var), 函數(shù)聲明(FD), 函數(shù)形參(function arguments)) } };對(duì)變量的間接引用(通過(guò)VO的屬性名)只允許發(fā)生在全局上下文中的變量對(duì)象上(全局對(duì)象本身就是變量對(duì)象,這部分會(huì)在后續(xù)作相應(yīng)的介紹)。 對(duì)于其他的上下文而言,是無(wú)法直接引用VO的,因?yàn)閂O是實(shí)現(xiàn)層的。
聲明新的變量和函數(shù)的過(guò)程其實(shí)就是在VO中創(chuàng)建新的和變量以及函數(shù)名對(duì)應(yīng)的屬性和屬性值的過(guò)程。
如下所示:
var a = 10; function test(x) { var b = 20; }; test(30);上述代碼對(duì)應(yīng)的變量對(duì)象則如下所示:
// 全局上下文中的變量對(duì)象 VO(globalContext) = { a: 10, test: }; // “test”函數(shù)上下文中的變量對(duì)象 VO(test functionContext) = { x: 30, b: 20 }; 但是,在實(shí)現(xiàn)層(標(biāo)準(zhǔn)中定義的),變量對(duì)象只是一個(gè)抽象的概念。在實(shí)際執(zhí)行上下文中,VO可能完全不叫VO,并且初始的結(jié)構(gòu)也可能完全不同。不同執(zhí)行上下文中的變量對(duì)象變量對(duì)象上的一些操作(比如:變量的初始化)和行為對(duì)于所有的執(zhí)行上下文類(lèi)型來(lái)說(shuō)都已一樣的。從這一點(diǎn)來(lái)說(shuō),將變量對(duì)象表示成抽象的概念更加合適。 函數(shù)上下文還能定義額外的與變量對(duì)象相關(guān)的信息。
AbstractVO (generic behavior of the variable instantiation process) ║ ╠══> GlobalContextVO ║ (VO === this === global) ║ ╚══> FunctionContextVO (VO === AO, object and are added)接下來(lái)對(duì)這塊內(nèi)容進(jìn)行詳細(xì)介紹。
全局上下文中的變量對(duì)象首先,有必要對(duì)全局對(duì)象(Global object)作個(gè)定義。
全局對(duì)象是一個(gè)在進(jìn)入任何執(zhí)行上下文前就創(chuàng)建出來(lái)的對(duì)象;此對(duì)象以單例形式存在;它的屬性在程序任何地方都可以直接訪(fǎng)問(wèn),其生命周期隨著程序的結(jié)束而終止。
全局對(duì)象在創(chuàng)建的時(shí)候,諸如Math,String,Date,parseInt等等屬性也會(huì)被初始化,同時(shí),其中一些對(duì)象會(huì)指向全局對(duì)象本身——比如,DOM中,全局對(duì)象上的window屬性就指向了全局對(duì)象(但是,并非所有的實(shí)現(xiàn)都是如此):
global = { Math: , String: ... ... window: global };在引用全局對(duì)象的屬性時(shí),前綴通??梢允÷?,因?yàn)槿謱?duì)象是不能通過(guò)名字直接訪(fǎng)問(wèn)的。然而,通過(guò)全局對(duì)象上的this值,以及通過(guò)如DOM中的window對(duì)象這樣遞歸引用的方式都可以訪(fǎng)問(wèn)到全局對(duì)象:
String(10); // 等同于 global.String(10); // 帶前綴 window.a = 10; // === global.window.a = 10 === global.a = 10; this.b = 20; // global.b = 20;回到全局上下文的變量對(duì)象上——這里變量對(duì)象就是全局對(duì)象本身:
VO(globalContext) === global;準(zhǔn)確地理解這個(gè)事實(shí)是非常必要的:正是由于這個(gè)原因,當(dāng)在全局上下文中聲明一個(gè)變量時(shí),可以通過(guò)全局對(duì)象上的屬性來(lái)間地引用該變量(比方說(shuō),當(dāng)變量名提前未知的情況下)
var a = new String("test"); alert(a); // directly, is found in VO(globalContext): "test" alert(window["a"]); // indirectly via global === VO(globalContext): "test" alert(a === this.a); // true var aKey = "a"; alert(window[aKey]); // indirectly, with dynamic property name: "test"函數(shù)上下文中的變量對(duì)象在函數(shù)的執(zhí)行上下文中,VO是不能直接訪(fǎng)問(wèn)的。它主要扮演被稱(chēng)作活躍對(duì)象(activation object)(簡(jiǎn)稱(chēng):AO)的角色。
VO(functionContext) === AO;活躍對(duì)象會(huì)在進(jìn)入函數(shù)上下文的時(shí)候創(chuàng)建出來(lái),初始化的時(shí)候會(huì)創(chuàng)建一個(gè)arguments屬性,其值就是Arguments對(duì)象:
AO = { arguments: };Arguments對(duì)象是活躍對(duì)象上的屬性,它包含了如下屬性:
callee —— 對(duì)當(dāng)前函數(shù)的引用
length —— 實(shí)參的個(gè)數(shù)
properties-indexes(數(shù)字,轉(zhuǎn)換成字符串)其值是函數(shù)參數(shù)的值(參數(shù)列表中,從左到右)。properties-indexes的個(gè)數(shù) == arguments.length;
arguments對(duì)象的properties-indexes的值和當(dāng)前(實(shí)際傳遞的)形參是共享的。如下所示:
function foo(x, y, z) { // 定義的函數(shù)參數(shù)(x,y,z)的個(gè)數(shù) alert(foo.length); // 3 // 實(shí)際傳遞的參數(shù)個(gè)數(shù) alert(arguments.length); // 2 // 引用函數(shù)自身 alert(arguments.callee === foo); // true // 參數(shù)互相共享 alert(x === arguments[0]); // true alert(x); // 10 arguments[0] = 20; alert(x); // 20 x = 30; alert(arguments[0]); // 30 // 然而,對(duì)于沒(méi)有傳遞的參數(shù)z, // 相關(guān)的arguments對(duì)象的index-property是不共享的 z = 40; alert(arguments[2]); // undefined arguments[2] = 50; alert(z); // 40 } foo(10, 20);上述例子,在當(dāng)前的Google Chrome瀏覽器中有個(gè)bug——參數(shù)z和arguments[2]也是互相共享的。
處理上下文代碼的幾個(gè)階段至此,也就到了本文最核心的部分了。處理執(zhí)行上下文代碼分為兩個(gè)階段:
進(jìn)入執(zhí)行上下文
執(zhí)行代碼
對(duì)變量對(duì)象的修改和這兩個(gè)階段密切相關(guān)。
要注意的是,這兩個(gè)處理階段是通用的行為,與上下文類(lèi)型無(wú)關(guān)(不管是全局上下文還是函數(shù)上下文都是一致的)。
進(jìn)入執(zhí)行上下文一旦進(jìn)入執(zhí)行上下文(在執(zhí)行代碼之前),VO就會(huì)被一些屬性填充(在此前已經(jīng)描述過(guò)了):
函數(shù)的形參(當(dāng)進(jìn)入函數(shù)執(zhí)行上下文時(shí))
—— 變量對(duì)象的一個(gè)屬性,其屬性名就是形參的名字,其值就是實(shí)參的值;對(duì)于沒(méi)有傳遞的參數(shù),其值為undefined函數(shù)聲明(FunctionDeclaration, FD) —— 變量對(duì)象的一個(gè)屬性,其屬性名和值都是函數(shù)對(duì)象創(chuàng)建出來(lái)的;如果變量對(duì)象已經(jīng)包含了相同名字的屬性,則替換它的值
變量聲明(var,VariableDeclaration) —— 變量對(duì)象的一個(gè)屬性,其屬性名即為變量名,其值為undefined;如果變量名和已經(jīng)聲明的函數(shù)名或者函數(shù)的參數(shù)名相同,則不會(huì)影響已經(jīng)存在的屬性。
看下面這個(gè)例子:
function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}
test(10); // call當(dāng)以10為參數(shù)進(jìn)入“test”函數(shù)上下文的時(shí)候,對(duì)應(yīng)的AO如下所示:
AO(test) = { a: 10, b: undefined, c: undefined, d: e: undefined };注意了,上面的AO并不包含函數(shù)“x”。這是因?yàn)檫@里的“x”并不是函數(shù)聲明而是函數(shù)表達(dá)式(FunctionExpression,簡(jiǎn)稱(chēng)FE),函數(shù)表達(dá)式不會(huì)對(duì)VO造成影響。 盡管函數(shù)“_e”也是函數(shù)表達(dá)式,然而,正如我們所看到的,由于它被賦值給了變量“e”,因此它可以通過(guò)“e”來(lái)訪(fǎng)問(wèn)到。關(guān)于函數(shù)聲明和函數(shù)表達(dá)式的區(qū)別會(huì)在第五章——函數(shù)作具體介紹。
至此,處理上下文代碼的第一階段介紹完了,接下來(lái)介紹第二階段——執(zhí)行代碼階段。
執(zhí)行代碼此時(shí),AO/VO的屬性已經(jīng)填充好了。(盡管,大部分屬性都還沒(méi)有賦予真正的值,都只是初始化時(shí)候的undefined值)。
繼續(xù)以上一例子為例,到了執(zhí)行代碼階段,AO/VO就會(huì)修改成為如下形式:
AO["c"] = 10; AO["e"] = ;再次注意到,這里函數(shù)表達(dá)式“_e”仍在內(nèi)存中,這是因?yàn)樗槐4嬖诼暶鞯淖兞俊癳”中,而同樣是函數(shù)表達(dá)式的“x”卻不在AO/VO中: 如果嘗試在定義前或者定義后調(diào)用“x”函數(shù),這時(shí)會(huì)發(fā)生“x為定義”的錯(cuò)誤。未保存的函數(shù)表達(dá)式只有在定義或者遞歸時(shí)才能調(diào)用。
如下是更加典型的例子:
alert(x); // function var x = 10; alert(x); // 10 x = 20; function x() {}; alert(x); // 20上述例子中,為何“x”打印出來(lái)是函數(shù)呢?為何在聲明前就可以訪(fǎng)問(wèn)到?又為何不是10或者20呢?原因在于,根據(jù)規(guī)則——在進(jìn)入上下文的時(shí)候,VO會(huì)被填充函數(shù)聲明; 同一階段,還有變量聲明“x”,但是,正如此前提到的,變量聲明是在函數(shù)聲明和函數(shù)形參之后,并且,變量聲明不會(huì)對(duì)已經(jīng)存在的同樣名字的函數(shù)聲明和函數(shù)形參發(fā)生沖突, 因此,在進(jìn)入上下文的階段,VO填充為如下形式:
VO = {}; VO["x"] = // 發(fā)現(xiàn)var x = 10; // 如果函數(shù)“x”還未定義 // 則 "x" 為undefined, 但是,在我們的例子中 // 變量聲明并不會(huì)影響同名的函數(shù)值 VO["x"] =隨后,在執(zhí)行代碼階段,VO被修改為如下所示:
VO["x"] = 10; VO["x"] = 20;正如在第二個(gè)和第三個(gè)alert顯示的那樣。
如下例子再次看到在進(jìn)入上下文階段,變量存儲(chǔ)在VO中(因此,盡管else的代碼塊永遠(yuǎn)都不會(huì)執(zhí)行到,而“b”卻仍然在VO中):
if (true) { var a = 1; } else { var b = 2; } alert(a); // 1 alert(b); // undefined, but not "b is not defined"關(guān)于變量大多數(shù)講JavaScript的文章甚至是JavaScript的書(shū)通常都會(huì)這么說(shuō):“聲明全局變量的方式有兩種,一種是使用var關(guān)鍵字(在全局上下文中),另外一種是不用var關(guān)鍵字(在任何位置)”。 而這樣的描述是錯(cuò)誤的。要記住的是:
使用var關(guān)鍵字是聲明變量的唯一方式如下賦值語(yǔ)句:
a = 10;僅僅是在全局對(duì)象上創(chuàng)建了新的屬性(而不是變量)?!安皇亲兞俊辈⒉灰馕吨鼰o(wú)法改變,它是ECMAScript中變量的概念(它之后可以變?yōu)槿謱?duì)象的屬性,因?yàn)閂O(globalContext) === global,還記得吧?)
不同點(diǎn)如下所示:
alert(a); // undefined alert(b); // "b" is not defined b = 10; var a = 20;接下來(lái)還是要談到VO和在不同階段對(duì)VO的修改(進(jìn)入上下文階段和執(zhí)行代碼階段):
進(jìn)入上下文:VO = { a: undefined };我們看到,這個(gè)階段并沒(méi)有任何“b”,因?yàn)樗皇亲兞浚癰”在執(zhí)行代碼階段才出現(xiàn)。(但是,在我們這個(gè)例子中也不會(huì)出現(xiàn),因?yàn)樵凇癰”出現(xiàn)前就發(fā)生了錯(cuò)誤)
將上述代碼稍作改動(dòng):
alert(a); // undefined, we know why b = 10; alert(b); // 10, created at code execution var a = 20; alert(a); // 20, modified at code execution這里關(guān)于變量還有非常重要的一點(diǎn):與簡(jiǎn)單屬性不同的是,變量是不能刪除的{DontDelete},這意味著要想通過(guò)delete操作符來(lái)刪除一個(gè)變量是不可能的。
a = 10; alert(window.a); // 10 alert(delete a); // true alert(window.a); // undefined var b = 20; alert(window.b); // 20 alert(delete b); // false alert(window.b); // still 20但是,這里有個(gè)例外,就是“eval”執(zhí)行上下文中,是可以刪除變量的:
eval("var a = 10;"); alert(window.a); // 10 alert(delete a); // true alert(window.a); // undefined利用某些debug工具,在終端測(cè)試過(guò)這些例子的童鞋要注意了:其中Firebug也是使用了eval來(lái)執(zhí)行終端的代碼。因此,這個(gè)時(shí)候var也是可以刪除的。
實(shí)現(xiàn)層的特性:parent屬性正如此前介紹的,標(biāo)準(zhǔn)情況下,是無(wú)法直接訪(fǎng)問(wèn)活躍對(duì)象的。然而,在某些實(shí)現(xiàn)中,比如知名的SpiderMonkey和Rhino,函數(shù)有個(gè)特殊的屬性parent, 該屬性是對(duì)該函數(shù)創(chuàng)建所在的活躍對(duì)象的引用(或者全局變量對(duì)象)。
如下所示(SpiderMonkey,Rhino):
var global = this; var a = 10; function foo() {} alert(foo.__parent__); // global var VO = foo.__parent__; alert(VO.a); // 10 alert(VO === global); // true上述例子中,可以看到函數(shù)foo是在全局上下文中創(chuàng)建的,相應(yīng)的,它的parent屬性設(shè)置為全局上下文的變量對(duì)象,比如說(shuō):全局對(duì)象。
然而,在SpiderMonkey中以相同的方式獲取活躍對(duì)象是不可能的:不同的版本表現(xiàn)都不同,內(nèi)部函數(shù)的parent屬性會(huì)返回null或者全局對(duì)象。
在Rhino中,以相同的方式獲取活躍對(duì)象是允許的:如下所示(Rhino):
var global = this; var x = 10; (function foo() { var y = 20; // the activation object of the "foo" context var AO = (function () {}).__parent__; print(AO.y); // 20 // __parent__ of the current activation // object is already the global object, // i.e. the special chain of variable objects is formed, // so-called, a scope chain print(AO.__parent__ === global); // true print(AO.__parent__.x); // 10 })();總結(jié)本文,我們介紹了與執(zhí)行上下文相關(guān)的對(duì)象。希望,本文能夠?qū)Υ蠹矣兴鶐椭?,同時(shí)也希望本文能夠起到解惑的作用。
擴(kuò)展閱讀10.1.3 —— 變量初始化
10.1.5 —— 全局對(duì)象
10.1.6 —— 活躍對(duì)象
10.1.8 —— 參數(shù)對(duì)象
此文譯自Dmitry A.Soshnikov的文章Variable object
趙靜/宋珍珍 譯
via 前端翻譯小站
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/85446.html
摘要:在前端開(kāi)發(fā)中閉包是一個(gè)很重要的知識(shí)點(diǎn),是面試中一定會(huì)被問(wèn)到的內(nèi)容。閉包的用途閉包可以用在許多地方。這里僅僅是我對(duì)閉包的一些見(jiàn)解,若有錯(cuò)誤的地方,還望大家提出,一起交流共同進(jìn)步參考文獻(xiàn)你不知道的上卷深入理解系列 在前端開(kāi)發(fā)中閉包是一個(gè)很重要的知識(shí)點(diǎn),是面試中一定會(huì)被問(wèn)到的內(nèi)容。之前我對(duì)閉包的理解主要是通過(guò)閉包可以在函數(shù)外部能訪(fǎng)問(wèn)到函數(shù)內(nèi)部的變量,對(duì)閉包運(yùn)用的也很少,甚至自己寫(xiě)過(guò)閉包自己都...
摘要:所有變量聲明由名稱(chēng)和對(duì)應(yīng)值組成一個(gè)變量對(duì)象的屬性被創(chuàng)建如果變量名稱(chēng)跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類(lèi)屬性。 介紹 JavaScript編程的時(shí)候總避免不了聲明函數(shù)和變量,以成功構(gòu)建我們的系統(tǒng),但是解釋器是如何并且在什么地方去查找這些函數(shù)和變量呢?我們引用這些對(duì)象的時(shí)候究竟發(fā)生了什么? 原始發(fā)布:Dmitry A. Soshnikov 發(fā)布時(shí)間:2009-...
摘要:全局執(zhí)行環(huán)境的變量對(duì)象始終是作用域鏈中的最后一個(gè)變量對(duì)象。綜上,每個(gè)函數(shù)對(duì)應(yīng)一個(gè)執(zhí)行環(huán)境,每個(gè)執(zhí)行環(huán)境對(duì)應(yīng)一個(gè)變量對(duì)象,而多個(gè)變量對(duì)象構(gòu)成了作用域鏈,如果當(dāng)前執(zhí)行環(huán)境是函數(shù),那么其活動(dòng)對(duì)象在作用域鏈的前端。 1.幾個(gè)概念 先說(shuō)幾個(gè)概念:函數(shù)、執(zhí)行環(huán)境、變量對(duì)象、作用域鏈、活動(dòng)對(duì)象。這幾個(gè)東東之間有什么關(guān)系呢,往下看~ 函數(shù) 函數(shù)大家都知道,我想說(shuō)的是,js中,在函數(shù)內(nèi)部有兩個(gè)特殊...
摘要:本系列的第一篇文章著重提供一個(gè)關(guān)于引擎運(yùn)行時(shí)和調(diào)用棧的概述。在硬件層面,計(jì)算機(jī)內(nèi)存由大量的觸發(fā)器組成。每個(gè)觸發(fā)器包含幾個(gè)晶體管能夠存儲(chǔ)一個(gè)比特譯注位。可以通過(guò)唯一標(biāo)識(shí)符來(lái)訪(fǎng)問(wèn)單個(gè)觸發(fā)器,所以可以對(duì)它們進(jìn)行讀寫(xiě)操作。比特稱(chēng)為個(gè)字節(jié)。 原文 How JavaScript works: memory management + how to handle 4 common memory lea...
摘要:為什么會(huì)這樣這段代碼究竟是如何運(yùn)行的執(zhí)行上下文堆棧瀏覽器中的解釋器單線(xiàn)程運(yùn)行。瀏覽器始終執(zhí)行位于堆棧頂部的,并且一旦函數(shù)完成執(zhí)行當(dāng)前操作,它將從堆棧頂部彈出,將控制權(quán)返回到當(dāng)前堆棧中的下方上下文。確定在上下文中的值。 原文:What is the Execution Context & Stack in JavaScript? git地址:JavaScript中的執(zhí)行上下文和隊(duì)列(...
閱讀 4619·2021-11-22 09:34
閱讀 2830·2021-11-12 10:36
閱讀 835·2021-08-18 10:23
閱讀 2734·2019-08-30 15:55
閱讀 3253·2019-08-30 15:53
閱讀 2153·2019-08-30 15:44
閱讀 1435·2019-08-29 15:37
閱讀 1564·2019-08-29 13:04