javascript
大话javascript 2期:执行上下文与执行上下文栈
一、什么是執(zhí)行上下文?
執(zhí)行上下文(Execution Context): 函數(shù)執(zhí)行前進行的準備工作(也稱執(zhí)行上下文環(huán)境)JavaScript在執(zhí)行一個“代碼段”之前,即解析(預(yù)處理)階段,會先進行一些“準備工作”,例如掃描JS中var定義的變量、函數(shù)名等,進而生成執(zhí)行上下文。
| 變量對象(VO, variable object) | 當(dāng)前函數(shù)定義的變量、函數(shù)、參數(shù) |
| 作用域鏈(Scope chain) | 源代碼定義時形成的作用域鏈 |
| this |
JS中的“代碼段”分為三種:全局代碼段、函數(shù)體代碼段、eval代碼段。(注:ES6之前,JS不存在“代碼塊”作用域的概念,即除了函數(shù)之外所有“{}”里的代碼,都屬于全局作用域)
全局代碼段“準備工作”包括:
1.變量、函數(shù)表達式 —— 變量聲明,默認賦值為undefined; 2.this —— 賦值; 3.函數(shù)聲明 —— 賦值。函數(shù)體代碼段“準備工作”包括:
1.變量、函數(shù)表達式 —— 變量聲明,默認賦值為undefined; 2.this —— 賦值; 3.函數(shù)聲明 —— 賦值; 4.參數(shù) —— 賦值; 5.argument —— 賦值; 6.自由變量的取值作用域 —— 賦值。evel()不推薦使用,所以不再分析evel代碼段。
至此,“執(zhí)行上下文”的定義可以通俗化為 —— 在執(zhí)行代碼段之前(預(yù)處理階段),把將要用到的所有變量都事先拿出來,有的直接賦值,有的先用undefined占個空,這些變量共同組成的詞法環(huán)境,即為執(zhí)行上下文環(huán)境。
二、執(zhí)行上下文棧
javaScript是單線程語言,簡單理解下單線程,就是同個時間段只能做一件任務(wù),完成之后才可以繼續(xù)下一個任務(wù)。
函數(shù)編程中,代碼中會聲明多個函數(shù),對應(yīng)的執(zhí)行上下文也會存在多個。在JavaScript中,通過棧的存取方式來管理執(zhí)行上下文,我們可稱其為執(zhí)行棧,或函數(shù)調(diào)用棧(Call Stack)。
1.棧數(shù)據(jù)結(jié)構(gòu)
要簡單理解棧的存取方式,我們可以通過類比乒乓球盒子來分析。如下圖左側(cè)。
棧遵循"先進后出,后進先出"的規(guī)則,或稱LIFO ("Last In First Out") 規(guī)則。
如圖所示,我們只能從棧頂取出或放入乒乓球,最先放進盒子的總是最后才能取出。
棧中"放入/取出",也可稱為"入棧/出棧"。
總結(jié)棧數(shù)據(jù)結(jié)構(gòu)的特點:
- 后進先出,先進后出
- 出口在頂部,且僅有一個
2.執(zhí)行上下文棧ECS(Execution Context Stack)(函數(shù)調(diào)用棧)
程序執(zhí)行進入一個執(zhí)行環(huán)境時,它的執(zhí)行上下文就會被創(chuàng)建,并被推入執(zhí)行棧中(入棧);程序執(zhí)行完成時,它的執(zhí)行上下文就會被銷毀,并從棧頂被推出(出棧),控制權(quán)交由下一個執(zhí)行上下文。
因為JS執(zhí)行中最先進入全局環(huán)境,所以處于"棧底的永遠是全局環(huán)境的執(zhí)行上下文"。而處于"棧頂?shù)氖钱?dāng)前正在執(zhí)行函數(shù)的執(zhí)行上下文",當(dāng)函數(shù)調(diào)用完成后,它就會從棧頂被推出(理想的情況下,閉包會阻止該操作,閉包后續(xù)文章深入詳解)。
"全局環(huán)境只有一個,對應(yīng)的全局執(zhí)行上下文也只有一個,只有當(dāng)頁面被關(guān)閉之后它才會從執(zhí)行棧中被推出,否則一直存在于棧底"
function foo () {function bar () {return 'I am bar';}return bar(); } foo();
3.執(zhí)行上下文的生命周期
執(zhí)行上下文的生命周期有兩個階段:
創(chuàng)建階段:函數(shù)被調(diào)用時,進入函數(shù)環(huán)境,為其創(chuàng)建一個執(zhí)行上下文,此時進入創(chuàng)建階段
執(zhí)行階段:執(zhí)行函數(shù)中代碼時,此時執(zhí)行上下文進入執(zhí)行階段
創(chuàng)建階段的操作
創(chuàng)建變量對象
- 函數(shù)環(huán)境會初始化創(chuàng)建Arguments對象(并賦值)
- 函數(shù)聲明(并賦值)
- 變量聲明,函數(shù)表達式聲明(未賦值)
執(zhí)行階段的操作
變量對象賦值
- 變量賦值
- 函數(shù)表達式賦值
2.調(diào)用函數(shù)
3.順序執(zhí)行其它代碼
變量對象和活動對象的區(qū)別:
當(dāng)進入到一個執(zhí)行上下文后,這個變量對象才會被激活,所以叫活動對象(AO),這時候活動對象上的各種屬性才能被訪問。
"創(chuàng)建階段對函數(shù)聲明做賦值,變量及函數(shù)表達式僅做聲明,真正的賦值操作要等到執(zhí)行上下文代碼執(zhí)行階段"。
代碼例子1:變量提升
function foo() {console.log(a); // 輸出undefinedvar a = 'I am here'; // 賦值 } foo();// 實際執(zhí)行過程
function foo() {var a; // 變量聲明,var初始化undefinedconsole.log(a); a = 'I am here'; // 變量重新賦值 }代碼例子2:函數(shù)聲明優(yōu)先級
function foo() {console.log(bar);var bar = 20;function bar() {return 10;}var bar = function() {return 30;} } foo(); // 輸出bar()整個函數(shù)聲明函數(shù)聲明,變量聲明,函數(shù)表達式的優(yōu)先級
4.執(zhí)行上下文的數(shù)量限制(堆棧溢出)
執(zhí)行上下文可存在多個,雖然沒有明確的數(shù)量限制,但如果超出棧分配的空間,會造成堆棧溢出。常見于遞歸調(diào)用,沒有終止條件造成死循環(huán)的場景。 // 遞歸調(diào)用自身 function foo() {foo(); } foo();// 報錯: Uncaught RangeError: Maximum call stack size exceeded三、執(zhí)行上下文流程圖
總結(jié)
以上是生活随笔為你收集整理的大话javascript 2期:执行上下文与执行上下文栈的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试前抢救一下--朴素贝叶斯分类器
- 下一篇: SpringBoot JPA不调用sav