国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript內部原理系列-變量對象(Variable object)

legendaryedu / 1044人閱讀

摘要:它主要扮演被稱作活躍對象簡稱的角色。的個數對象的的值和當前實際傳遞的形參是共享的。處理執(zhí)行上下文代碼分為兩個階段進入執(zhí)行上下文執(zhí)行代碼對變量對象的修改和這兩個階段密切相關。在中,以相同的方式獲取活躍對象是允許的

概要

我們總是會在程序中定義一些函數和變量,之后會使用這些函數和變量來構建我們的系統。
然而,對于解釋器來說,它又是如何以及從哪里找到這些數據的(函數,變量)?當引用一個對象的時候,在解釋器內部又發(fā)生了什么?

許多ECMA腳本程序員都知道,變量和執(zhí)行上下文是密切相關的:

var a = 10; // 全局上下文中的變量

(function () {
  var b = 20; // 函數上下文中的局部變量
})();

alert(a); // 10
alert(b); // "b" is not defined

不僅如此,許多程序員也都知道,ECMAScript標準中指出獨立的作用域只有通過“函數代碼”(可執(zhí)行代碼類型中的一種)才能創(chuàng)建出來。比方說,與C/C++不同的是,在ECMAScript中for循環(huán)的代碼塊是無法創(chuàng)建本地上下文的:

for (var k in {a: 1, b: 2}) {
  alert(k);
}

alert(k); // 盡管循環(huán)已經結束,但是變量“k”仍然在作用域中

下面就來詳細介紹下,當聲明變量和函數的時候,究竟發(fā)生了什么。

數據聲明

既然變量和執(zhí)行上下文有關,那它就該知道數據存儲在哪里以及如何獲取。這種機制就稱作變量對象:

  

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.

舉個例子,可以用ECMAScript的對象來表示變量對象:

VO = {};

VO同時也是一個執(zhí)行上下文的屬性:

activeExecutionContext = {
  VO: {
    // 上下文中的數據 (變量聲明(var), 函數聲明(FD), 函數形參(function arguments))
  }
};

對變量的間接引用(通過VO的屬性名)只允許發(fā)生在全局上下文中的變量對象上(全局對象本身就是變量對象,這部分會在后續(xù)作相應的介紹)。 對于其他的上下文而言,是無法直接引用VO的,因為VO是實現層的。

聲明新的變量和函數的過程其實就是在VO中創(chuàng)建新的和變量以及函數名對應的屬性和屬性值的過程。

如下所示:

var a = 10;

function test(x) {
  var b = 20;
};

test(30);

上述代碼對應的變量對象則如下所示:

// 全局上下文中的變量對象
VO(globalContext) = {
  a: 10,
  test:
};

// “test”函數上下文中的變量對象
VO(test functionContext) = {
  x: 30,
  b: 20
}; 但是,在實現層(標準中定義的),變量對象只是一個抽象的概念。在實際執(zhí)行上下文中,VO可能完全不叫VO,并且初始的結構也可能完全不同。
不同執(zhí)行上下文中的變量對象

變量對象上的一些操作(比如:變量的初始化)和行為對于所有的執(zhí)行上下文類型來說都已一樣的。從這一點來說,將變量對象表示成抽象的概念更加合適。 函數上下文還能定義額外的與變量對象相關的信息。

AbstractVO (generic behavior of the variable instantiation process)

  ║
  ╠══> GlobalContextVO
  ║        (VO === this === global)
  ║
  ╚══> FunctionContextVO
           (VO === AO,  object and  are added)

接下來對這塊內容進行詳細介紹。

全局上下文中的變量對象

首先,有必要對全局對象(Global object)作個定義。

  

全局對象是一個在進入任何執(zhí)行上下文前就創(chuàng)建出來的對象;此對象以單例形式存在;它的屬性在程序任何地方都可以直接訪問,其生命周期隨著程序的結束而終止。

全局對象在創(chuàng)建的時候,諸如Math,String,Date,parseInt等等屬性也會被初始化,同時,其中一些對象會指向全局對象本身——比如,DOM中,全局對象上的window屬性就指向了全局對象(但是,并非所有的實現都是如此):

