這是 JavaScript 系列的第 3 篇。segmentfault
首先來看一個引例:瀏覽器
function foo() { console.log('1'); bar(); console.log('3'); } function bar() { console.log('2'); } foo();
這段代碼將從上往下依次執行,並輸出 '1', '2', '3'。微信
咱們能夠看到,bar 函數的執行順序彷佛和它定義的順序沒有關係。爲何呢?這你就得弄懂執行棧了。數據結構
全部的 JS 代碼在運行時都是在執行上下文中進行的。執行上下文是一個抽象的概念,JS 中有三種執行上下文:函數
一般,咱們的代碼中都不止一個上下文,那這些上下文的執行順序應該是怎樣的?從上往下依次執行?學習
棧,是一種數據結構,具備先進後出的原則。JS 中的執行棧就具備這樣的結構,當引擎第一次遇到 JS 代碼時,會產生一個全局執行上下文並壓入執行棧,每遇到一個函數調用,就會往棧中壓入一個新的上下文。引擎執行棧頂的函數,執行完畢,彈出當前執行上下文。this
以引例來講明。當 foo() 函數被調用,將 foo 函數的執行上下文壓入執行棧,接着執行輸出 ‘1’;當 bar() 函數被調用,將 bar 函數的執行上下文壓入執行棧,接着執行輸出 ‘2’;bar() 執行完畢,被彈出執行棧,foo() 函數接着執行,輸出 ‘3’;foo() 函數執行完畢,被彈出執行棧。spa
那如今來看這個例子:3d
var count = 0; function foo(count) { count += 1; console.log(count); } foo(count); // 1 foo(count); // 1
咱們用執行棧來理解一下,函數每次被調用都會產生新的執行上下文,並被壓入執行棧,執行完畢後當前上下文就會被彈出執行棧。因此第一次調用應該返回 1,第二次調用也應該返回 1,第 n 次調用都應該返回 1。code
你理解了嗎?那再來看一個例子:
var count = 0; function foo() { count += 1; console.log(count); } foo(count); // 1 foo(count); // 2
WTF?這個例子和上一個的區別是這裏 foo 函數沒有指定形參。而這個例子其實就是一般說的函數內部沒有使用 var 聲明的變量,都會被當作全局變量(非嚴格模式)。
可是這裏咱們也能夠用執行棧來解釋。
函數的形參屬於函數執行上下文,因此當指定這個形參後,它就隨着函數被調用而新建,隨着函數銷燬而銷燬。若是不指定這個形參,上一篇文章已經介紹過做用域鏈的概念,就會沿着做用域鏈找到全局變量 count,它屬於全局執行上下文,這個時候再去調用 foo() 函數就會讀寫這個全局變量。
每一個 foo() 函數調用後,給 count 加一,而後被彈出執行棧,而全局執行上下文的生命週期將伴隨着整個程序,因此第一次調用打印 1,第二次調用打印 2,第 n 次調用打印 n。
是否是很奇妙呢?隨着學習的深刻,你會發現 JavaScript 的奇妙遠不止於此。
執行棧屬於 JavaScript 中基礎的概念,它與做用域、做用域鏈、執行上下文、變量對象/活動對象的聯繫都很是緊密。
文章首發於微信公衆號,理解 JavaScript 執行棧
歡迎關注個人公衆號 cameraee,一塊兒交流學習。
正在更新...