一险耀、執(zhí)行環(huán)境execution context
定義:
執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù)觉痛,決定了他們各自的行為荠瘪。每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象(VO),環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中铐达。
獨(dú)家解毒:每個函數(shù)都有自己的執(zhí)行環(huán)境岖赋,當(dāng)執(zhí)行進(jìn)入一個函數(shù)時(shí)檬果,函數(shù)的執(zhí)行環(huán)境就會被推入一個執(zhí)行環(huán)境棧的頂部并獲取執(zhí)行權(quán)瓮孙。當(dāng)這個函數(shù)執(zhí)行完畢,它的執(zhí)行環(huán)境又從這個棧的頂部被刪除选脊,并把執(zhí)行權(quán)并還給之前執(zhí)行環(huán)境杭抠。這就是ECMAScript程序中的執(zhí)行流。
也可以這樣解讀:當(dāng)調(diào)用一個 JavaScript 函數(shù)時(shí)恳啥,該函數(shù)就會進(jìn)入與該函數(shù)相對應(yīng)的執(zhí)行環(huán)境偏灿。如果又調(diào)用了另外一個函數(shù),則又會創(chuàng)建一個新的執(zhí)行環(huán)境钝的,并且在函數(shù)調(diào)用期間執(zhí)行過程都處于該環(huán)境中翁垂。當(dāng)調(diào)用的函數(shù)返回后铆遭,執(zhí)行過程會返回原始執(zhí)行環(huán)境。因而沿猜,運(yùn)行中的 JavaScript 代碼就構(gòu)成了一個執(zhí)行環(huán)境棧枚荣。
當(dāng)函數(shù)被調(diào)用時(shí)函數(shù)的局部環(huán)境被創(chuàng)建(函數(shù)內(nèi)的代碼執(zhí)行完畢后,該環(huán)境被銷毀啼肩,同時(shí)保存在其中的所有變量和函數(shù)定義也隨之被銷毀)橄妆。
全局執(zhí)行環(huán)境
全局執(zhí)行環(huán)境是最外圍的一個執(zhí)行環(huán)境。在Web瀏覽器中祈坠,全局執(zhí)行環(huán)境被認(rèn)為是window對象害碾,因此所有全局變量和函數(shù)都是作為window對象的屬性和方法創(chuàng)建的。某個執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后赦拘,該環(huán)境被銷毀慌随,保存在其中的所有變量和函數(shù)定義也隨之銷毀(全局執(zhí)行環(huán)境知道應(yīng)用程序退出–例如關(guān)閉網(wǎng)頁或?yàn)g覽器—時(shí)才會被銷毀)
函數(shù)內(nèi)執(zhí)行環(huán)境
當(dāng)執(zhí)行流進(jìn)入一個函數(shù)時(shí),函數(shù)的環(huán)境就會被推入一個環(huán)境棧中躺同。而在函數(shù)執(zhí)行后儒陨,棧將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境笋籽。
執(zhí)行環(huán)境的建立
創(chuàng)建階段
發(fā)生在函數(shù)調(diào)用時(shí)蹦漠,但在執(zhí)行具體代碼之前。具體完成創(chuàng)建作用域鏈车海、創(chuàng)建變量笛园、函數(shù)和參數(shù)以及求this(講this的時(shí)候講)的值 。
在創(chuàng)建執(zhí)行環(huán)境階段會進(jìn)行函數(shù)的定義侍芝,此時(shí)處于函數(shù)定義期:
? 函數(shù)定義的時(shí)候研铆,都會創(chuàng)建一個[[scope]]屬性,通這個對象對應(yīng)的是一個對象的列表州叠,列表中的對象僅能javascript內(nèi)部訪問棵红,沒法通過語法訪問。
我們定義一全局函數(shù)A咧栗,那么A函數(shù)就創(chuàng)建了一個A的[[scope]]屬性逆甜。此時(shí),[[scope]]里面只包含了全局對象【Global Object】致板。
而如果交煞, 我們在A的內(nèi)部定義一個B函數(shù),那B函數(shù)同樣會創(chuàng)建一個[[scope]]屬性斟或,B的[[scope]]屬性包含了兩個對象素征,一個是A的活動對象Activation Object、一個是全局對象,A的活動對象在前面御毅,全局對象排在后面根欧。
簡而言之,一個函數(shù)的[Scope]屬性中對象列表的順序是上一層函數(shù)的Activation Object對象端蛆,然后是上上層的咽块,一直到最外層的全局對象。
注意這重要的一點(diǎn)--[[scope]]在函數(shù)創(chuàng)建時(shí)被存儲--靜態(tài)(不變的)欺税,永遠(yuǎn)永遠(yuǎn)侈沪,直至函數(shù)銷毀。即:函數(shù)可以永不調(diào)用晚凿,但[[scope]]屬性已經(jīng)寫入亭罪,并存儲在函數(shù)對象中。
* 變量對象VO
每一個執(zhí)行環(huán)境都對應(yīng)一個變量對象歼秽,在該執(zhí)行環(huán)境中定義的所有變量和函數(shù)都存放在其對應(yīng)的變量對象中应役。
(1)進(jìn)入執(zhí)行上下文時(shí),VO的初始化過程如下:
函數(shù)的形參:變量對象的一個屬性燥筷,其屬性名就是形參的名字箩祥,其值就是實(shí)參的值;對于沒有傳遞的參數(shù)肆氓,其值為undefined袍祖;
函數(shù)聲明:變量對象的一個屬性,其屬性名和屬性值都是函數(shù)對象創(chuàng)建出來的谢揪,如果變量對象已經(jīng)辦好了相同名字的屬性蕉陋,則替換它的值
變量聲明:變量對象的一個屬性,其屬性名即為變量名拨扶,其值為undefined凳鬓;如果變量名和已經(jīng)聲明的函數(shù)名或者函數(shù)的參數(shù)名,則不會影響已經(jīng)存在的屬性
(2)執(zhí)行代碼階段患民,變量對象中的一些屬性undefined值將會確定(VO變成AO)
這里需要說明一下:函數(shù)表達(dá)式不包含在變量對象之中
function foo(x, y) {
var z = 30;
function bar() {} // 函數(shù)聲明
(function baz() {}); // 函數(shù)表達(dá)式
}
foo(10, 20);
* 活動對象AO
未進(jìn)入執(zhí)行階段之前缩举,變量對象中的屬性都不能訪問!(具體實(shí)現(xiàn)的引擎可以訪問匹颤,執(zhí)行代碼是不能訪問的)仅孩。但是進(jìn)入執(zhí)行階段之后,變量對象轉(zhuǎn)變?yōu)榱?strong>活動對象惋嚎,里面的屬性都能被訪問了杠氢,然后開始進(jìn)行執(zhí)行階段的操作站刑。所以活動對象實(shí)際就是變量對象在真正執(zhí)行時(shí)的另一種形式另伍。
其實(shí)就是加了一個鎖,函數(shù)中的程序未執(zhí)行的時(shí)候(進(jìn)入上下文階段)有鎖,不能訪問摆尝,而進(jìn)入執(zhí)行階段則可以訪問温艇,變量對象變?yōu)榛顒訉ο蟆?/p>
*作用域?qū)ο骩[scope]]
函數(shù)定義時(shí)創(chuàng)建的一個包含函數(shù)變量方法的可訪問權(quán)限的特殊對象
* 作用域鏈scope chains
作用域鏈的原理和原型鏈很類似,如果這個變量在自己的作用域中沒有堕汞,那么它會尋找父級的勺爱,直到最頂層
可訪問變量或方法scope chains= 該執(zhí)行環(huán)境的AO + [[Scope]]
var x = 10;
(function foo() {
var y = 20;
(function bar() {
var z = 30;
// "x"和"y"是自由變量
// 會在作用域鏈的下一個對象中找到(函數(shù)”bar”的互動對象之后)
console.log(x + y + z);
})();
})();
執(zhí)行階段
主要完成變量賦值、函數(shù)引用和解釋/執(zhí)行其他代碼
總的來說可以將執(zhí)行上下文看作是一個對象
EC(execution context) = {
VO:{/*函數(shù)中的arguments對象讯检、參數(shù)琐鲁、內(nèi)部變量以及函數(shù)聲明*/}
this:{},
Scope:{/*AO以及所有父執(zhí)行上下文中的AO*/}
}
完整Demo:
// 第一步頁面載入創(chuàng)全局執(zhí)行環(huán)境global executing context和全局活動象
// 定義全局[[scope]],只含有Window對象
// 掃描全局的定義變量及函數(shù)對象:color【undefined】、changecolor【FD創(chuàng)建changecolor的[[scope]]人灼,
// 此時(shí)里面 只含有全局活動對象】,加入到window中围段,所以全局變量和全局函數(shù)對象都是做為window的屬性定義的。
// 程序已經(jīng)定義好所以在此執(zhí)行環(huán)境內(nèi)任何位置都可以執(zhí)行changecolor(),color也已經(jīng)被定義投放,但是它的值是undefined
// 第二步color賦值"blue"
var color = "blue";
// 它是不需要賦值的奈泪,它就是引用本身
function changecolor() {
// 第四步進(jìn)入changecolor的執(zhí)行環(huán)境
// 創(chuàng)建活動對象,掃描定義變量和定義函數(shù)灸芳,anothercolor【undefined】和swapcolors【FD創(chuàng)建swapcolors的[[scope]]加入changecolor的活動對象和全局活動對象】加入到活動對象,活動對象中同時(shí)還要加入arguments和this
// 活動對象推入scope chain 頂端
// 程序已經(jīng)定義好所以在此執(zhí)行環(huán)境內(nèi)任何位置都可以執(zhí)行swapcolors(),anothercolor也已經(jīng)被定義好涝桅,但它的值是undefined
// 第五anothercolor賦值"red"
var anothercolor = "red";
// 它是不需要賦值的,它就是引用本身
function swapcolors() {
// 第七步進(jìn)入swapcolors的執(zhí)行環(huán)境烙样,創(chuàng)建它的活動對象
// 復(fù)制swapcolors的[[scope]]到scope chain
// 掃描定義變量和定義函數(shù)對象冯遂,活動對象中加入變量tempcolor【undefined】以及arguments和this
// 活動對象推入scope chain 頂端
// 第八步tempcolor賦值anothercolor,anothercolor和color會沿著scope chain被查到,并繼續(xù)往下執(zhí)行
var tempcolor = anothercolor;
anothercolor = color;
color = tempcolor;
}
// 第六步執(zhí)行swapcolors,進(jìn)入其執(zhí)行環(huán)境
swapcolors();
}
// 第三步執(zhí)行changecolor,進(jìn)入其執(zhí)行環(huán)境
changecolor();