JavaScript 中的執行環境、作用域(scope)以及變量提升(hoisting)

先看下面一段代碼:

var a = 0;
alert("1st alert : a = " + a);
function fun(){
    alert("2nd alert : a = " + a);
    var a = 1;
    setTimeout(function(){
        alert("3rd alert : a = " + a);
        a = 2;
    },1000);
    a = 3;
    setTimeout(function(){
        alert("4th alert : a = " + a);
        a = 4;
    },4000);
}
fun();
alert("5th alert : a = " + a);

代碼執行的結果是:

1st alert : a = 0

2nd alert : a = undefined

5th alert : a = 0

3rd alert : a = 3

4th alert : a = 2

疑問1:對於 2nd alert 而言,爲什麼 a 的值是 undefined ? 

首先來看 JS 的執行環境和作用域。

執行環境(executing context)定義了變量或函數有權訪問的其他數據。在 JS 中,有兩種執行環境,一種是全局環境,也就是 Web 瀏覽器中的 window 對象,而另一種就是函數的執行環境。

在執行環境中存在一個變量對象,這個對象保存了環境中定義的所有變量和函數。

當代碼在執行的時候,會創建變量對象的一個作用域鏈,作用域鏈的前端是當前環境的變量對象,然後依次是外層環境的變量對象,逐層向外直到全局執行環境。

在標識符的解析過程中,會從前端開始沿着作用域鏈一級一級搜索,直到找到標識符或搜索完全局環境爲止。

所以在 JS 中,花括號並不代表一個獨立的作用域,循環體中定義的變量,循環體外依然可能訪問到(在同一個執行環境中)

看下面例子:

var a = 0;
function fun(){
    alert("a=" + a);
}
fun();

結果是 a=0

這就是因爲在調用 fun 函數的時候,搜索標識符 a 並無法在本執行環境中找到,但是通過作用域鏈搜索到外層作用域的時候找到了 a

而爲什麼第一個例子中,2nd alert , a 的值是 undefined ?

這是因爲在 JS 中,使用 var 聲明的變量或者使用函數聲明方式聲明的函數(不是函數表達式)會自動被添加到最接近的環境中,也就是所謂的變量提升(hoisting)。什麼意思,相當於上面的 fun 函數定義中前兩行的代碼變成:

function fun(){
    var a;
    alert("2nd alert : a = " + a);
    a = 1;
    //other codes
}

所以在搜索標識符 a 的時候,在本執行環境中就可以搜索到,而不用搜索到外層環境,所以在 2nd alert 中,a 的值就是 undefined。

而對於函數的定義也是如此,這就是爲什麼使用函數聲明的方式時,調用函數可以放在函數聲明之前,而使用函數表達式的時候卻不可以的原因了。

疑問 2:5th alert 爲什麼在 3rd 和 4th 之前?

這是因爲 JavaScript 引擎對於其任務隊列是單線程處理的,而 setTimeout 屬於異步代碼,只有當 JS 線程中沒有同步代碼的時候纔會執行異步代碼。

setTimeout(function(){while(true){}},1000);
setTimeout(function(){alert('end 2');},2000);
setTimeout(function(){alert('end 1');},100);
alert('end');
所以在上面的代碼中,首先出現的是 end ,其次是 end 1,之後就再也不會出現。因爲在第一個 setTimeout 函數中死循環佔用了 JS 引擎的單線程,阻塞了其他進程。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章