在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 變量對(duì)象(Variable Object)
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

變量對(duì)象(Variable Object)

介紹

JavaScript 編程的時(shí)候總避免不了聲明函數(shù)和變量,以成功構(gòu)建我們的系統(tǒng),但是解釋器是如何并且在什么地方去查找這些函數(shù)和變量呢?我們引用這些對(duì)象的時(shí)候究竟發(fā)生了什么?

大多數(shù) ECMAScript 程序員應(yīng)該都知道變量與執(zhí)行上下文有密切關(guān)系:

var a = 10; // 全局上下文中的變量
(function () {
  var b = 20; // function上下文中的局部變量
})();
alert(a); // 10
alert(b); // 全局變量 "b" 沒有聲明

并且,很多程序員也都知道,當(dāng)前 ECMAScript 規(guī)范指出獨(dú)立作用域只能通過“函數(shù)(function)”代碼類型的執(zhí)行上下文創(chuàng)建。也就是說,相對(duì)于 C/C++來說,ECMAScript 里的 for 循環(huán)并不能創(chuàng)建一個(gè)局部的上下文。

for (var k in {a: 1, b: 2}) {
  alert(k);
}
alert(k); // 盡管循環(huán)已經(jīng)結(jié)束但變量k依然在當(dāng)前作用域

我們來看看一下,我們聲明數(shù)據(jù)的時(shí)候到底都發(fā)現(xiàn)了什么細(xì)節(jié)。

數(shù)據(jù)聲明

如果變量與執(zhí)行上下文相關(guān),那變量自己應(yīng)該知道它的數(shù)據(jù)存儲(chǔ)在哪里,并且知道如何訪問。這種機(jī)制稱為變量對(duì)象(variable object)。

變量對(duì)象(縮寫為 VO)是一個(gè)與執(zhí)行上下文相關(guān)的特殊對(duì)象,它存儲(chǔ)著在上下文中聲明的以下內(nèi)容:

  • 變量 (var, 變量聲明);
  • 函數(shù)聲明 (FunctionDeclaration, 縮寫為 FD);
  • 函數(shù)的形參;

舉例來說,我們可以用普通的 ECMAScript 對(duì)象來表示一個(gè)變量對(duì)象:

VO = {};

就像我們所說的,VO 就是執(zhí)行上下文的屬性(property):

activeExecutionContext = {
  VO: {
    // 上下文數(shù)據(jù)(var, FD, function arguments)
  }
};

只有全局上下文的變量對(duì)象允許通過 VO 的屬性名稱來間接訪問(因?yàn)樵谌稚舷挛睦?,全局?duì)象自身就是變量對(duì)象,稍后會(huì)詳細(xì)介紹),在其它上下文中是不能直接訪問 VO 對(duì)象的,因?yàn)樗皇莾?nèi)部機(jī)制的一個(gè)實(shí)現(xiàn)。

當(dāng)我們聲明一個(gè)變量或一個(gè)函數(shù)的時(shí)候,和我們創(chuàng)建 VO 新屬性的時(shí)候一樣沒有別的區(qū)別(即:有名稱以及對(duì)應(yīng)的值)。

例如:

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

對(duì)應(yīng)的變量對(duì)象是:

// 全局上下文的變量對(duì)象
VO(globalContext) = {
  a: 10,
  test: <reference to function>
};
// test函數(shù)上下文的變量對(duì)象
VO(test functionContext) = {
  x: 30,
  b: 20
};

在具體實(shí)現(xiàn)層面(以及規(guī)范中)變量對(duì)象只是一個(gè)抽象概念。(從本質(zhì)上說,在具體執(zhí)行上下文中,VO 名稱是不一樣的,并且初始結(jié)構(gòu)也不一樣。

不同執(zhí)行上下文中的變量對(duì)象

對(duì)于所有類型的執(zhí)行上下文來說,變量對(duì)象的一些操作(如變量初始化)和行為都是共通的。從這個(gè)角度來看,把變量對(duì)象作為抽象的基本事物來理解更為容易。同樣在函數(shù)上下文中也定義和變量對(duì)象相關(guān)的額外內(nèi)容。

抽象變量對(duì)象VO (變量初始化過程的一般行為)
  ║
  ╠══> 全局上下文變量對(duì)象GlobalContextVO
  ║        (VO === this === global)
  ║
  ╚══> 函數(shù)上下文變量對(duì)象FunctionContextVO
           (VO === AO, 并且添加了<arguments>和<formal parameters>)

我們來詳細(xì)看一下:

全局上下文中的變量對(duì)象

首先,我們要給全局對(duì)象一個(gè)明確的定義:

  • 全局對(duì)象(Global object) 是在進(jìn)入任何執(zhí)行上下文之前就已經(jīng)創(chuàng)建了的對(duì)象;
  • 這個(gè)對(duì)象只存在一份,它的屬性在程序中任何地方都可以訪問,全局對(duì)象的生命周期終止于程序退出那一刻。

全局對(duì)象初始創(chuàng)建階段將 Math、String、Date、parseInt 作為自身屬性,等屬性初始化,同樣也可以有額外創(chuàng)建的其它對(duì)象作為屬性(其可以指向到全局對(duì)象自身)。例如,在 DOM 中,全局對(duì)象的 window 屬性就可以引用全局對(duì)象自身(當(dāng)然,并不是所有的具體實(shí)現(xiàn)都是這樣):

global = {
  Math: <...>,
  String: <...>
  ...
  ...
  window: global //引用自身
};

當(dāng)訪問全局對(duì)象的屬性時(shí)通常會(huì)忽略掉前綴,這是因?yàn)槿謱?duì)象是不能通過名稱直接訪問的。不過我們依然可以通過全局上下文的 this 來訪問全局對(duì)象,同樣也可以遞歸引用自身。例如,DOM 中的 window。綜上所述,代碼可以簡寫為:

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;

