1. 程式人生 > >執行環境,作用域

執行環境,作用域

復雜 colors tab 程序 str chang 閉包 將不 ges

在javascript的學習中,執行環境、作用域是2個非常非常重要和基本的概念,理解了這2個概念對於javsacript中很多腳本的運行結果就能明白其中的道理了,比如搞清作用域和執行環境對於閉包的理解至關重要。

一、執行環境(exection context,也有稱之為執行上下文)

所有 JavaScript 代碼都是在一個執行環境中被執行的。執行環境是一個概念,一種機制,用來完成JavaScript運行時在作用域、生存期等方面的處理,它定義了變量或函數是否有權訪問其他數據,決定各自行為。

在javascript中,可執行的JavaScript代碼分三種類型:
1. Global Code,即全局的、不在任何函數裏面的代碼,例如:一個js文件、嵌入在HTML頁面中的js代碼等。
2. Eval Code,即使用eval()函數動態執行的JS代碼。
3. Function Code,即用戶自定義函數中的函數體JS代碼。
不同類型的JavaScript代碼具有不同的執行環境,這裏我們不考慮evel code,對應於global code和function code存在2種執行環境:全局執行環境和函數執行環境。

在一個頁面中,第一次載入JS代碼時創建一個全局執行環境,全局執行環境是最外圍的執行環境,在Web瀏覽器中,全局執行環境被認為是window對象。因此,所有的全局變量和函數都是作為window對象的屬性和方法創建的。

當調用一個 JavaScript 函數時,該函數就會進入與該函數相對應的執行環境。如果又調用了另外一個函數(或者遞歸地調用同一個函數),則又會創建一個新的執行環境,並且在函數調用期間執行過程都處於該環境中。當調用的函數返回後,執行過程會返回原始執行環境。因而,運行中的 JavaScript 代碼就構成了一個執行環境棧。

Code1:

 function Fn1(){
     function Fn2(){
        alert(document.body.tagName);//BODY
        //other code...
     }
   Fn2();
}
 Fn1();
 //code here

技術分享圖片

(圖1 執行環境棧 摘自:笨蛋的座右銘的博文)

程序在進入每個執行環境的時候,JavaScript引擎在內部創建一個對象,叫做變量對象(Variable Object)。對應函數的每一個參數,在Variable Object上添加一個屬性,屬性的名字、值與參數的名字、值相同。函數中每聲明一個變量,也會在Variable Object上添加一個屬性,名字就是變量名,因此為變量賦值就是給Variable Object對應的屬性賦值。在函數中訪問參數或者局部變量時,就是在variable Object上搜索相應的屬性,返回其值。(另外註意:一般情況下Variable Object是一個內部對象,JS代碼中無法直接訪問。規範中對其實現方式也不做要求,因此它可能只是引擎內部的一種數據結構。)

大致處理方式就這樣,但作用域的概念不只這麽簡單,例如函數體中可以使用全局變量、函數嵌套定義時情況更復雜點。這些情況下怎樣處理?JavaScript引擎將不同執行位置上的Variable Object按照規則構建一個鏈表,在訪問一個變量時,先在鏈表的第1個Variable Object上查找,如果沒有找到則繼續在第2個Variable Object上查找,直到搜索結束。這就是Scope/Scope Chain的大致概念。

二、Scope/Scope Chain(作用域/作用域鏈)

當代碼在一個環境中執行時,都會創建基於Variable Object的一個作用域鏈。 作用域鏈的用途是保證對執行環境有權訪問的所有變量和函數的有序訪問。整個作用域鏈是由不同執行位置上的Variable Object按照規則所構建一個鏈表。作用域鏈的最前端,始終是當前正在執行的代碼所在環境的Variable Object。如果這個環境是函數(比如Fn2),則將其活動對象(activation object)作為變量對象。活動對象在最開始時只包含一個變量,就是函數內部的arguments對象。作用域鏈中的下一個Variable Object來自該函數(Fn2)的包含環境(也就是Fn1),而再下一個Variable object來自再下一個包含環境。這樣,一直延續到全局執行環境,全局執行環境的Variable Object始終是作用域鏈中的最後一個對象。

如上所述,作用域鏈感覺就是一個Variable Object鏈表,當訪問一個變量時,先在鏈表的第一個Variable Object(最前端)上查找,如果沒有找到則繼續在第二個Variable Object上查找,直到搜索結束,也就是搜索到全局執行環境的Variable Object中。這也就形成了Scope Chain的概念。

