摘要:以上簡單總結(jié)了下對執(zhí)行上下文和變量對象的理解,主要在于記錄總結(jié)一下學(xué)習(xí)成果,目前文章的水平實在不敢談分享。
執(zhí)行上下文(Execution Context)
文章同步到github javaScript中的執(zhí)行上下文和變量對象
JavaScript代碼執(zhí)行的過程,包括編譯和執(zhí)行兩個階段,編譯就是通過詞法分析,構(gòu)建抽象抽象語法樹,并編譯成機器識別的指令,在JavaScript代碼編譯階段,作用域規(guī)則就已經(jīng)確定了;在代碼執(zhí)行階段,或者函數(shù)一旦調(diào)用,便會創(chuàng)建執(zhí)行上下文(Execution Context),也叫執(zhí)行環(huán)境
在ECMA-262中有如下一段定義
當(dāng)控制器轉(zhuǎn)入 ECMA 腳本的可執(zhí)行代碼時,控制器會進入一個執(zhí)行環(huán)境。當(dāng)前活動的多個執(zhí)行環(huán)境在邏輯上形成一個棧結(jié)構(gòu)。該邏輯棧的最頂層的執(zhí)行環(huán)境稱為當(dāng)前運行的執(zhí)行環(huán)境。任何時候,當(dāng)控制器從當(dāng)前運行的執(zhí)行環(huán)境相關(guān)的可執(zhí)行代碼轉(zhuǎn)入與該執(zhí)行環(huán)境無關(guān)的可執(zhí)行代碼時,會創(chuàng)建一個新的執(zhí)行環(huán)境。新建的這個執(zhí)行環(huán)境會推入棧中,成為當(dāng)前運行的執(zhí)行環(huán)境.
這也是一個抽象的概念,在一段JavaScript代碼中,會創(chuàng)建多個執(zhí)行上下文,執(zhí)行上下文定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù), ,通過閱讀規(guī)范及相關(guān)文檔,了解到執(zhí)行上下文(簡稱EC)主要包括三個點,用偽代碼表示如下:
EC = { this: // 綁定this指向為當(dāng)前執(zhí)行上下文, 如果函數(shù)屬于全局函數(shù),則this指向window scopeChain: [] // 創(chuàng)建當(dāng)前執(zhí)行環(huán)境的作用域鏈, VO: {} // 當(dāng)前環(huán)境的變量對象(Variable Object),每個環(huán)境都有一個與之關(guān)聯(lián)的變量對象 }
看下面這一段代碼:
var a = 1; function foo() { var b = 2; function bar() { console.log(b) } bar() console.log(a); } foo()
1.執(zhí)行這段代碼,首先會創(chuàng)建全局上下文globleEC,并推入執(zhí)行上下文棧中;
2.當(dāng)調(diào)用foo()時便會創(chuàng)建foo的上下文fooEC,并推入執(zhí)行上下文棧中;
3.當(dāng)調(diào)用bar()時便會創(chuàng)建bar的上下文barEC,并推入執(zhí)行上下文棧中;
4.當(dāng)bar函數(shù)執(zhí)行完,barEC便會從執(zhí)行上下文棧中彈出;
5.當(dāng)foo函數(shù)執(zhí)行完,fooEC便會從執(zhí)行上下文棧中彈出;
6.在瀏覽器窗口關(guān)閉后,全局上下文globleEC便會從執(zhí)行上下文棧中彈出;
總結(jié): 棧底永遠都是全局上下文,而棧頂就是當(dāng)前正在執(zhí)行的上下文
再舉一個例子結(jié)合瀏覽器開發(fā)者工具來看看到底什么執(zhí)行上線文
function foo() { bar() console.log("foo") } function bar() { baz() console.log("bar") } function baz() { debugger // 打斷點觀察執(zhí)行上下文棧中的情況 }
可以看到當(dāng)前baz正在執(zhí)行,所以棧頂是baz的執(zhí)行上下文,而棧底永遠都是Global上下文
繼續(xù)執(zhí)行,baz函數(shù)執(zhí)行完成后,從執(zhí)行上下文棧頂彈出,繼續(xù)執(zhí)行bar函數(shù)內(nèi)后面的代碼,bar函數(shù)執(zhí)行完,bar的執(zhí)行上下文從棧中彈出;然后執(zhí)行foo函數(shù)后面的代碼,foo函數(shù)執(zhí)行完后,從執(zhí)行上下文從棧中彈出;最后全局上下文從執(zhí)行上下文從棧中彈出,清空執(zhí)行上下文從棧。
變量對象(Variable Object):每一個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象,是一個抽象的概念,環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數(shù)據(jù)時會在后臺使用它們。
當(dāng)瀏覽器第一次加載js腳本程序的時候, 默認進入全局執(zhí)行環(huán)境, 此次的全局環(huán)境變量對象為window, 在代碼中可以訪問。
如果環(huán)境是函數(shù), 則將此活動對象做為當(dāng)前上下文的變量對象(VO = AO), 此時變量對象是不可通過代碼來訪問的,下面主要對活動對象進行講解。
活動對象(Activation Object) 1.初始化活動對象(下文縮寫為AO)當(dāng)函數(shù)一調(diào)用,立刻創(chuàng)建當(dāng)前上下文的活動對象, 并將活動對象作為變量對象,通過arguments屬性初始化,值為arguments對象(傳入的實參集合,與形參無關(guān),形參做為局部環(huán)境的局部變量被定義)
AO = { arguments:};
arguments對象有以下屬性:
length: 真正傳遞參數(shù)的個數(shù);
callee: 指向當(dāng)前函數(shù)的引用,也就是被調(diào)用的函數(shù);
"類index": 字符串類型的整數(shù), 值就是arguments對象中對象下標的值,arguments對象應(yīng)和數(shù)組加以區(qū)別, 它就是arguments對象,只是能和數(shù)組具有相同的length屬性,和可以通過下標來訪問值
function show (a, b, c) { // 通過Object.prototype.toString.call()精準判斷類型, 證明arguments不同于數(shù)組類型 var arr = [1, 2, 3]; console.log(Object.prototype.toString.call(arr)); // [object Array] console.log(Object.prototype.toString.call(arguments)); // [object Arguments] console.log(arguments.length) // 2 傳遞進來實參的個數(shù) console.log(arguments.callee === show) // true 就是被調(diào)用的函數(shù)show自身 //參數(shù)共享 console.log(a === arguments[0]) // true a = 15; console.log(arguments[0]) // 15 arguments[0] = 25; console.log(a) // 25; 但是,對于沒有傳進來的參數(shù)c, 和arguments的第三個索引是不共享的 c = 25; console.log(arguments[2]) // undefined argument[2] = 35; console.log(c) // 25 } show(10, 20);
接著往下走,這才是關(guān)鍵的地方,執(zhí)行環(huán)境的代碼被分成兩個階段來處理:
進入執(zhí)行環(huán)境
執(zhí)行函數(shù)的代碼
2.進入執(zhí)行環(huán)境函數(shù)如果被調(diào)用, 進入執(zhí)行環(huán)境(上下文),并立即創(chuàng)建活動對象, 通過arguments屬性初始化, 與此同時會掃描執(zhí)行環(huán)境中的所有形參、所有函數(shù)聲明、所有變量聲明, 添加到活動對象(AO)中, 并確定this的值,然后會開始執(zhí)行代碼。
在進入執(zhí)行環(huán)境這個階段:
所有形參聲明:
形參名稱作為活動對象屬性被創(chuàng)建, 如果傳遞實參, 值就為實參值, 如果沒有傳遞參數(shù), 值就為undefined
所有函數(shù)聲明:
函數(shù)名稱作為活動對象的屬性被創(chuàng)建,值是一個指針在內(nèi)存中, 指向這個函數(shù),如果變量對象已經(jīng)存在相同名稱的屬性, 則完全替換。
所有變量聲明:
所有變量名稱作為活動對象的屬性被創(chuàng)建, 值為undefined,但是和函數(shù)聲明不同的是, 如果變量名稱跟已經(jīng)存在的屬性(形式參數(shù)和函數(shù))相同、則不會覆蓋
function foo(a, b) { var c = 10; function d() { console.log("d"); } var e = function () { console.log("e"); }; (function f() {}) if (true) { var g = 20; } else { var h = 30; } } foo(10);
此時在進入foo函數(shù)執(zhí)行上下文時,foo的活動對象fooAO為:
fooAO = { arguments: { 0: 10, length: 1 }, a: 10, b: undefined, c: undefined, d://指向d函數(shù)的指針, e: undefined, g: undefined, h: undefined // 雖然else中的代碼永遠不會執(zhí)行,但是h仍然是活動對象中的屬性 }
這個例子做如下幾點說明:
1.關(guān)于函數(shù),只會創(chuàng)建函數(shù)聲明作為活動對象的屬性, 而f函數(shù)作為函數(shù)表達式并不會出現(xiàn)在活動對象(AO)中
2.e雖然值是一個函數(shù), 但是作為變量屬性被活動對象創(chuàng)建
3.代碼執(zhí)行階段在進入執(zhí)行上下文階段,活動對象擁有了屬性,但是很多屬性值為undefined, 到代碼執(zhí)行階段就開始為這些屬性賦值了
還是上面的代碼例子, 此時活動對象如下:
fooAO = { arguments: { 0: 10, length: 1 }, a: 10, b: undefined, c: 10, // 賦值為undefined d://指向d函數(shù)的指針, e: // 指向e函數(shù)的指針 g: 20, h: undefined // 聲明h變量,但是沒有賦值 }
變量對象包括:{ arguments對象+函數(shù)形參+內(nèi)部變量+函數(shù)聲明(但不包含表達式) }
這時這個活動對象, 即作為當(dāng)前執(zhí)行環(huán)境的變量對象會被推到此執(zhí)行環(huán)境作用域鏈的最前端(作用域鏈本篇不做介紹,會在下一篇文章中多帶帶講解作用域和作用域鏈), 假定執(zhí)行環(huán)境為一個對象,則整個執(zhí)行環(huán)境可以訪問到的屬性如下:
偽代碼如下:
fooExecutionContext = { scopeChain: [], //fooAO +所有父執(zhí)行環(huán)境的活動對象, fooAO: { arguments: { 0: 10, length: 1 }, a: 10, b: undefined, c: 10, // 賦值為undefined d://指向d函數(shù)的指針, e: // 指向e函數(shù)的指針 g: 20, h: undefined }, this: 當(dāng)前執(zhí)行環(huán)境的上下文指針 }
補充:
下面的例子為了說明一下變量聲明的順序及變量同名不會影響函數(shù)聲明
console.log(foo); // foo的函數(shù)體 var foo = 10; console.log(foo) // 10 function foo() {}; foo = 20; console.log(foo); // 20
在代碼執(zhí)行之前, 就會讀取函數(shù)聲明,變量聲明的順序在函數(shù)聲明和形參聲明之后, 整個流程如下:
1. 進入執(zhí)行環(huán)境階段:
1. var VO = {} 2. VO[foo] = "foo函數(shù)指針" 3. 掃描到var foo = 10, // 但是foo做為function已經(jīng)聲明,所以變量聲明不會影響同名的函數(shù)聲明,如果代碼中沒有foo函數(shù)聲明的話,則foo為undefined
代碼執(zhí)行階段:
1. VO[foo] = 10; 2. VO[foo] = 20;
解析代碼完成。
以上簡單總結(jié)了下對執(zhí)行上下文和變量對象的理解,主要在于記錄總結(jié)一下學(xué)習(xí)成果,目前文章的水平實在不敢談分享。如有理解不對的地方還請各位大神多多指教,想了解更深可以去查看本文最后主要參考資料的鏈接,都是經(jīng)典啊,相信看完也就理解了。
本文主要參考資料:
JavaScript高級程序設(shè)計(第3版)
ECMAScript5.1中文版--執(zhí)行環(huán)境
前端基礎(chǔ)進階(二):執(zhí)行上下文詳細圖解
前端基礎(chǔ)進階(三):變量對象詳解
深入理解JavaScript系列(11):執(zhí)行上下文(Execution Contexts)
深入理解JavaScript系列(12):變量對象(Variable Object)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/108133.html
摘要:變量對象作用域鏈因為變量對象在執(zhí)行上下文進入執(zhí)行階段時,就變成了活動對象,因此圖中使用了來表示。 作用域 作用域就是變量與函數(shù)的可訪問范圍,即作用域控制著變量與函數(shù)的可見性和生命周期。在 JavaScript 中,變量的作用域有全局作用域和局部作用域兩種。JavaScript 采用詞法作用域(lexical scoping),也就是靜態(tài)作用域。 靜態(tài)作用域 函數(shù)的作用域在函數(shù)定義的時候...
摘要:變量對象作用域鏈因為變量對象在執(zhí)行上下文進入執(zhí)行階段時,就變成了活動對象,因此圖中使用了來表示。 作用域 作用域就是變量與函數(shù)的可訪問范圍,即作用域控制著變量與函數(shù)的可見性和生命周期。在 JavaScript 中,變量的作用域有全局作用域和局部作用域兩種。JavaScript 采用詞法作用域(lexical scoping),也就是靜態(tài)作用域。 靜態(tài)作用域 函數(shù)的作用域在函數(shù)定義的時候...
摘要:深入系列第四篇,具體講解執(zhí)行上下文中的變量對象與活動對象。下一篇文章深入之作用域鏈本文相關(guān)鏈接深入之執(zhí)行上下文棧深入系列深入系列目錄地址。 JavaScript深入系列第四篇,具體講解執(zhí)行上下文中的變量對象與活動對象。全局上下文下的變量對象是什么?函數(shù)上下文下的活動對象是如何分析和執(zhí)行的?還有兩個思考題幫你加深印象,快來看看吧! 前言 在上篇《JavaScript深入之執(zhí)行上下文?!分?..
摘要:在中,一個未使用明確標識符的函數(shù)被稱為一個匿名函數(shù)。記住在中,由關(guān)鍵字聲明的變量是一個局部變量,而忽略了這個關(guān)鍵字則會創(chuàng)建一個全局變量。函數(shù)被賦值給一個局部變量,在外部無法訪問它。這個函數(shù)表達式的變種被稱為一個命名的函數(shù)表達式。 本文是@堂主 對《Pro JavaScript with Mootools》一書的第二章函數(shù)部分知識點講解的翻譯。該書的作者 Mark Joseph Obce...
閱讀 2787·2021-11-19 11:30
閱讀 3069·2021-11-15 11:39
閱讀 1793·2021-08-03 14:03
閱讀 1999·2019-08-30 14:18
閱讀 2055·2019-08-30 11:16
閱讀 2169·2019-08-29 17:23
閱讀 2611·2019-08-28 18:06
閱讀 2545·2019-08-26 12:22