非常有必要要理解上述結(jié)論,基于這個(gè)原理,在全局上下文中聲明的對(duì)應(yīng),我們才可以間接通過全局對(duì)象的屬性來訪問它(例如,事先不知道變量名稱)。

var a = new String('test');
alert(a); // 直接訪問,在VO(globalContext)里找到:"test"
alert(window['a']); // 間接通過global訪問:global === VO(globalContext): "test"
alert(a === this.a); // true
var aKey = 'a';
alert(window[aKey]); // 間接通過動(dòng)態(tài)屬性名稱訪問:"test"

函數(shù)上下文中的變量對(duì)象

在函數(shù)執(zhí)行上下文中,VO 是不能直接訪問的,此時(shí)由活動(dòng)對(duì)象(activation object,縮寫為 AO)扮演 VO 的角色。

VO(functionContext) === AO;

活動(dòng)對(duì)象是在進(jìn)入函數(shù)上下文時(shí)刻被創(chuàng)建的,它通過函數(shù)的 arguments 屬性初始化。arguments 屬性的值是 Arguments 對(duì)象:

AO = {
  arguments: <ArgO>
};

Arguments 對(duì)象是活動(dòng)對(duì)象的一個(gè)屬性,它包括如下屬性:

  1. callee — 指向當(dāng)前函數(shù)的引用
  2. length — 真正傳遞的參數(shù)個(gè)數(shù)
  3. properties-indexes (字符串類型的整數(shù)) 屬性的值就是函數(shù)的參數(shù)值(按參數(shù)列表從左到右排列)。 properties-indexes 內(nèi)部元素的個(gè)數(shù)等于 arguments.length. properties-indexes 的值和實(shí)際傳遞進(jìn)來的參數(shù)之間是共享的。

例如:

function foo(x, y, z) {
  // 聲明的函數(shù)參數(shù)數(shù)量arguments (x, y, z)
  alert(foo.length); // 3
  // 真正傳進(jìn)來的參數(shù)個(gè)數(shù)(only x, y)
  alert(arguments.length); // 2
  // 參數(shù)的callee是函數(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
  // 不過,沒有傳進(jìn)來的參數(shù)z,和參數(shù)的第3個(gè)索引值是不共享的
  z = 40;
  alert(arguments[2]); // undefined
  arguments[2] = 50;
  alert(z); // 40
}
foo(10, 20);

這個(gè)例子的代碼,在當(dāng)前版本的 Google Chrome 瀏覽器里有一個(gè) bug — 即使沒有傳遞參數(shù) z,z 和 arguments[2]仍然是共享的。

處理上下文代碼的 2 個(gè)階段

現(xiàn)在我們終于到了本文的核心點(diǎn)了。執(zhí)行上下文的代碼被分成兩個(gè)基本的階段來處理:

  1. 進(jìn)入執(zhí)行上下文
  2. 執(zhí)行代碼

變量對(duì)象的修改變化與這兩個(gè)階段緊密相關(guān)。

注:這 2 個(gè)階段的處理是一般行為,和上下文的類型無關(guān)(也就是說,在全局上下文和函數(shù)上下文中的表現(xiàn)是一樣的)。

進(jìn)入執(zhí)行上下文

當(dāng)進(jìn)入執(zhí)行上下文(代碼執(zhí)行之前)時(shí),VO 里已經(jīng)包含了下列屬性(前面已經(jīng)說了):

函數(shù)的所有形參(如果我們是在函數(shù)執(zhí)行上下文中)

— 由名稱和對(duì)應(yīng)值組成的一個(gè)變量對(duì)象的屬性被創(chuàng)建;沒有傳遞對(duì)應(yīng)參數(shù)的話,那么由名稱和 undefined 值組成的一種變量對(duì)象的屬性也將被創(chuàng)建。