global = {
  Math: ,
  String: 
  ...
  ...
  window: global
};

在引用全局對象的屬性時,前綴通常可以省略,因為全局對象是不能通過名字直接訪問的。然而,通過全局對象上的this值,以及通過如DOM中的window對象這樣遞歸引用的方式都可以訪問到全局對象:

String(10); // 等同于 global.String(10);

// 帶前綴
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;

回到全局上下文的變量對象上——這里變量對象就是全局對象本身:

VO(globalContext) === global;

準確地理解這個事實是非常必要的:正是由于這個原因,當在全局上下文中聲明一個變量時,可以通過全局對象上的屬性來間地引用該變量(比方說,當變量名提前未知的情況下)

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"
函數上下文中的變量對象

在函數的執(zhí)行上下文中,VO是不能直接訪問的。它主要扮演被稱作活躍對象(activation object)(簡稱:AO)的角色。

VO(functionContext) === AO;

活躍對象會在進入函數上下文的時候創(chuàng)建出來,初始化的時候會創(chuàng)建一個arguments屬性,其值就是Arguments對象:

AO = {
  arguments:
};

Arguments對象是活躍對象上的屬性,它包含了如下屬性:

callee —— 對當前函數的引用

length —— 實參的個數

properties-indexes(數字,轉換成字符串)其值是函數參數的值(參數列表中,從左到右)。properties-indexes的個數 == arguments.length;
arguments對象的properties-indexes的值和當前(實際傳遞的)形參是共享的。

如下所示:

function foo(x, y, z) {

  // 定義的函數參數(x,y,z)的個數
  alert(foo.length); // 3

  // 實際傳遞的參數個數
  alert(arguments.length); // 2

  // 引用函數自身
  alert(arguments.callee === foo); // true

  // 參數互相共享

  alert(x === arguments[0]); // true
  alert(x); // 10

  arguments[0] = 20;
  alert(x); // 20

  x = 30;
  alert(arguments[0]); // 30

  // 然而,對于沒有傳遞的參數z,
  // 相關的arguments對象的index-property是不共享的

  z = 40;
  alert(arguments[2]); // undefined

  arguments[2] = 50;
  alert(z); // 40

}

foo(10, 20);

上述例子,在當前的Google Chrome瀏覽器中有個bug——參數z和arguments[2]也是互相共享的。

處理上下文代碼的幾個階段

至此,也就到了本文最核心的部分了。處理執(zhí)行上下文代碼分為兩個階段:

進入執(zhí)行上下文

執(zhí)行代碼

對變量對象的修改和這兩個階段密切相關。

要注意的是,這兩個處理階段是通用的行為,與上下文類型無關(不管是全局上下文還是函數上下文都是一致的)。

進入執(zhí)行上下文

一旦進入執(zhí)行上下文(在執(zhí)行代碼之前),VO就會被一些屬性填充(在此前已經描述過了):

函數的形參(當進入函數執(zhí)行上下文時)
—— 變量對象的一個屬性,其屬性名就是形參的名字,其值就是實參的值;對于沒有傳遞的參數,其值為undefined

函數聲明(FunctionDeclaration, FD) —— 變量對象的一個屬性,其屬性名和值都是函數對象創(chuàng)建出來的;如果變量對象已經包含了相同名字的屬性,則替換它的值

變量聲明(var,VariableDeclaration) —— 變量對象的一個屬性,其屬性名即為變量名,其值為undefined;如果變量名和已經聲明的函數名或者函數的參數名相同,則不會影響已經存在的屬性。
看下面這個例子:
function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}
test(10); // call

當以10為參數進入“test”函數上下文的時候,對應的AO如下所示:

AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: 
  e: undefined
};

注意了,上面的AO并不包含函數“x”。這是因為這里的“x”并不是函數聲明而是函數表達式(FunctionExpression,簡稱FE),函數表達式不會對VO造成影響。 盡管函數“_e”也是函數表達式,然而,正如我們所看到的,由于它被賦值給了變量“e”,因此它可以通過“e”來訪問到。關于函數聲明和函數表達式的區(qū)別會在第五章——函數作具體介紹。

至此,處理上下文代碼的第一階段介紹完了,接下來介紹第二階段——執(zhí)行代碼階段。

