JavaScript的scope和context都是不能被我們直接使用的東西,存在于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();
- 瀏覽器開始運行,環境棧中添加global執行環境,輸出global
- 執行了func1(),環境棧中添加func1執行環境,輸出func1
- 在func1中執行了func2(),環境棧中添加func2執行環境,輸出func2
- func2執行完畢,環境棧中推出func2執行環境
- func1執行完畢,環境棧中推出func1執行環境
上面這個例子的環境棧變化如下圖所示:
執行環境和作用域的區別
自我理解(未確認):JavaScript的代碼在執行前會確定作用域,產生作用域鏈,然后當執行到某個函數的時候,函數執行環境就被推入環境棧,執行環境上就會添加變量對象、活動對象、作用域鏈這些東西。變量對象和活動對象是差不多的,只是狀態不一樣,一個是執行前確定的,一個是執行時用到的,上面保存了函數中定義的變量或者函數聲明。作用域是一套規則,用來確定在何處以及如何查找對象,當作用域嵌套的時候,就產生了作用域鏈,當前作用域沒有找到變量的時候,就在上一層作用域查找,直到找到變量或者到達最外層作用域。