首先,我們要知道執行環境和作用域是兩個完全不同的概念。
函數的每次調用都有與之緊密相關的作用域和執行環境。從根本上來說,作用域是基於函數的,而執行環境是基於對象的(例如:全局執行環境即window對象)。
換句話說,作用域涉及到所被調用函數中的變量訪問,並且不同的調用場景是不一樣的。執行環境始終是this關鍵字的值,它是擁有當前所執行代碼的對象的引用。每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數據時會在後台使用它。
執行環境(也稱執行上下文–execution context)
當JavaScript解釋器初始化執行代碼時,它首先默認進入全局執行環境,從此刻開始,函數的每次調用都會創建一個新的執行環境。
每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中(execution stack)。在函數執行完後,棧將其環境彈出,把控制權返回給之前的執行環境。ECMAScript程序中的執行流正是由這個便利的機制控制著。
執行環境可以分為創建和執行兩個階段。在創建階段,解析器首先會創建一個變量對象(variable object,也稱為活動對象 activation object),它由定義在執行環境中的變量、函數聲明、和參數組成。在這個階段,作用域鏈會被初始化,this的值也會被最終確定。在執行階段,代碼被解釋執行。
Demo:
<script type="text/javascript"> function Fn1(){ function Fn2(){ alert(document.body.tagName);//BODY //other code... } Fn2(); } Fn1(); //code here </script>
小結
當javascript代碼被浏覽器載入後,默認最先進入的是一個全局執行環境。當在全局執行環境中調用執行一個函數時,程序流就進入該被調用函數內,此時JS引擎就會為該函數創建一個新的執行環境,並且將其壓入到執行環境堆棧的頂部。浏覽器總是執行當前在堆棧頂部的執行環境,一旦執行完畢,該執行環境就會從堆棧頂部被彈出,然後,進入其下的執行環境執行代碼。這樣,堆棧中的執行環境就會被依次執行並且彈出堆棧,直到回到全局執行環境。
此外還要注意一下幾點:
單線程
同步執行
唯一的全局執行環境
局部執行環境的個數沒有限制
每次某個函數被調用,就會有個新的局部執行環境為其創建,即使是多次調用的自身函數(即一個函數被調用多次,也會創建多個不同的局部執行環境)。
作用域
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈(scope chain)。作用域鏈的用途是保證對執行環境有權訪問的所有變量和函數的有序訪問。
作用域鏈包含了執行環境棧中的每個執行環境對應的變量對象。通過作用域鏈,可以決定變量的訪問和標識符的解析。
注意:全局執行環境的變量對象始終都是作用域鏈的最後一個對象。
在訪問變量時,就必須存在一個可見性的問題(內層環境可以訪問外層中的變量和函數,而外層環境不能訪問內層的變量和函數)。更深入的說,當訪問一個變量或調用一個函數時,JavaScript引擎將不同執行環境中的變量對象按照規則構建一個鏈表,在訪問一個變量時,先在鏈表的第一個變量對象上查找,如果沒有找到則繼續在第二個變量對象上查找,直到搜索到全局執行環境的變量對象即window對象。這也就形成了Scope Chain的概念。
作用域鏈圖,清楚的表達了執行環境與作用域的關系(一一對應的關系),作用域與作用域之間的關系(鏈表結構,由上至下的關系)。
Demo:
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 這裡可以訪問color, anotherColor, 和 tempColor } // 這裡可以訪問color 和 anotherColor,但是不能訪問 tempColor swapColors(); } changeColor(); // 這裡只能訪問color console.log("Color is now " + color);
上述代碼一共包括三個執行環境:全局執行環境、changeColor()的局部執行環境、swapColors()的局部執行環境。
全局環境有一個變量color和一個函數changecolor();
changecolor()函數的局部環境中具有一個anothercolor屬性和一個swapcolors函數,當然,changecolor函數中可以訪問自身以及它外圍(即全局環境)中的變量;
swapcolor()函數的局部環境中具有一個變量tempcolor。在該函數內部可以訪問上面的兩個環境(changecolor和window)中的所有變量,因為那兩個環境都是它的父執行環境。
上述代碼的作用域鏈如下圖所示:
從上圖發現。內部環境可以通過作用域鏈訪問所有的外部環境,但是外部環境不能訪問內部環境中的任何變量和函數。
標識符解析(變量名或函數名搜索)是沿著作用域鏈一級一級地搜索標識符的過程。搜索過程始終從作用域鏈的前端開始,然後逐級地向後(全局執行環境)回溯,直到找到標識符為止。
執行環境與作用域的區別與聯系
執行環境為全局執行環境和局部執行環境,局部執行環境是函數執行過程中創建的。
作用域鏈是基於執行環境的變量對象的,由所有執行環境的變量對象(對於函數而言是活動對象,因為在函數執行環境中,變量對象是不能直接訪問的,此時由活動對象(activation object,縮寫為AO)扮演VO(變量對象)的角色。)共同組成。
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈。作用域鏈的用途:是保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。
小練習
<script type="text/javascript"> (function(){ a= 5; console.log(window.a);//undefined var a = 1;//這裡會發生變量聲明提升 console.log(a);//1 })(); </script>
window.a之所以是undefined,是因為var a = 1;發生了變量聲明提升。相當於如下代碼:
<script type="text/javascript"> (function(){ var a;//a是局部變量 a = 5;//這裡局部環境中有a,就不會找全局中的 console.log(window.a);//undefined a = 1;//這裡會發生變量聲明提升 console.log(a);//1 })(); </script>
以上就是小編為大家帶來的老生常談原生JS執行環境與作用域全部內容了,希望大家多多支持~