1. 程式人生 > >淺析javascript呼叫棧

淺析javascript呼叫棧

轉自https://segmentfault.com/a/1190000010360316

看原文第二個例子的時候真的是一頭霧水,說明和圖怎麼有點搭不上,後面看圖去揣測才明白是什麼意思。為了下次方便理解,稍微修改了下說明,讓自己不會理解錯導致又花時間。

 

一 什麼是呼叫棧

程式碼在執行過程中,會有一個叫做呼叫棧(call stack)的概念。呼叫棧是一種棧結構,它用來儲存計算機程式執行時候其活躍子程式的資訊。(比如什麼函式正在執行,什麼函式正在被這個函式呼叫等等資訊)。呼叫棧是解析器的一種機制。call stack

我們以一段簡單程式碼為示例,來看一看到底什麼是呼叫棧,它是一個怎麼樣的執行機制

 function boo (a) {
    return a * 3
  }
  function foo (b) {
    return boo(4) * 2
  }
  console.log(foo(3))

二 詳解程式碼執行

下面我們來分析一下上述程式碼的執行過程
(1)console.log(foo(3)) 執行,形成一個棧幀,呼叫foo函式,再形成另一個棧幀。
(2)新的棧幀壓在上一個棧幀之上,繼續執行程式碼,foo函式中又呼叫了boo函式,形成了另一個棧幀壓在舊棧幀之上。然後執行boo。
(3)當執行完boo時候,返回值給foo函式之後,boo被推出呼叫棧,foo函式繼續執行,然後foo函式執行完,返回值給console.log,foo函式被推出呼叫棧,console.log得到foo函式的返回值,執行,輸出結果,最後console.log也被推出呼叫棧,該段程式執行完成。
圖解程式碼執行過程:

三 一個更復雜的例子

// 省略一部分html
<button>click</button>
$.on('button', 'click', function onClick() {
    setTimeout(function timer() {
        console.log('You clicked the button!');    
    }, 0);
});

console.log("Hi!");

setTimeout(function timeout() {
    console.log("Click the button!");
}, 5000);

console.log("My Name Is Chirs.")

大家看看上敘的程式碼,結合一下前面的的分析,思考一下呼叫棧是怎麼工作的?
(1)執行console.log("hi"),該函式沒有呼叫任何其他函式。
(2)然後繼續執行下面的setTimeout,setTimeout是一個非同步函式,經過5秒之後,在執行佇列裡面插入這個回撥函式,然後如果該佇列之前沒有其他函式,就執行該佇列,有則等待前面的函式執行完成,再執行。
(3)console.log("My Name Is Chirs")不會等待5s之後,再執行,因為settimeout並不會在呼叫棧中執行5秒,實際上它在呼叫棧中是立即執行完的。
(4 )假設在要console.log("My Name Is Chirs")時,我們點選了按鈕,按鈕繫結的回撥事件被新增到執行佇列中。(執行佇列中的程式碼要等呼叫棧被清空之後才會執行)由於呼叫棧中還有程式碼需要執行,所以會繼續執行下面的console.log("My Name Is Chirs")。
(5)然後執行完console.log("My Name Is Chirs")之後,由於時間還沒有經過5s,所以點選按鈕的回撥事件會被先壓入棧中去執行,由於該回調事件裡面又是一個settimeout事件,由於它的事件間隔只有0s,所以這個settimeout的回撥會先被壓入執行佇列。先輸出You clicked the button! 然後5s間隔到了的settimeout把回撥函式壓入佇列,這時候呼叫棧中沒有程式碼在執行,所以會執行這個程式碼,輸出"Click the button“。結束程式碼執行。

同樣來看一個執行示意圖

四 總結

呼叫棧其實就是一種解析器去處理程式的機制,它是棧資料結構。它能追蹤子程式的執行狀態。
(1)當指令碼要呼叫一個函式時,解析器把該函式新增到棧中並且執行這個函式。並形成一個棧幀
(2)任何被這個函式呼叫的函式會進一步新增到呼叫棧中,形成另一個棧幀,並且執行到它們被上個程式呼叫的位置。
(3)當執行完這個函式後,如果它沒有呼叫其他函式,則它會從呼叫棧中推出。然後呼叫棧繼續執行其他部門。
(4) 非同步函式的回撥函式一般都會被新增到執行佇列裡面,如settimeout會在響應的時間後把回撥函式放入佇列中,佇列裡的函式需要等棧為空時才會被推入棧中執行。如果佇列中有其他函式,需要等佇列前面的函式被堆入呼叫棧中之後才會執行