所有函數(shù)聲明(FunctionDeclaration, FD)

—由名稱和對(duì)應(yīng)值(函數(shù)對(duì)象(function-object))組成一個(gè)變量對(duì)象的屬性被創(chuàng)建;如果變量對(duì)象已經(jīng)存在相同名稱的屬性,則完全替換這個(gè)屬性。

所有變量聲明(var, VariableDeclaration)

— 由名稱和對(duì)應(yīng)值(undefined)組成一個(gè)變量對(duì)象的屬性被創(chuàng)建;如果變量名稱跟已經(jīng)聲明的形式參數(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)進(jìn)入帶有參數(shù) 10 的 test 函數(shù)上下文時(shí),AO 表現(xiàn)為如下:

AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <reference to FunctionDeclaration "d">
  e: undefined
};

注意,AO 里并不包含函數(shù)“x”。這是因?yàn)椤皒” 是一個(gè)函數(shù)表達(dá)式(FunctionExpression, 縮寫為 FE) 而不是函數(shù)聲明,函數(shù)表達(dá)式不會(huì)影響 VO。 不管怎樣,函數(shù)“_e” 同樣也是函數(shù)表達(dá)式,但是就像我們下面將看到的那樣,因?yàn)樗峙浣o了變量 “e”,所以它可以通過名稱“e”來訪問。 函數(shù)聲明 FunctionDeclaration 與函數(shù)表達(dá)式 FunctionExpression 的不同,將在第 15 章 Functions 進(jìn)行詳細(xì)的探討,也可以參考本系列第 2 章揭秘命名函數(shù)表達(dá)式來了解。

這之后,將進(jìn)入處理上下文代碼的第二個(gè)階段 — 執(zhí)行代碼。

代碼執(zhí)行

這個(gè)周期內(nèi),AO/VO 已經(jīng)擁有了屬性(不過,并不是所有的屬性都有值,大部分屬性的值還是系統(tǒng)默認(rèn)的初始值 undefined )。

還是前面那個(gè)例子, AO/VO 在代碼解釋期間被修改如下:

AO['c'] = 10;
AO['e'] = <reference to FunctionExpression "_e">;

再次注意,因?yàn)?FunctionExpression“_e”保存到了已聲明的變量“e”上,所以它仍然存在于內(nèi)存中。而 FunctionExpression “x”卻不存在于 AO/VO 中,也就是說如果我們想嘗試調(diào)用“x”函數(shù),不管在函數(shù)定義之前還是之后,都會(huì)出現(xiàn)一個(gè)錯(cuò)誤“x is not defined”,未保存的函數(shù)表達(dá)式只有在它自己的定義或遞歸中才能被調(diào)用。

另一個(gè)經(jīng)典例子:

alert(x); // function
var x = 10;
alert(x); // 10
x = 20;
function x() {}; 
alert(x); // 20

為什么第一個(gè) alert “x” 的返回值是 function,而且它還是在“x” 聲明之前訪問的“x” 的?為什么不是 10 或 20 呢?因?yàn)?,根?jù)規(guī)范函數(shù)聲明是在當(dāng)進(jìn)入上下文時(shí)填入的; 同意周期,在進(jìn)入上下文的時(shí)候還有一個(gè)變量聲明“x”,那么正如我們?cè)谏弦粋€(gè)階段所說,變量聲明在順序上跟在函數(shù)聲明和形式參數(shù)聲明之后,而且在這個(gè)進(jìn)入上下文階段,變量聲明不會(huì)干擾VO 中已經(jīng)存在的同名函數(shù)聲明或形式參數(shù)聲明,因此,在進(jìn)入上下文時(shí),VO 的結(jié)構(gòu)如下:

VO = {};
VO['x'] = <reference to FunctionDeclaration "x">
// 找到var x = 10;
// 如果function "x"沒有已經(jīng)聲明的話
// 這時(shí)候"x"的值應(yīng)該是undefined
// 但是這個(gè)case里變量聲明沒有影響同名的function的值
VO['x'] = <the value is not disturbed, still function>

緊接著,在執(zhí)行代碼階段,VO 做如下修改:

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

我們可以在第二、三個(gè) alert 看到這個(gè)效果。

在下面的例子里我們可以再次看到,變量是在進(jìn)入上下文階段放入 VO 中的。(因?yàn)?,雖然 else 部分代碼永遠(yuǎn)不會(huì)執(zhí)行,但是不管怎樣,變量“b”仍然存在于 VO 中。)

if (true) {
  var a = 1;
} else {
  var b = 2;
}
alert(a); // 1
alert(b); // undefined,不是b沒有聲明,而是b的值是undefined

關(guān)于變量