執(zhí)行代碼

此時,AO/VO的屬性已經填充好了。(盡管,大部分屬性都還沒有賦予真正的值,都只是初始化時候的undefined值)。

繼續(xù)以上一例子為例,到了執(zhí)行代碼階段,AO/VO就會修改成為如下形式:

AO["c"] = 10;
AO["e"] = ;

再次注意到,這里函數表達式“_e”仍在內存中,這是因為它被保存在聲明的變量“e”中,而同樣是函數表達式的“x”卻不在AO/VO中: 如果嘗試在定義前或者定義后調用“x”函數,這時會發(fā)生“x為定義”的錯誤。未保存的函數表達式只有在定義或者遞歸時才能調用。

如下是更加典型的例子:

alert(x); // function

var x = 10;
alert(x); // 10

x = 20;

function x() {};

alert(x); // 20

上述例子中,為何“x”打印出來是函數呢?為何在聲明前就可以訪問到?又為何不是10或者20呢?原因在于,根據規(guī)則——在進入上下文的時候,VO會被填充函數聲明; 同一階段,還有變量聲明“x”,但是,正如此前提到的,變量聲明是在函數聲明和函數形參之后,并且,變量聲明不會對已經存在的同樣名字的函數聲明和函數形參發(fā)生沖突, 因此,在進入上下文的階段,VO填充為如下形式:

VO = {};

VO["x"] = 

// 發(fā)現var x = 10;
// 如果函數“x”還未定義
// 則 "x" 為undefined, 但是,在我們的例子中
// 變量聲明并不會影響同名的函數值

VO["x"] = 

隨后,在執(zhí)行代碼階段,VO被修改為如下所示:

VO["x"] = 10;
VO["x"] = 20;

正如在第二個和第三個alert顯示的那樣。

如下例子再次看到在進入上下文階段,變量存儲在VO中(因此,盡管else的代碼塊永遠都不會執(zhí)行到,而“b”卻仍然在VO中):

if (true) {
  var a = 1;
} else {
  var b = 2;
}

alert(a); // 1
alert(b); // undefined, but not "b is not defined"
關于變量

大多數講JavaScript的文章甚至是JavaScript的書通常都會這么說:“聲明全局變量的方式有兩種,一種是使用var關鍵字(在全局上下文中),另外一種是不用var關鍵字(在任何位置)”。 而這樣的描述是錯誤的。要記住的是:
使用var關鍵字是聲明變量的唯一方式

如下賦值語句:

a = 10;

僅僅是在全局對象上創(chuàng)建了新的屬性(而不是變量)。“不是變量”并不意味著它無法改變,它是ECMAScript中變量的概念(它之后可以變?yōu)槿謱ο蟮膶傩裕驗閂O(globalContext) === global,還記得吧?)

不同點如下所示:

alert(a); // undefined
alert(b); // "b" is not defined

b = 10;
var a = 20;

接下來還是要談到VO和在不同階段對VO的修改(進入上下文階段和執(zhí)行代碼階段):
進入上下文:

VO = {
  a: undefined
};

我們看到,這個階段并沒有任何“b”,因為它不是變量,“b”在執(zhí)行代碼階段才出現。(但是,在我們這個例子中也不會出現,因為在“b”出現前就發(fā)生了錯誤)

將上述代碼稍作改動:

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

這里關于變量還有非常重要的一點:與簡單屬性不同的是,變量是不能刪除的{DontDelete},這意味著要想通過delete操作符來刪除一個變量是不可能的。

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

但是,這里有個例外,就是“eval”執(zhí)行上下文中,是可以刪除變量的:

eval("var a = 10;");
alert(window.a); // 10

alert(delete a); // true

alert(window.a); // undefined

利用某些debug工具,在終端測試過這些例子的童鞋要注意了:其中Firebug也是使用了eval來執(zhí)行終端的代碼。因此,這個時候var也是可以刪除的。

實現層的特性:parent屬性

正如此前介紹的,標準情況下,是無法直接訪問活躍對象的。然而,在某些實現中,比如知名的SpiderMonkey和Rhino,函數有個特殊的屬性parent, 該屬性是對該函數創(chuàng)建所在的活躍對象的引用(或者全局變量對象)。

