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