天天看點

JS:作用域以及作用域鍊

剛剛看了一篇曾在阿裡工作的員工寫的部落格,不得不說,在生活中,總是會發現那些很優秀又有正能量的人,從他們的身上學到的不僅僅是知識,更是一種态度,我是一個新手,但是我選擇熱愛我所做的事情,基礎很重要,學習前端的人甚多,但是我想在這條路上刨一刨,以求甚解,發現前端的美。

作用域:

作用域就是變量和函數可通路範圍,控制着變量的函數可見性與生命周期。

執行環境:定義了變量或函數有權通路的其他資料,決定了它們各自的行為。每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都儲存在這個對象中。雖然在我們編寫代碼的時候無法通路它,但是解析器會在處理資料的時候,在背景使用它。

全局執行環境是最外圍的一個執行環境。根據ECMAScript實作所在的宿主環境不同,表示執行環境的對象也不同。在web浏覽器中,全局執行環境被認為是window對象,是以所有的全局變量和函數都是作為window的屬性和方法建立的。

某個執行環境中所有的代碼執行完畢之後,該環境被銷毀,儲存在其中的所有變量和函數定義也随之銷毀(全局執行環境直到應用程式退出——例如關閉網頁或者浏覽器時才會被銷毀)

每個函數都有自己的執行環境。當函數流進入一個函數時,函數環境就會被推入一個環境棧中。而在函數執行之後,棧将其環境彈出,把控制權交給之前執行的環境。

變量沒有在函數内聲明或者聲明的時候沒有帶var 就是全局變量,擁有全局作用域,window對象的所有屬性擁有全局作用域,在代碼的任何地方都可以通路,在函數内部聲明并且以var修飾的變量就是局部變量,隻能在函數體内使用 ,函數參數雖然沒有用var聲明但依然是局部變量。

例如:

var a = ;//全局變量
        function fn(b){//b作為形參是一個局部變量
            c = ;//全局變量
            var d = ;//局部變量
            function subFn(){
                var e = d;//複函數的局部變量對子函數可見
                for(var i = ;i<;i++){
                     console.log(i);
                }
                console.log(i);//3,在for循環外可以通路到i的值和JS中無塊級作用域有關,下文解釋
            }
            subFn();

        }
        fn();
           

JavaScript中無塊級作用域

JavaScript中無塊級作用域,在其他類似C語言中,由花括号封閉的代碼都有自己的作用域(如果用ECMAScript的話來講,就是他們自己的執行環境),例如:

if(true){
  var color = "blue";//在if語句中定義了color,如果是在C,C++或者Java中,color會在if語句執行完畢之後被銷毀
}
console.log(color);//"blue" 在if語句中定義了color,如果是在C,C++或者Java中,color會在if語句執行完畢之後被銷毀

for(var i = ;i<;i++){
  doSomething(i);
}
console.log(i);//  由for語句建立的變量i即使在for循環執行結束完畢之後,也依然會存在循環外部的執行環境中
           

在if語句中定義了color,如果是在C,C++或者Java中,color會在if語句執行完畢之後被銷毀。但是JavaScript,由for語句建立的變量i即使在for循環執行結束完畢之後,也依然會存在循環外部的執行環境中。

聲明變量

使用var 聲明的變量會自動添加到最近的執行環境中去。在函數内部,最接近的環境就是函數的局部環境

預解析

在真正執行代碼之前,JavaScript解析器輝預解析代碼,将變量,函數聲明部分提前解釋,這就意味着我們可以在function聲明語句之前調用function

例如:

console.log(a);//undefined
   var a = ;
   console.log(a);//
   console.log(b);//Uncaught ReferenceError:b is not defined
           

上面的代碼在執行var a = 3;語句之前其聲明部分就得到了預解析(但是不會執行指派語句),是以第一次的時候是undefined,而不會報錯,在執行過指派語句之後會得到3,是以上面這段代碼得到的效果相當于下面這段代碼:

var a;
console.log(a);
a = ;
console.log(a);
console.log(b);
           

作用域鍊

當代碼在一個環境中執行的時候,會建立變量對象的一個作用域。作用域鍊的用途是:保證對執行環境有權通路的所有變量 和函數的有序通路。作用域的最前端,始終是目前執行代碼所在的環境的變量對象。如果這個環境是函數,則将其活動對象作為變量對象。活動對象在最開始時隻包含一個變量,即arguments對象(這個對象在全局環境中是不存在的)。作用域鍊中的下一個變量對象來自包含的(外部)環境,再下一個變量對象則來自于下一個包含環境。這樣一直延續到全局執行環境,全局執行環境的變量對象始終都是作用域鍊的最後一個對象。

例如:

var color = "blue";
       function changeColor(){
        var anotherColor = "red";
        function swapColors(){
            var tempColor = anotherColor;
            anotherColor = color;
            color = tempColor;
            //這裡可以通路color,anotherColor和tempColor
        }
        //這裡可以通路到color,anotherColor,但是不能通路到tempColor
        swapColors();
       }
       //這裡隻能通路color
       changeColor();
           

以上代碼共涉及三個執行環境,全局環境,changeColor()的局部環境和swapColor()的局部環境。全局環境中隻有一個變量color和一個函數changeColor()的函數,但是它也可以通路全局環境中的變量color,s 中有一個變量tempC lor,該變量隻能在這個環境中通路到

内部環境可以通過作用域鍊通路到所有的外部環境中去,但是外部環境不能通路内部環境的任何變量和函數。

舉例具體說明作用域鍊建立的過程:

function compare(value1,value2){
   if(value1<value2){
      return -;
   }
   else if(value1>value2){
     return ;
   }
   else{
     return ;
   }
}
           

以上代碼先定義了compare函數,然後又在全局作用域中調用了它。在建立compare()函數時,會建立一個預先包含全局變量對象的作用域鍊,這個作用域鍊被儲存在内部的[[Scope]]屬性中。當調用compare()函數時,會為函數建立一個執行環境,然後通過複制函數[[Scope]]屬性中的對象建構的起執行環境的作用域鍊。此後,又有一個活動對象(在此作為變量對象使用)被建立并推入執行環境作用域前端。對于這個例子compare()函數的執行環境而言,其作用域鍊中包含兩個變量對象:本地活動對象和全局變量對象。顯然,作用域鍊本質上是一個指向變量對象的指針清單,它隻引用但不包含變量對象