首先,我們要知道執行環境和作用域是兩個完全不同的概念。
函數的每次呼叫都有與之緊密相關的作用域和執行環境。從根本上來說,作用域是基於函數的,而執行環境是基於物件的(例如:全域執行環境即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>