javascript高級程序第三版學習筆記【執行環境、作用域】


執行環境 

 執行環境定義了變量和函數有權訪問的其他數據,決定了他們各自的行為。

每個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的所有變量和函數都保存在這個對象上,雖然我們在編寫代碼的時候無法訪問這個對象,但解析器在處理數據時會在后台用到它。

ECMAScript實現的宿主環境不同,表示的執行環境對象也不一樣。

在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對象的屬性和方法創建的。

某個執行環境中的所有代碼執行完畢后,該環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀,全局執行環境直到應用程序退出后---例如關閉瀏覽器和網頁---時才被銷毀。

 

函數執行環境

 每個函數都有自己的執行環境,當執行進入一個函數時,函數的執行環境就會被推入一個執行環境棧的頂部並獲取執行權。當這個函數執行完畢,它的執行環境又從這個棧的頂部被刪除,並把執行權並還給之前執行環境。這就是ECMAScript程序中的執行流。

 

【定義期】

函數定義的時候,都會創建一個[[scope]]屬性,這個對象對應的是一個對象的列表,列表中的對象僅能javascript內部訪問,沒法通過語法訪問。

我們定義一全局函數A,那么A函數就創建了一個A的[[scope]]屬性。此時,[[scope]]里面只包含了全局對象【Global Object】。而如果, 我們在A的內部定義一個B函數,那B函數同樣會創建一個[[scope]]屬性,B的[[scope]]屬性包含了兩個對象,一個是A的活動對象【Activation Object】【對於函數來說】一個是全局對象,A的活動對象上面,全局對象在下面。以此類摧,每個函數的都在定義的時候創建自己的[[scope]],里面保存着一個類似於棧的格式的數據。

下面是示例代碼:

// 外部函數
function A(){
     var somevar;
        
     // 內部函數
    function B(){
         var somevar;
     }
}

看下示意圖:

【執行期】

當函數被執行的時候,就是進入這個函數的執行環境,首先會創一個它自己的活動對象【Activation Object】(這個對象中包含了this、參數(arguments)、局部變量(包括命名的參數)的定義,當然全局對象是沒有arguments的)和一個變量對象的作用域鏈[[scope chain]],然后,把這個執行環境的[scope]按順序復制到[[scope chain]]里,最后把這個活動對象推入到[[scope chain]]的頂部。這樣[[scope chain]]就是一個有序的棧,這樣保了對執行環境有權訪問的所有變量和對象的有序訪問。

 

// 第一步頁面載入創全局執行環境global executing context和全局活動象
// 定義全局[[scope]],只含有Window對象
// 掃描全局的定義變量及函數對象:color【undefined】、changecolor【FD創建changecolor的[[scope]],此時里面只含有全局活動對象】,加入到window中,所以全局變量和全局函數對象都是做為window的屬性定義的。
// FD已經定義好所以在此執行環境內任何位置都可以執行changecolor(),color也已經被定義,但是它的值是undefined

// 第二步color賦值"blue"
var color = "blue";

// 它是不需要賦值的,它就是引用本身
function changecolor() {
    // 第四步進入changecolor的執行環境
    // 復制changecolor的[[scope]]到scope chain
    // 創建活動對象,掃描定義變量和定義函數,anothercolor【undefined】和swapcolors【FD創建swapcolors的[[scope]]加入changecolor的活動對象和全局活動對象】加入到活動對象,活動對象中同時還要加入arguments和this
    // 活動對象推入scope chain 頂端
    // FD已經定義好所以在此執行環境內任何位置都可以執行swapcolors(),anothercolor也已經被定義好,但它的值是undefined
    
    // 第五anothercolor賦值"red"
    var anothercolor = "red";
    
    // 它是不需要賦值的,它就是引用本身
    function swapcolors() {
        // 第七步進入swapcolors的執行環境,創建它的活動對象
        // 復制swapcolors的[[scope]]到scope chain
        // 掃描定義變量和定義函數對象,活動對象中加入變量tempcolor【undefined】以及arguments和this
        // 活動對象推入scope chain 頂端
        
        // 第八步tempcolor賦值anothercolor,anothercolor和color會沿着scope chain被查到,並繼續往下執行
        var tempcolor = anothercolor;
            anothercolor = color;
            color = tempcolor;    
    }

    // 第六步執行swapcolors,進入其執行環境
    swapcolors();
}

// 第三步執行changecolor,進入其執行環境
changecolor();

 

訪問標識符:當執行js代碼的過程中,遇到一個標識符,就會根據標識符的名稱,在執行上下文(Execution Context)的作用域鏈中進行搜索。從作用域鏈的第一個對象(該函數的Activation Object對象)開始,如果沒有找到,就搜索作用域鏈中的下一個對象,如此往復,直到找到了標識符的定義。如果在搜索完作用域中的最后一個對象,也就是全局對象(Global Object)以后也沒有找到,則會拋出一個錯誤,提示undefined。

 

既然講到執行期,順便講一下javascript的【聲明提升機制Hoisting