通常,各類文章和 JavaScript 相關(guān)的書籍都聲稱:“不管是使用 var 關(guān)鍵字(在全局上下文)還是不使用 var 關(guān)鍵字(在任何地方),都可以聲明一個(gè)變量”。請(qǐng)記住,這是錯(cuò)誤的概念:

任何時(shí)候,變量只能通過使用 var 關(guān)鍵字才能聲明。

上面的賦值語句:

a = 10;

這僅僅是給全局對(duì)象創(chuàng)建了一個(gè)新屬性(但它不是變量)?!安皇亲兞俊辈⒉皇钦f它不能被改變,而是指它不符合 ECMAScript 規(guī)范中的變量概念,所以它“不是變量”(它之所以能成為全局對(duì)象的屬性,完全是因?yàn)?VO(globalContext) === global,大家還記得這個(gè)吧?)。

讓我們通過下面的實(shí)例看看具體的區(qū)別吧:

alert(a); // undefined
alert(b); // "b" 沒有聲明
b = 10;
var a = 20;

所有根源仍然是 VO 和進(jìn)入上下文階段和代碼執(zhí)行階段:

進(jìn)入上下文階段:

VO = {
  a: undefined
};

我們可以看到,因?yàn)椤癰”不是一個(gè)變量,所以在這個(gè)階段根本就沒有“b”,“b”將只在代碼執(zhí)行階段才會(huì)出現(xiàn)(但是在我們這個(gè)例子里,還沒有到那就已經(jīng)出錯(cuò)了)。

讓我們改變一下例子代碼:

alert(a); // undefined, 這個(gè)大家都知道,
b = 10;
alert(b); // 10, 代碼執(zhí)行階段創(chuàng)建
var a = 20;
alert(a); // 20, 代碼執(zhí)行階段修改

關(guān)于變量,還有一個(gè)重要的知識(shí)點(diǎn)。變量相對(duì)于簡單屬性來說,變量有一個(gè)特性(attribute):{DontDelete},這個(gè)特性的含義就是不能用 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

但是這個(gè)規(guī)則在有個(gè)上下文里不起走樣,那就是 eval 上下文,變量沒有{DontDelete}特性。

eval('var a = 10;');
alert(window.a); // 10
alert(delete a); // true
alert(window.a); // undefined

使用一些調(diào)試工具(例如:Firebug)的控制臺(tái)測試該實(shí)例時(shí),請(qǐng)注意,F(xiàn)irebug 同樣是使用 eval 來執(zhí)行控制臺(tái)里你的代碼。因此,變量屬性同樣沒有{DontDelete}特性,可以被刪除。

特殊實(shí)現(xiàn): __parent__ 屬性

前面已經(jīng)提到過,按標(biāo)準(zhǔn)規(guī)范,活動(dòng)對(duì)象是不可能被直接訪問到的。但是,一些具體實(shí)現(xiàn)并沒有完全遵守這個(gè)規(guī)定,例如 SpiderMonkey 和 Rhino;的實(shí)現(xiàn)中,函數(shù)有一個(gè)特殊的屬性 __parent__,通過這個(gè)屬性可以直接引用到活動(dòng)對(duì)象(或全局變量對(duì)象),在此對(duì)象里創(chuàng)建了函數(shù)。

例如 (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)建的,所以屬性__parent__ 指向全局上下文的變量對(duì)象,即全局對(duì)象。

然而,在SpiderMonkey中用同樣的方式訪問活動(dòng)對(duì)象是不可能的:在不同版本的SpiderMonkey中,內(nèi)部函數(shù)的__parent__ 有時(shí)指向 null ,有時(shí)指向全局對(duì)象。

在 Rhino 中,用同樣的方式訪問活動(dòng)對(duì)象是完全可以的。

例如 (Rhino):

var global = this;
var x = 10;
(function foo() {
  var y = 20;
  // "foo"上下文里的活動(dòng)對(duì)象
  var AO = (function () {}).__parent__;
  print(AO.y); // 20
  // 當(dāng)前活動(dòng)對(duì)象的__parent__ 是已經(jīng)存在的全局對(duì)象
  // 變量對(duì)象的特殊鏈形成了
  // 所以我們叫做作用域鏈
  print(AO.__parent__ === global); // true
  print(AO.__parent__.x); // 10
})();

總結(jié)

在這篇文章里,我們深入學(xué)習(xí)了跟執(zhí)行上下文相關(guān)的對(duì)象。我希望這些知識(shí)對(duì)您來說能有所幫助,能解決一些您曾經(jīng)遇到的問題或困惑。按照計(jì)劃,在后續(xù)的章節(jié)中,我們將探討作用域鏈,標(biāo)識(shí)符解析,閉包。

其它參考

  1. Variable Instantiation;
  2. Global Object;
  3. Activation Object;
  4. Arguments Object.