摘要:變量對象作用域鏈因為變量對象在執行上下文進入執行階段時,就變成了活動對象,因此圖中使用了來表示。
作用域
作用域就是變量與函數的可訪問范圍,即作用域控制著變量與函數的可見性和生命周期。在 JavaScript 中,變量的作用域有全局作用域和局部作用域兩種。JavaScript 采用詞法作用域(lexical scoping),也就是靜態作用域。
靜態作用域函數的作用域在函數定義的時候就決定了。
js函數有一個內部屬性 [[scope]],當函數創建的時候,就會保存所有父變量對象到其中,下文會詳細描述
函數的作用域是在函數調用的時候才決定的。
實例靜態作用域的語言下面的代碼會打出1,因為在foo定義的時候,他的作用域就確定了在全局(后面講變量對象的時候也會說foo是注冊在全局的而不是在bar里面才注冊)
執行 foo 函數,先從 foo 函數內部查找是否有局部變量 value,如果沒有,就根據書寫的位置,查找上面一層的代碼,也就是 value 等于 1,所以結果會打印 1。
var value = 1; function foo() { console.log(value); } function bar() { var value = 2; foo(); } bar();執行上下文 執行上下文(Execution Context)
就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。
JavaScript代碼的整個執行過程,分為兩個階段,代碼編譯階段與代碼執行階段。
編譯階段由編譯器完成,將代碼翻譯成可執行代碼,這個階段作用域規則會確定。
執行階段由引擎完成,主要任務是執行可執行代碼,執行上下文在這個階段創建。
執行上下文創建和執行:執行上下文有以下三個屬性
變量對象(Variable object,VO)
作用域鏈(Scope chain)
this
執行上下文總共有三種類型:全局執行上下文: 這是默認的、最基礎的執行上下文。不在任何函數中的代碼都位于全局執行上下文中。它做了兩件事:1. 創建一個全局對象,在瀏覽器中這個全局對象就是 window 對象。2. 將 this 指針指向這個全局對象。一個程序中只能存在一個全局執行上下文。
函數執行上下文: 每次調用函數時,都會為該函數創建一個新的執行上下文。每個函數都擁有自己的執行上下文,但是只有在函數被調用的時候才會被創建。一個程序中可以存在任意數量的函數執行上下文。每當一個新的執行上下文被創建,它都會按照特定的順序執行一系列步驟,具體過程將在本文后面討論。
Eval 函數執行上下文: 運行在 eval 函數中的代碼也獲得了自己的執行上下文(不常用)
執行上下文棧JavaScript 引擎創建了執行上下文棧(Execution context stack,ECS)來管理執行上下文
當 JavaScript 引擎首次讀取你的腳本時,它會創建一個全局執行上下文并將其推入當前的執行棧。每當發生一個函數調用,引擎都會為該函數創建一個新的執行上下文并將其推到當前執行棧的頂端。
引擎會運行執行上下文在執行棧頂端的函數,當此函數運行完成后,其對應的執行上下文將會從執行棧中彈出,上下文控制權將移到當前執行棧的下一個執行上下文。
let a = "Hello World!"; function first() { console.log("Inside first function"); second(); console.log("Again inside first function"); } function second() { console.log("Inside second function"); } first(); console.log("Inside Global Execution Context");
瀏覽器中加載時,JavaScript 引擎會創建一個全局執行上下文并且將它推入當前的執行棧。
當調用 first() 函數時,JavaScript 引擎為該函數創建了一個新的執行上下文并將其推到當前執行棧的頂端。
當在 first() 函數中調用 second() 函數時,創建了一個新的執行上下文并將其推到當前執行棧的頂端。
當 second() 函數執行完成后,它的執行上下文從當前執行棧中彈出,上下文控制權將移到當前執行棧的下一個執行上下文,即 first() 函數的執行上下文。
當 first() 函數執行完成后,它的執行上下文從當前執行棧中彈出,上下文控制權將移到全局執行上下文。
一旦所有代碼執行完畢,Javascript 引擎把全局執行上下文從執行棧中移除。
// 偽代碼 ECStack = [ globalContext ]; // first() ECStack.push(變量對象 什么是變量對象functionContext); // fun1中竟然調用了fun2,還要創建fun2的執行上下文 ECStack.push( functionContext); // second()執行完畢 ECStack.pop(second); // first()執行完畢 ECStack.pop(first); // 當整個應用程序結束的時候,ECStack 才會被清空,所以程序結束之前, ECStack 最底部永遠有個 globalContext:
變量對象是與執行上下文相關的數據作用域,存儲了在上下文中定義的變量和函數聲明。
什么是全局對象全局對象是預定義的對象,作為 JavaScript 的全局函數和全局屬性的占位符。通過使用全局對象,可以訪問所有其他所有預定義的對象、函數和屬性。
在頂層 JavaScript 代碼中,可以用關鍵字 this 引用全局對象。因為全局對象是作用域鏈的頭,這意味著所有非限定性的變量和函數名都會作為該對象的屬性來查詢。
例如,當JavaScript 代碼引用 parseInt() 函數時,它引用的是全局對象的 parseInt 屬性。全局對象是作用域鏈的頭,還意味著在頂層 JavaScript 代碼中聲明的所有變量都將成為全局對象的屬性。
可以通過 this 引用,在客戶端 JavaScript 中,全局對象就是 Window 對象。
console.log(this);// this 引用,在客戶端 JavaScript 中,全局對象就是 Window 對象。 console.log(this instanceof Object);//全局對象是由 Object 構造函數實例化的一個對象。 console.log(Math.random());//.預定義了一堆,嗯,一大堆函數和屬性。 console.log(this.Math.random()); var a = 1;//作為全局變量的宿主。 console.log(this.a);函數上下文
在函數上下文中,我們用活動對象(activation object, AO)來表示變量對象。
變量對象VO和活動對象AO是同一個對象在不同階段的表現形式。當進入執行環境的創捷階段時,變量對象被創建,這時變量對象的屬性無法被訪問。進入執行階段后,變量對象被激活變成活動對象,此時活動對象的屬性可以被訪問。
當進入執行上下文時,這時候還沒有執行代碼,在這個階段中,執行上下文會分別創建變量對象,建立作用域鏈,以及確定this的指向。
變量對象會包括:
函數的所有形參 (如果是函數上下文)
函數聲明
變量聲明
function foo(a) { var b = 2; var c=3; function c() {} var d = function() {}; b = 3; } foo(1);
根據函數參數,創建并初始化arguments對象,及形參屬性
檢查上下文中的函數聲明,將函數名作為變量對象的屬性,函數引用作為值。如果該函數名在變量對象中已存在,則覆蓋已存在的函數引用。
檢查上下文的變量聲明,將變量名作為變量對象的屬性,值設置為undefined。如果該變量名在變量對象中已存在,為防止與函數名沖突,則跳過,不進行任何操作。
AO = { arguments: { 0: 1, length: 1 }, a: 1,//注意a已經初始化了 b: undefined, c: reference to function c(){},//如果重名后調過了變量c只有函數c d: undefined }代碼執行階段
上下文創建完成之后,就會開始執行代碼,這個時候,會完成變量賦值,函數引用,以及執行其他代碼。
AO = { arguments: { 0: 1, length: 1 }, a: 1, b: 3, c: 3,//執行階段c又會重新被賦值 d: reference to FunctionExpression "d" }上下文總結
全局上下文的變量對象初始化是全局對象
函數上下文創建階段函數先注冊重名覆蓋,變量后注冊重名跳過
函數上下文的變量對象初始化只包括 Arguments 對象
在進入執行上下文時會給變量對象添加形參、函數聲明、變量聲明等初始的屬性值,也就是初始化變量對象
在代碼執行階段,會再次修改變量對象的屬性值
作用域鏈 什么是作用域鏈 定義作用域鏈,是由當前環境與上層環境的一系列變量對象組成,它保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。
形成上文的作用域中講到過函數的作用域在函數定義的時候就決定了,因為函數有一個內部屬性 [[scope]],當函數創建的時候,就會保存所有父變量對象到其中,當查找變量的時候,會先從當前上下文的變量對象中查找,如果沒有找到,就會從自己的scope中保存的父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫做作用域鏈。
區分作用域與作用域鏈 作用域在JavaScript中,我們可以將作用域定義為一套規則,這套規則用來管理引擎如何在當前作用域以及嵌套的子作用域中根據標識符名稱進行變量查找。
兩者的區別作用域是一套規則,那么作用域鏈是什么呢?是這套規則的具體實現。
作用域規則在代碼編譯階段就確定了,而作用域鏈是在執行上下文的創建階段生成的
舉個例子var a = 20; function test() { var b = 10; //function innerTest() { // var c = 10; // return b + c; //} return b; } test();
執行過程
1.test 函數在全局上下文中被創建,保存全局上下文的變量對象組成的作用域鏈到內部屬性[[scope]]
test.[[scope]] = [ globalContext.VO ];
2.創建 test 函數執行上下文,test函數執行上下文被壓入執行上下文棧
ECStack = [ testContext, globalContext ];
3.test 函數并不立刻執行,開始做準備工作,第一步:復制[[scope]]屬性到函數上下文,創建了作用域鏈
testContext = { Scope: testscope.[[scope]], }
4.第二步:用 arguments 創建活動對象,隨后初始化活動對象,加入形參、函數聲明、變量聲明
testscopeContext = { AO: { arguments: { length: 0 }, b: undefined }, Scope: testscope.[[scope]], }
5.第三步:將活動對象壓入 testscope 作用域鏈頂端
testscopeContext = { AO: { arguments: { length: 0 }, b: undefined }, Scope: [AO, [[Scope]]]//用Scope簡寫testscope.[[scope]] }
6.準備工作做完,開始執行函數,隨著函數的執行,修改 AO 的屬性值
testscopeContext = { AO: { arguments: { length: 0 }, b: 10 }, Scope: [AO, [[Scope]]] }
7.查找到 b 的值,返回后函數執行完畢,函數上下文從執行上下文棧中彈出
ECStack = [ globalContext ];
8.如果test內部含有innerTest函數,則在該innerTest函數創建時將test上下文中的作用域鏈傳入(testscopeContext.Scope)
然后后循環執行和test相同的步驟
var a = 20; function test() { var b = 10; function innerTest() { var c = 10; return b + c; } return b; } test();
全局,函數test,函數innerTest的執行上下文先后創建。我們設定他們的變量對象分別為VO(global),VO(test), VO(innerTest)。而innerTest的作用域鏈,則同時包含了這三個變量對象,所以innerTest的執行上下文可如下表示。
innerTestContext = { AO: {...}, // 變量對象 Scope: [VO(innerTest), VO(test), VO(global)], // 作用域鏈 }
因為變量對象在執行上下文進入執行階段時,就變成了活動對象,因此圖中使用了AO來表示。Active Object
作用域鏈是由一系列變量對象組成,我們可以在這個單向通道中,查詢變量對象中的標識符,這樣就可以訪問到上一層作用域中的變量了。
https://github.com/mqyqingfen...
https://www.jianshu.com/p/21a...
https://juejin.im/post/5bdfd3...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/54225.html
摘要:變量對象作用域鏈因為變量對象在執行上下文進入執行階段時,就變成了活動對象,因此圖中使用了來表示。 作用域 作用域就是變量與函數的可訪問范圍,即作用域控制著變量與函數的可見性和生命周期。在 JavaScript 中,變量的作用域有全局作用域和局部作用域兩種。JavaScript 采用詞法作用域(lexical scoping),也就是靜態作用域。 靜態作用域 函數的作用域在函數定義的時候...
摘要:示例當一個函數創建后,它的作用域鏈會被創建此函數的作用域中可訪問的數據對象填充。每一個運行期上下文都和一個作用域鏈關聯。此時,作用域鏈中函數的所有局部變量所在的作用域對象會被推后,訪問代價變高了。 作用域 作用域就是變量與函數的可訪問范圍,即作用域控制著變量與函數的可見性和生命周期。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。 作用域鏈 函數對象有一個內部屬性[...
摘要:下面,讓我們以一個函數的創建和激活兩個時期來講解作用域鏈是如何創建和變化的。這時候執行上下文的作用域鏈,我們命名為至此,作用域鏈創建完畢。 JavaScript深入系列第五篇,講述作用鏈的創建過程,最后結合著變量對象,執行上下文棧,讓我們一起捋一捋函數創建和執行的過程中到底發生了什么? 前言 在《JavaScript深入之執行上下文棧》中講到,當JavaScript代碼執行一段可執行代...
摘要:并且作用域鏈也確定了在當前上下文中查找標識符后返回的值。為了具象化分析問題,我們可以假設作用域鏈是一個數組,數組成員有一系列變量對象組成。注意,所有作用域鏈的最末端都為全局變量對象。所以作用域作用域鏈都是在當前運行環境內代碼執行前就確定了。 什么是作用域(Scope)? 作用域產生于程序源代碼中定義變量的區域,在程序編碼階段就確定了。javascript 中分為全局作用域(Global...
閱讀 1990·2021-09-09 09:33
閱讀 1117·2019-08-30 15:43
閱讀 2670·2019-08-30 13:45
閱讀 3311·2019-08-29 11:00
閱讀 862·2019-08-26 14:01
閱讀 3574·2019-08-26 13:24
閱讀 487·2019-08-26 11:56
閱讀 2694·2019-08-26 10:27