摘要:變量對象就是執行上下文和作用域鏈中間的橋梁。作用域鏈和留到后面再講,今天我們先來弄明白變量對象。全局執行上下文環境全局對象全局上下文環境的變量對象引用全局對象自身屬性作用域鏈舉個例子因此,在全局上下文環境中,變量對象用全局對象來表示。
上一節我們討論了執行上下文,那么在上下文中到底有什么內容,為什么它會和作用域鏈扯上關系,JS 解釋器又是怎么找到我們聲明的函數和變量,看完這一節,相信大家就不會再迷惑了。
變量對象就是執行上下文和作用域鏈中間的橋梁。
劇透一下,神秘的 this 就存在于執行上下文環境之中!
當然,之后我會多帶帶用幾節來徹底講明白 this 到底是什么(其實 this 很簡單)。
接下來,我們進入正文。
1. 執行上下文包含什么一個執行上下文我們可以抽象的理解為對象(object)。
每一個執行上下文都有一些屬性(又稱為上下文狀態),它們用來追蹤關聯代碼的執行進度。
我用一個結構圖來說明:
Variable Object 就代表變量對象。
Scope Chain 代表作用域鏈。
thisValue 代表神秘的 this 。
作用域鏈和 this 留到后面再講,今天我們先來弄明白變量對象。
2. 變量對象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) 。
變量對象(Variable Object -- 簡寫 VO)是一個抽象的概念,指代與執行上下文相關的特殊對象,它存儲著在上下文中聲明的:
變量(var)
函數聲明 (function declaration,簡寫 FD)
函數的形參(arguments)
我們假設變量對象為一個普通 ECMAScript 對象:
VO = {};
就像前面講過的,VO 是執行上下文的一個屬性:
activeExecutionContext = { VO: { // 上下文數據 (vars, FD, arguments) } }
因為變量對象是一個抽象的概念,所以并不能通過變量對象的名稱直接訪問,但是卻可以通過別的方法來間接訪問變量對象,例如在全局上下文環境的變量對象會有一個屬性 window (DOM 中) 可以引用變量對象自身,全局上下文環境的另一個屬性 this 也指向全局上下文環境的變量對象。
舉個例子:
var a = 2; function foo (num) { var b = 5; } (function exp () { console.log(111); }) foo(10);
這里對應的變量對象是:
// 全局上下文環境的變量對象 VO(globalContext) = { // 一些全局環境初始化時系統自動創建的屬性: Math、String、Date、parseInt等等 ··· // 全局上下文的變量對象中有一個屬性可以訪問到自身,在瀏覽器中這個屬性是 window ,在 node 中這個屬性是 global window: global // 自己定義的屬性 a: 10, foo:}; // foo 函數上下文的變量對象 VO(foo functionContext) = { num: 10, b: 5 };
注意:函數表達式并不包括在變量對象中。
3. 不同執行上下文中的變量對象執行上下文包括:全局上下文、函數上下文和 eval() 上下文。
這里我們先來了解一下什么是全局對象:
全局對象(global object)是指在進入任何執行上下文之前就已經創建了的對象。 這個對象只有一份,它的屬性在程序中的任何地方都可以訪問,全局對象的生命周期終止于程序退出的那一刻。
全局對象初始化時系統將創建并初始化一系列原始屬性,例如:Math、String、Date、parseInt、window等等,之后是我們在全局上下文中自己定義的全局變量。在 DOM 中,全局對象的 window 屬性可以引用全局對象自身,全局上下文環境的 this 屬性也可以引用全局對象。
// 全局執行上下文環境 EC(globalContext) = { // 全局對象(全局上下文環境的變量對象) global: { Math: <...>, String: <...>, ... ... window: global // 引用全局對象自身 }, // this 屬性 this: global // 作用域鏈 ... }
舉個例子:
var a = 10; console.log(a); // 10 console.log(window.a); // 10 console.log(this.a); // 10
因此,在全局上下文環境中,變量對象用全局對象來表示。
在函數上下文中,變量對象用活動對象 AO(Active Object)來表示。
VO(functionContext) = AO
活動對象是在進入函數上下文時刻被創建的,它是通過函數的 arguments 屬性進行初始化。arguments 也是一個對象。
AO = { arguments: { ... } }
arguments 是活動對象的一個屬性,它也是一個對象,包括以下屬性:
callee - 指向當前函數的引用
length - 真正傳遞的參數個數
properties-indexes - index 是字符串類型的整數,例如"1": "aa",類似于數組類型,也可以通過arguments[1]來訪問,但是不能用數組的方法(push, pop等等)。另外,properties-indexes 的值和實際傳遞進來的參數之間是共享的,一個改變,另一個也隨之改變。
舉個例子:
function foo (x, y, z) { // 聲明的函數參數數量 console.log(foo.length); // 3 // 實際傳遞進來的參數數量 console.log(arguments.length); // 2 // arguments 的 callee 屬性指向當前函數 console.log(arguments.callee === foo) // true // 參數共享 console.log(x === arguments[0]); // true console.log(x); // 10 arguments[0] = 20; console.log(x); // 20 x = 30; console.log(arguments[0]); // 30 // 但是注意,沒有傳遞進來的參數 z ,和第3個索引值是不共享的 z = 40; console.log(arguments[2]); // undefined arguments[2] = 50; console.log(z); // 40 } foo(10, 20);4. 代碼是如何被處理的
在第1節中我們講過js 代碼的編譯過程,其中有一步叫作預編譯,是說在代碼執行前的幾微秒會首先對代碼進行編譯,形成詞法作用域,然后執行。
那么執行上下文的代碼就就可以分成兩個階段來處理:
進入執行上下文(預編譯)
執行代碼
而變量對象的修改變化和這兩個階段是緊密相關的。
并且所有類型的執行上下文都會有這2個階段。
當引擎進入執行上下文時(代碼還未執行),VO 里已經包含了一些屬性:
函數的所有形參(如果是函數執行上下文)
由名稱和對應值組成的一個變量對象的屬性被創建,如果沒有傳遞對應的實參,那么由名稱和 undefined 組成的一種變量對象的屬性也會被創建。
所有的函數聲明(Function Declaration - FD)
由名稱和對應值(函數對象 function object)組成的一個變量對象的屬性被創建,如果變量對象已經存在相同名稱函數的屬性,則完全替換這個屬性。
所有的變量聲明(Variable Declaration - var)
由名稱和對應值(在預編譯階段所有變量值都是 undefined)組成的一個變量對象的屬性被創建,如果變量名和已經聲明的形參或者函數相同,則變量名不會干擾已經存在的這類屬性,如果已經存在相同的變量名,則跳過當前聲明的變量名。
注意:變量碰到相同名稱的變量是忽略,函數碰到相同名稱的函數是覆蓋。
舉個例子:
function foo (a, b) { var c = 5; function bar () {}; var d = function _d () {}; (function f () {}); } foo(10);
當進入帶有實參10的 foo 函數上下文時(預編譯時,此時代碼還沒有執行),AO 結構如下:
AO(foo) = { a: 10, b: undefined, c: undefined, bar:, d: undefined };
注意,函數表達式 f 并不包含在活動對象 AO 內。
也就是說,只有函數聲明會被包含在變量對象 VO 里面,函數表達式并不會影響變量對象。
行內函數表達式 _d 則只能在該函數內部可以使用, 也不會包含在 VO 內。
這之后,就會進入第2個階段,代碼執行階段。
在這個階段,AO/VO 已經有了屬性(并不是所有的屬性都有值,大部分屬性的值還是系統默認的初始值 undefined)。
AO 在代碼執行階段被修改如下:
AO["c"] = 5; AO["d"] =
再次要提醒大家,因為函數表達式 _d 已經保存到了聲明的變量 d 上面,所以變量 d 仍然存在于 VO/AO 中。我們可以通 d() 來執行函數。但是函數表達式 f 卻不存在于 VO/AO 中,也就是說,如果我們想嘗試調用 f 函數,不管在函數定義前還是定義后,都會出現一個錯誤"f is not defined",未保存的函數表達式只有在它自己的定義或遞歸中才能被調用。
再來一個經典例子:
console.log(x); // function var x = 10; console.log(x); // 10 x = 20; function x () {}; console.log(x); // 20
這里為什么是這樣的結果呢?
上邊我們說過,在代碼執行之前的預編譯,會為變量對象生成一些屬性,先是形參,再是函數聲明,最后是變量,并且變量并不會影響同名的函數聲明。
所以,在進入執行上下文時,AO/VO 結構如下:
AO = { x:// 在碰到變量聲明 x 時,因為已經存在了函數聲明 x ,所以會忽略 }
緊接著,在代碼執行階段,AO/VO 被修改如下:
AO["x"] = 10; AO["x"] = 20;
希望大家可以好好理解變量對象,對于理解我們后邊要講的作用域鏈有很大的幫助。
5. 變量有一些文章說過:
不管是使用 var 關鍵字(在全局上下文)還是不使用 var 關鍵字(在任何地方),都可以聲明一個變量。
請記住,這是錯誤的觀念。
任何時候,變量都只能通過使用 var 關鍵字來聲明(ES6 之前)。
a = 10;
上面的賦值語句,僅僅是給全局對象創建了一個新屬性(在在非嚴格模式,嚴格模式下會報錯),但注意,它不是變量。“不是變量”并不是說它不能被改變,而是指它不符合ECMAScript 規范中變量的概念。
讓我們通過一個例子來看一下兩者的區別:
console.log(a); // undefined console.log(b); // 報錯,b is not defined b = 10; var a = 20;
只要我們很好的理解了:變量對象、預編譯階段和執行代碼階段,就可以迅速的給出答案。
預編譯(進入上下文)階段:
VO = { a: undefined }
我們可以看到,因為 b 不是通過 var 聲明的,所以這個階段根本就沒有 b ,b 只有在代碼執行階段才會出現。但是在這個例子中,還沒有執行到 b 那就已經報錯了。
我們稍微更改一下示例代碼:
console.log(a); // undefined b = 10; console.log(b); // 10 代碼執行階段被創建 console.log(window.b); // 10 console.log(this.b); // 10 var a = 20; console.log(a); // 20 代碼執行階段被修改
關于變量,還有一個很重要的知識點。
變量不能用 delete 操作符來刪除。
a = 10; console.log(window.a); // 10 console.log(delete a); // true console.log(window.a); // undefined var b = 20; console.log(window.b); // 20 console.log(delete b); // false console.log(window.b); // 20
注意:這個規則在 eval() 上下文中不起作用。
eval("var a = 10;"); console.log(window.a); // 10 console.log(delete a); // true console.log(window.a); // undefined6. 總結
這一節中我們講了變量對象,下一節就是我們的重頭戲 - 作用域鏈。希望大家可以持續關注我,我們一起進步。
歡迎關注我的公眾號文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81953.html
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結果如下本小節,我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業級應用開發框架,于 2004 年由 Rod Johnson 發布了 1.0 版本。經過十幾年的迭代,現在的 Spring 框架已經非常成熟了...
本回內容介紹 上一回聊到JS中模擬接口,裝飾者模式,摻元類,分析了backbone的繼承源碼,感覺還好吧! 介一回,偶們來聊一下在JS單例模式(singleton),單例模式其實運用很廣泛,比如:jquery,AngularJS,underscore吖蝦米的都是單例模式,來吧,直接開始咯: 1. 單例模式 保證一個類只有一個實例,從全局命名空間里提供一個唯一的訪問點來訪問該對象。其實之前寫過的對象...
摘要:命令用于規定本模塊的對外接口??崭衲K名字符串。其他模塊加載該模塊時,命令可以為該匿名函數指定任意名字。寫法函數聲明命令用在非匿名函數前,也是可以的。加載的時候,視同匿名函數加載。 本文字符數8200+,閱讀時間約16分鐘。 『ES6知識點總結』模塊Module 第一節:Module基本概念 【01】過去使用CommonJS和AMD,前者用于服務器,后者用于瀏覽器。 Module可以取...
摘要:但是它們其實并不是二選一的關系并不是只能用或者。正因為如此,指令有時也被稱為虛擬指令。這是因為是采用基于棧的虛擬機的機制。聲明模塊的全局變量。。下文預告現在你已經了解了模塊的工作原理,下面將會介紹為什么運行的更快。 作者:Lin Clark 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58c77641a6d8...
摘要:前端最基礎的就是。對應,是標簽的屬性。獲取匹配元素相對父元素的偏移。返回的對象包含兩個整型屬性和。一組包含作為動畫屬性和終值的樣式屬性和及其值的集合動畫的額外選項。指示是否在效果隊列中放置動畫。 前端最基礎的就是 HTML+CSS+Javascript。掌握了這三門技術就算入門,但也僅僅是入門,現在前端開發的定義已經遠遠不止這些。前端小課堂(HTML/CSS/JS),本著提升技術水平,...
閱讀 3529·2021-11-22 11:59
閱讀 945·2021-09-27 13:36
閱讀 3603·2021-09-24 09:47
閱讀 2251·2021-09-01 11:39
閱讀 970·2021-08-31 09:37
閱讀 2304·2021-08-05 10:01
閱讀 1665·2019-08-30 15:55
閱讀 693·2019-08-30 15:54