javascript
java函数ao活动对象_JavaScript中的执行上下文和变量对象
執行上下文(Execution Context)
JavaScript代碼執行的過程,包括編譯和執行兩個階段,編譯就是通過詞法分析,構建抽象抽象語法樹,并編譯成機器識別的指令,在JavaScript代碼編譯階段,作用域規則就已經確定了;在代碼執行階段,或者函數一旦調用,便會創建執行上下文(Execution Context),也叫執行環境
在ECMA-262中有如下一段定義
當控制器轉入 ECMA 腳本的可執行代碼時,控制器會進入一個執行環境。當前活動的多個執行環境在邏輯上形成一個棧結構。該邏輯棧的最頂層的執行環境稱為當前運行的執行環境。任何時候,當控制器從當前運行的執行環境相關的可執行代碼轉入與該執行環境無關的可執行代碼時,會創建一個新的執行環境。新建的這個執行環境會推入棧中,成為當前運行的執行環境.
這也是一個抽象的概念,在一段JavaScript代碼中,會創建多個執行上下文,執行上下文定義了變量或函數有權訪問的其他數據, ,通過閱讀規范及相關文檔,了解到執行上下文(簡稱EC)主要包括三個點,用偽代碼表示如下:
EC = {
this: // 綁定this指向為當前執行上下文, 如果函數屬于全局函數,則this指向window
scopeChain: [] // 創建當前執行環境的作用域鏈,
VO: {} // 當前環境的變量對象(Variable Object),每個環境都有一個與之關聯的變量對象
}
看下面這一段代碼:
var a = 1;
function foo() {
var b = 2;
function bar() {
console.log(b)
}
bar()
console.log(a);
}
foo()
1.執行這段代碼,首先會創建全局上下文globleEC,并推入執行上下文棧中;
2.當調用foo()時便會創建foo的上下文fooEC,并推入執行上下文棧中;
3.當調用bar()時便會創建bar的上下文barEC,并推入執行上下文棧中;
4.當bar函數執行完,barEC便會從執行上下文棧中彈出;
5.當foo函數執行完,fooEC便會從執行上下文棧中彈出;
6.在瀏覽器窗口關閉后,全局上下文globleEC便會從執行上下文棧中彈出;
總結: 棧底永遠都是全局上下文,而棧頂就是當前正在執行的上下文
再舉一個例子結合瀏覽器開發者工具來看看到底什么執行上線文
function foo() {
bar()
console.log('foo')
}
function bar() {
baz()
console.log('bar')
}
function baz() {
debugger // 打斷點觀察執行上下文棧中的情況
}
可以看到當前baz正在執行,所以棧頂是baz的執行上下文,而棧底永遠都是Global上下文
繼續執行,baz函數執行完成后,從執行上下文棧頂彈出,繼續執行bar函數內后面的代碼,bar函數執行完,bar的執行上下文從棧中彈出;然后執行foo函數后面的代碼,foo函數執行完后,從執行上下文從棧中彈出;最后全局上下文從執行上下文從棧中彈出,清空執行上下文從棧。
變量對象(Variable Object):
每一個執行環境都有一個與之關聯的變量對象,是一個抽象的概念,環境中定義的所有變量和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數據時會在后臺使用它們。
當瀏覽器第一次加載js腳本程序的時候, 默認進入全局執行環境, 此次的全局環境變量對象為window, 在代碼中可以訪問。
如果環境是函數, 則將此活動對象做為當前上下文的變量對象(VO = AO), 此時變量對象是不可通過代碼來訪問的,下面主要對活動對象進行講解。
活動對象(Activation Object)
1.初始化活動對象(下文縮寫為AO)
當函數一調用,立刻創建當前上下文的活動對象, 并將活動對象作為變量對象,通過arguments屬性初始化,值為arguments對象(傳入的實參集合,與形參無關,形參做為局部環境的局部變量被定義)
AO = {
arguments:
};
arguments對象有以下屬性:
length: 真正傳遞參數的個數;
callee: 指向當前函數的引用,也就是被調用的函數;
'類index': 字符串類型的整數, 值就是arguments對象中對象下標的值,arguments對象應和數組加以區別, 它就是arguments對象,只是能和數組具有相同的length屬性,和可以通過下標來訪問值
function show (a, b, c) {
// 通過Object.prototype.toString.call()精準判斷類型, 證明arguments不同于數組類型
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 傳遞進來實參的個數
console.log(arguments.callee === show) // true 就是被調用的函數show自身
//參數共享
console.log(a === arguments[0]) // true
a = 15;
console.log(arguments[0]) // 15
arguments[0] = 25;
console.log(a) // 25;
但是,對于沒有傳進來的參數c, 和arguments的第三個索引是不共享的
c = 25;
console.log(arguments[2]) // undefined
argument[2] = 35;
console.log(c) // 25
}
show(10, 20);
接著往下走,這才是關鍵的地方,執行環境的代碼被分成兩個階段來處理:
進入執行環境
執行函數的代碼
2.進入執行環境
函數如果被調用, 進入執行環境(上下文),并立即創建活動對象, 通過arguments屬性初始化, 與此同時會掃描執行環境中的所有形參、所有函數聲明、所有變量聲明, 添加到活動對象(AO)中, 并確定this的值,然后會開始執行代碼。
在進入執行環境這個階段:
所有形參聲明:
形參名稱作為活動對象屬性被創建, 如果傳遞實參, 值就為實參值, 如果沒有傳遞參數, 值就為undefined
所有函數聲明:
函數名稱作為活動對象的屬性被創建,值是一個指針在內存中, 指向這個函數,如果變量對象已經存在相同名稱的屬性, 則完全替換。
所有變量聲明:
所有變量名稱作為活動對象的屬性被創建, 值為undefined,但是和函數聲明不同的是, 如果變量名稱跟已經存在的屬性(形式參數和函數)相同、則不會覆蓋
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函數執行上下文時,foo的活動對象fooAO為:
fooAO = {
arguments: {
0: 10,
length: 1
},
a: 10,
b: undefined,
c: undefined,
d: //指向d函數的指針,
e: undefined,
g: undefined,
h: undefined // 雖然else中的代碼永遠不會執行,但是h仍然是活動對象中的屬性
}
這個例子做如下幾點說明:
1.關于函數,只會創建函數聲明作為活動對象的屬性, 而f函數作為函數表達式并不會出現在活動對象(AO)中
2.e雖然值是一個函數, 但是作為變量屬性被活動對象創建
3.代碼執行階段
在進入執行上下文階段,活動對象擁有了屬性,但是很多屬性值為undefined, 到代碼執行階段就開始為這些屬性賦值了
還是上面的代碼例子, 此時活動對象如下:
fooAO = {
arguments: {
0: 10,
length: 1
},
a: 10,
b: undefined,
c: 10, // 賦值為undefined
d: //指向d函數的指針,
e: // 指向e函數的指針
g: 20,
h: undefined // 聲明h變量,但是沒有賦值
}
變量對象包括:{ arguments對象+函數形參+內部變量+函數聲明(但不包含表達式) }
這時這個活動對象, 即作為當前執行環境的變量對象會被推到此執行環境作用域鏈的最前端(作用域鏈本篇不做介紹,會在下一篇文章中單獨講解作用域和作用域鏈), 假定執行環境為一個對象,則整個執行環境可以訪問到的屬性如下:
偽代碼如下:
fooExecutionContext = {
scopeChain: [], //fooAO +所有父執行環境的活動對象,
fooAO: {
arguments: {
0: 10,
length: 1
},
a: 10,
b: undefined,
c: 10, // 賦值為undefined
d: //指向d函數的指針,
e: // 指向e函數的指針
g: 20,
h: undefined
},
this: 當前執行環境的上下文指針
}
補充:
下面的例子為了說明一下變量聲明的順序及變量同名不會影響函數聲明
console.log(foo); // foo的函數體
var foo = 10;
console.log(foo) // 10
function foo() {};
foo = 20;
console.log(foo); // 20
在代碼執行之前, 就會讀取函數聲明,變量聲明的順序在函數聲明和形參聲明之后, 整個流程如下:
1. 進入執行環境階段:
1. var VO = {}
2. VO[foo] = 'foo函數指針'
3. 掃描到var foo = 10,
// 但是foo做為function已經聲明,所以變量聲明不會影響同名的函數聲明,如果代碼中沒有foo函數聲明的話,則foo為undefined
代碼執行階段:
1. VO[foo] = 10;
2. VO[foo] = 20;
解析代碼完成。
以上簡單總結了下對執行上下文和變量對象的理解,主要在于記錄總結一下學習成果,目前文章的水平實在不敢談分享。如有理解不對的地方還請各位大神多多指教,想了解更深可以去查看本文最后主要參考資料的鏈接,都是經典啊,相信看完也就理解了。
本文主要參考資料:
總結
以上是生活随笔為你收集整理的java函数ao活动对象_JavaScript中的执行上下文和变量对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java类加载器 架构 设计_类加载器(
- 下一篇: java jpa注解哪个包好,Sprin