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