先看下面代碼:

var myvar = 'my value';  
alert(myvar); // my value  

當然會彈出my value

再看下面這樣

var myvar = 'my value';  
(function(){
    alert(myvar); // undefined
    var myvar = 'you value';  
})()

結果卻是undefined

這是因為,javascript解析器,進入一個函數執行環境,先對var 和 function進行掃描。相當於會把var或者function【函數聲明】聲明提升到執行環境頂部。

上面的代碼會被解析成下面這樣:

var myvar = 'my value';  
(function(){
    var myvar;
    alert(myvar); // undefined
    myvar = 'you value';  
})()

根據,標識符找查機制當執行到alert的時候,查找myvar是局部變量的myvar,此時myvar並沒有被賦值。所以結果是undefined。
再看看下面的例子:

count(1,2); // 3
function count(a,b)
{
    alert(a+b);    
}

 

count(1,2); // 會報錯誤count is not a function
var count = function (a,b)
{
    alert(a+b);    
}

我們知道,根據聲明提升機制,var和function都會被提升到執行環境的頂部,已經掃描完畢。所以,上面那種寫法,會把function聲明提到執行環境頂部,所以即使調用在聲明的前面依然能夠執行。

而下面這種寫法會被解析成這樣:

var count;
count(1,2); // 會報錯誤count is not a function
count = function (a,b)
{
    alert(a+b);    
}

因為此時count只是被掃描,但還末被引用到函數對象,所以此時,它不是一個function,所以把它當函數來執行會報錯。

另外一個需要提一下的是,函數的聲明是優先於變量的聲明的。

 

作用域 

由上,可以得知,當查找一個個標識符的時候,是在作用域鏈[[scope chain]]里查找的,這個作用域鏈里只包含上一級的活動對象,但並不包括下一級的活動對象。這就意味着,內部執行環境可以訪問外部的執行環境定義的變量,但外部的執行環境是無法訪問內部執行環境定義的變量的。並且,內部環境定義的變量會覆蓋掉外部環境定義的變量。我們還可以知道,特定執行環境的查找的標識符是在【定義期】就已經完成的,而不是在【執行期】,因為作用域鏈[[scope chain]]是從定義期的[scope]復制過來的。但是它本身的Activation Object對象是在執行期才推入,作用域鏈頂端的,所以本身的私有變量是【執行期】完成的。

 還是上面的例子:

// color全局變量changecolor 和 swapcolors都可以沿着scope chain訪問到
var color = "blue";

function changecolor() {
   
    // anothercolor局部變量,全局訪問不到 但 changecolor和swapcolors都可以沿着scope chain訪問到
    var anothercolor = "red";
    
    function swapcolors() {
       
        // tempcolo局部變量,全局和changecolor訪問不到 但 swapcolors都可以沿着scope chain訪問到
        var tempcolor = anothercolor;
            anothercolor = color;
            color = tempcolor;    
    }

    swapcolors();
}
changecolor();

 

沒有塊級作用域

 與C、C++以及JAVA不同,Javscript沒有塊級作用域。看下面代碼:

if(true){
        var myvar = "木乙";    
    }
    alert(myvar);// 木乙

根據上面我們討論的,如果有塊級作用域,外部是訪問不到myvar的。再看下面

for (var i=0;i<5;i++){
            console.log(i)    
        }
        
        alert(i); // 5

對於有塊級作用域的語言來說,i做為for初始化的變量,在for之外是訪問不到的,這允分證明了,javascript是沒有塊級作用域的。

延長作用域鏈

雖然執行環境只有兩種——全局作用域和函數作用域,但是還是可以通過某種方式來延長作用域鏈。因為有些語句可以在作用域鏈的頂部增加一個臨時的變量對象。
有兩種情況會發生這種現象:
1、try-catch語句的catch塊;
2、with語句;
 
看下面實例:
(function bildUrl(){
            var qs = "?debug=true";
                
            with(location){
                var url = href + qs;    
            }
            alert(url)
        })()

with會把location對象的所有屬性和方法包含到變量對象中,並加入到作用域鏈的頂部。此時訪問href實際上就是location.href。

但新聲明的“url”變量會加入到最近的執行環境的變量對象里。試下面例子,說明url是可以訪問到的,只是此時是undefined

(function bildUrl(){
            var qs = "?debug=true";
            
            if (!url)
            {
                alert("這里可以看到url"); //可以正常顯示url已經被聲明並且被掃描進函數的變量對象
            }
            
            try{
                if (!a)
                {
                    alert(1);// 報錯因為a根本就不存在
                }
            }
            catch(e){
                console.log("作用域鏈被延長了吧");
            }
            
            with(location){
                var url = href + qs;    
            }
            
        })()

對catch語句來說,會創建一個新的變量對象加入到作用域鏈的頂部,其中包含的是被拋出的錯誤對象的聲明。
需要說明的是,ie8之前的版本有個不符合標准的地方,就是被勢出的錯誤對象會被加入到執行環境的變量對象。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM