作用域(scope)和執行環境(context)

JavaScript的scope和context都是不能被我們直接使用的東西,存在于JavaScript的整個執行過程,分為代碼編譯階段和代碼執行階段,在代碼編譯階段,編譯器將代碼翻譯成可執行代碼,此時會確定函數的作用域和作用域鏈;在代碼的執行階段,引擎執行代碼時會創建代碼的執行環境。所以首先我們要明白一點,執行環境和作用域不是同一個東西。

作用域

作用域是負責收集并維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規則,確定當前的執行的代碼對這些標識符的訪問權限。——《你不知道的JavaScript(上卷)》

JavaScript中作用域可以分為全局作用域和局部作用域。
全局作用域是整個程序在運行時都能訪問的;局部作用域中包含了函數作用域和塊級作用域。

下圖展示了函數作用域和全局作用域之間的關系:


作用域圖示(截取自《你不知道的JavaScript(上卷)》)

塊級作用域在ES3中有with和catch兩個,在ES6中引入了let和const

    /* with */
    var obj = {a: 0}
    with(obj) {
      a = 1
    }
    console.log(obj)  // {a:1}
    console.log(a)  // ReferenceError: a is not define

    /* catch */
    try {
      undefined() // 制造一個錯誤
    } catch(err) {
      console.log(err) // 輸出這個err
    }
    console.log(err) // ReferenceError: err is not define

利用catch的塊作用域,我們可以在不兼容ES6的環境下使用let的兼容寫法,可以看下面這個例子:

/* 無塊級作用域 */
var i = 0;
for(i; i < 10; i++) {
  var j = i
  setTimeout(function(){
    console.log(j)  // 輸出10個10
  })
}
/* 使用let */
var i = 0
for(i; i < 10; i++) {
  let j = i
  setTimeout(function(){
    console.log(j)  // 輸出從0到9 10個數字
  })
}

/* 使用catch */
var i = 0;
for (i; i < 10; i++) {
  try {
    throw undefined  // 產生錯誤,值為undefined
  } catch (j) {
    j = i
    setTimeout(function () {
      console.log(j)  // 輸出從0到9 10個數字
    })
  }
}

詞法作用域
JavaScript是用的詞法作用域,所以作用域和作用域鏈是在代碼編譯階段就確定的東西,下面看一個簡單的例子:

var a = 2;
function fn1() {
  console.log(a);
}
function fn2() {
  var a = 3;
  fn1();
}
fn2();  // 2

在上面的代碼中,變量a、fn1和fn2是在全局作用域中聲明的,fn1和fn2也產生了各自的函數作用域,但因為作用域和作用域鏈是在函數定義階段就確定的,所以在執行階段,fn1()輸出的a就是全局變量中的a。

執行環境

每個函數都有自己的執行環境(execution context)。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行之后,棧將其環境彈出,把控制權返回給之前的執行環境?!禞avaScript高級程序設計》

console.log('global');
function func1() {
  console.log('func1');
  var func2 = function() {
    console.log('func2');
  }
  func2();
}
func1();
  1. 瀏覽器開始運行,環境棧中添加global執行環境,輸出global
  2. 執行了func1(),環境棧中添加func1執行環境,輸出func1
  3. 在func1中執行了func2(),環境棧中添加func2執行環境,輸出func2
  4. func2執行完畢,環境棧中推出func2執行環境
  5. func1執行完畢,環境棧中推出func1執行環境

上面這個例子的環境棧變化如下圖所示:

上例圖解

執行環境和作用域的區別

自我理解(未確認):JavaScript的代碼在執行前會確定作用域,產生作用域鏈,然后當執行到某個函數的時候,函數執行環境就被推入環境棧,執行環境上就會添加變量對象、活動對象、作用域鏈這些東西。變量對象和活動對象是差不多的,只是狀態不一樣,一個是執行前確定的,一個是執行時用到的,上面保存了函數中定義的變量或者函數聲明。作用域是一套規則,用來確定在何處以及如何查找對象,當作用域嵌套的時候,就產生了作用域鏈,當前作用域沒有找到變量的時候,就在上一層作用域查找,直到找到變量或者到達最外層作用域。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容