與上面Code1代碼對應的作用域鏈圖如下所示(摘自:笨蛋的座右銘的博文,這個圖感覺不是很理想,不如下面的圖3更形象,把右側這部分調個個就好了。)

技術分享圖片

(圖2 作用域鏈圖)

如上圖所示,對於Code1代碼中的函數Fn2所對應的作用域鏈為:Fn2 Variable Object->Fn1 variable Object->全局對象。也就是說,在Fn2函數中進行變量訪問時,首先會在Fn2 Variable object訪問內進行尋找,如果沒有找到,則向上,搜索Fn1 Variable Object中是否存在,直至找到,停止搜索。

再拿《javascript高級程序設計第二版》中提到的例子來說明一下。代碼如下所示:

Code2:

 var color="blue";
 function changecolor(){
    var anothercolor="red";
    function swapcolors(){
	var tempcolor=anothercolor;
	anothercolor=color;
	color=tempcolor;
       // Todo something		
     }
    swapcolors();
}
changecolor();
 //這裏不能訪問tempcolor和anocolor;但是可以訪問color;
alert("Color is now  "+color);

在Code2代碼中,涉及3個執行環境全局環境、changecolor函數的局部環境和swapcolor局部環境。該段代碼的作用域鏈如下圖所示。

技術分享圖片 (圖3摘自:《javascript高級程序設計第二版》)

上圖中,

  • -》全局環境有1個變量color和1個函數changecolor()。
  • -》changecolor()函數的局部環境中具有1個anothercolor屬性和1個swapcolors函數,當然,changecolor函數中可以訪問自身以及它外圍(也就是全局環境)中的變量。
  • -》swapcolor()函數的局部環境中具有1個變量tempcolor。在該函數內部可以訪問上面的2個環境(changecolor和window)中的所有變量,因為那2個環境都是它的父執行環境。

通過上面的分析,我們可以得知內部環境可以通過作用域鏈訪問所有的外部環境,但外部環境不能訪問內部環境中的任何變量和函數。這些環境之間是線性、有次序的。每個環境都可以向上搜索作用域鏈,以便查詢變量和函數名;但任何環境不能通過向下搜索作用域鏈條而進入另一個執行環境。對於上述例子的swapcolor()函數而言,其作用域鏈包括:swapcolor()的變量對象、changecolor()變量對象和全局對象。swapcolor()的局部環境開始先在自己的Variable Object中搜索變量和函數名,找不到,則向上搜索changecolor作用域鏈。。。。。以此類推。但是,changecolor()函數是無法訪問swapcolor中的變量。

關於作用域總結以下幾條:

1、javascript 沒有塊級作用域。

直接上代碼:

for(var i=0;i<10;i++){
   doSomething(i);
}
alert(i); // output :10,why?

上述代碼運行後會返回10,為什麽呢?如果是同樣的java或是c#代碼,則不會是10,可能會提示運行錯誤,因為i只存在於for循環體重,在運行完for循環後,for中的所有變量就被銷毀了。而在javascript中則不是這樣的,在for中的變量聲明將會添加到當前的執行環境中(這裏是全局執行環境),因此在for循環完後,變量i依舊存在於循環外部的執行環境。因此,會輸出10。

2、聲明變量

使用var聲明變量時,這個變量將被自動添加到距離最近的可用環境中。對於函數而言,自然聲明的變量就會被添加到函數的局部環境中,變量在整個函數環境內都是可用的。

但是,如果變量沒有是用var進行聲明,將會被添加到全局環境,也就是說成位全局變量了。

所以在函數體內,進行聲明時,一般要在開頭用var進行聲明。

最後出幾個小例題:

var x = 1;
function rain(){
    alert( x );    //彈出 ‘undefined‘,而不是1,也不是10,why?
     var x = ‘10‘;
    alert( x );    //彈出 ‘10‘,why?
}
rain()

為什麽會是代碼中所說明的結果呢?

我認為和2個事情有關:作用域和預解析。我們可以很容易得出上述代碼的作用域鏈。

window全局環境和rain()函數局部環境。window全局環境中存在全局變量x和rain,而rain()函數的局部環境中包括:局部變量x。因此,在執行rain()函數時,不可能去訪問全局變量x的了,因為在當前的rain()函數內已經有局部變量x。所以alert出1,

但為什麽第一次alert出undefined呢?這可能和預解析有關。在代碼執行時,首先會解析出變量和函數的定義,上述代碼等價於下面的代碼。

function rain(){
    var x;
    alert( x );
    x = ‘10‘;
    alert( x );
}

by Aaron:https://www.cnblogs.com/aaronjs/articles/2167431.html

執行環境,作用域