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