如下所示(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

上述例子中,可以看到函數foo是在全局上下文中創(chuàng)建的,相應的,它的parent屬性設置為全局上下文的變量對象,比如說:全局對象。

然而,在SpiderMonkey中以相同的方式獲取活躍對象是不可能的:不同的版本表現都不同,內部函數的parent屬性會返回null或者全局對象。
在Rhino中,以相同的方式獲取活躍對象是允許的:

如下所示(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

})();
總結

本文,我們介紹了與執(zhí)行上下文相關的對象。希望,本文能夠對大家有所幫助,同時也希望本文能夠起到解惑的作用。

擴展閱讀

10.1.3 —— 變量初始化

10.1.5 —— 全局對象

10.1.6 —— 活躍對象

10.1.8 —— 參數對象

此文譯自Dmitry A.Soshnikov的文章Variable object
趙靜/宋珍珍 譯
via 前端翻譯小站

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/85446.html

相關文章

  • javascript中閉包的理解

    摘要:在前端開發(fā)中閉包是一個很重要的知識點,是面試中一定會被問到的內容。閉包的用途閉包可以用在許多地方。這里僅僅是我對閉包的一些見解,若有錯誤的地方,還望大家提出,一起交流共同進步參考文獻你不知道的上卷深入理解系列 在前端開發(fā)中閉包是一個很重要的知識點,是面試中一定會被問到的內容。之前我對閉包的理解主要是通過閉包可以在函數外部能訪問到函數內部的變量,對閉包運用的也很少,甚至自己寫過閉包自己都...

    Enlightenment 評論0 收藏0
  • 深入理解JavaScript系列12:變量對象

    摘要:所有變量聲明由名稱和對應值組成一個變量對象的屬性被創(chuàng)建如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。 介紹 JavaScript編程的時候總避免不了聲明函數和變量,以成功構建我們的系統,但是解釋器是如何并且在什么地方去查找這些函數和變量呢?我們引用這些對象的時候究竟發(fā)生了什么? 原始發(fā)布:Dmitry A. Soshnikov 發(fā)布時間:2009-...

    vincent_xyb 評論0 收藏0
  • [學習筆記] JavaScript 作用域鏈

    摘要:全局執(zhí)行環(huán)境的變量對象始終是作用域鏈中的最后一個變量對象。綜上,每個函數對應一個執(zhí)行環(huán)境,每個執(zhí)行環(huán)境對應一個變量對象,而多個變量對象構成了作用域鏈,如果當前執(zhí)行環(huán)境是函數,那么其活動對象在作用域鏈的前端。 1.幾個概念 先說幾個概念:函數、執(zhí)行環(huán)境、變量對象、作用域鏈、活動對象。這幾個東東之間有什么關系呢,往下看~ 函數 函數大家都知道,我想說的是,js中,在函數內部有兩個特殊...

    ?xiaoxiao, 評論0 收藏0
  • [譯文] JavaScript工作原理:內存管理+如何處理4種常見的內存泄露

    摘要:本系列的第一篇文章著重提供一個關于引擎運行時和調用棧的概述。在硬件層面,計算機內存由大量的觸發(fā)器組成。每個觸發(fā)器包含幾個晶體管能夠存儲一個比特譯注位。可以通過唯一標識符來訪問單個觸發(fā)器,所以可以對它們進行讀寫操作。比特稱為個字節(jié)。 原文 How JavaScript works: memory management + how to handle 4 common memory lea...

    adam1q84 評論0 收藏0
  • # JavaScript中的執(zhí)行上下文和隊列(棧)的關系?

    摘要:為什么會這樣這段代碼究竟是如何運行的執(zhí)行上下文堆棧瀏覽器中的解釋器單線程運行。瀏覽器始終執(zhí)行位于堆棧頂部的,并且一旦函數完成執(zhí)行當前操作,它將從堆棧頂部彈出,將控制權返回到當前堆棧中的下方上下文。確定在上下文中的值。 原文:What is the Execution Context & Stack in JavaScript? git地址:JavaScript中的執(zhí)行上下文和隊列(...

    DangoSky 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<