理解 JavaScript 執行棧

這是 JavaScript 系列的第 3 篇。segmentfault

引例

首先來看一個引例:瀏覽器

function foo() {
  console.log('1');
  bar();
  console.log('3');
}

function bar() {
  console.log('2');
}

foo();

這段代碼將從上往下依次執行,並輸出 '1', '2', '3'。微信

咱們能夠看到,bar 函數的執行順序彷佛和它定義的順序沒有關係。爲何呢?這你就得弄懂執行棧了。數據結構

執行棧

全部的 JS 代碼在運行時都是在執行上下文中進行的。執行上下文是一個抽象的概念,JS 中有三種執行上下文:函數

  • 全局執行上下文,默認的,在瀏覽器中是 window 對象,而且 this 在非嚴格模式下指向它。
  • 函數執行上下文,JS 的函數每當被調用時會建立一個上下文。
  • Eval 執行上下文,eval 函數會產生本身的上下文,這裏不討論。

一般,咱們的代碼中都不止一個上下文,那這些上下文的執行順序應該是怎樣的?從上往下依次執行?學習

棧,是一種數據結構,具備先進後出的原則。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,一塊兒交流學習。

clipboard.png

JavaScript 系列文章

分析 JavaScript 的數據類型與變量

理解 JavaScript 做用域

正在更新...

相關文章
相關標籤/搜索