javascript 做用域鏈與執行環境

前言:這是筆者學習以後本身的理解與整理。若是有錯誤或者疑問的地方,請你們指正,我會持續更新!javascript

 

  做用域、做用域鏈、執行環境、執行環境棧以及 this 的概念在 javascript 中很是重要,本人常常弄混淆,這裏梳理一下:java

  1. 局部做用域函數內部的區域,全局做用域就是 window;
  2. 做用域鏈取決於函數被聲明時的位置,解析標識符的時候就先找當前做用域,再向外查找,直到全局,這樣一個順序;和函數在哪裏調用無關;
  3. 執行環境就是函數可訪問的數據和變量的集合,也就是函數的做用域鏈上的全部數據和變量;
  4. 執行環境棧就是根據代碼執行順序,各執行環境按照棧的形式逐層訪問,而且用完了退出來扔掉;若是當前執行環境(存放當前做用域鏈裏的數據和變量)找不到變量,那就是找不到了,不會往以前的那個執行環境查找,它和做用域鏈是不一樣的;

做用域

  JavaScript 沒有塊級做用域的概念,只有函數級做用域:變量在聲明它們的函數體及其子函數內是可見的。函數

  做用域就是變量和函數的可訪問範圍,控制着變量和函數的可見性與生命週期,在 JavaScript 中變量的做用域有全局做用域和局部做用域。性能

  變量沒有在函數內聲明或者聲明的時候沒有帶 var 就是全局變量,擁有全局做用域。學習

        <script type="text/javascript">
            function test1(){
                a = 1;//全局變量,只有在當前函數運行時,纔有效
            }
            test1();
            console.log(a);//1       注意test1函數必須運行,否則找不到a
        </script>

 

  全局變量能夠當作 window 對象的屬性用,他們是同樣的。優化

        <script type="text/javascript">    
            var b = 1;//全局變量
            console.log(b === window.b);//true  全局變量能夠當作window對象的屬性用,他們是同樣的;
        </script>

 

  window 對象的全部屬性擁有全局做用域,在代碼任何地方均可以訪問。this

  函數內部聲明的變量就是局部變量,只能在函數體內使用,函數的參數雖然沒有使用 var 但仍然是局部變量。spa

        <script type="text/javascript">    
            var c = 1;//全局變量
//            console.log(d);//ReferenceError: d is not defined    引用錯誤,當前做用域就是最外層做用域,依然找不到d
            function test2(d){
                console.log(c);//1   全局變量,哪均可以訪問;(先找當前做用域,找不到,就向外層做用域找,直到window最外層,找到了)
                console.log(d);//3   形參是局部變量,只有當前做用域下能夠訪問
            }
            test2(3);
        </script>

 

做用域鏈

  做用域鏈取決於函數被聲明時的位置,解析標識符的時候就先從當前做用域開始找,在當前做用域中沒法找到時,引擎就會在外層嵌套的做用域中繼續查找,直到找到該變量,或抵達最外層的做用域(也就是全局做用域)爲止;它的路線已經被定死了,和函數在哪裏運行無關code

        <script type="text/javascript">
            var a = 1;
            var b = 2;
            var c = 3;
            var d = 4;
            function inner(d) {//它的做用域鏈是inner---全局
                var c = 8;
                console.log(a);//1  當前做用域找不到a,去全局做用域找到了a=1
                console.log(b);//2  當前做用域找不到b,去全局做用域找到了b=2
                console.log(c);//8  當前做用域找到了c=8
                console.log(d);//7  當前做用域找到了d=7,形參也是局部做用域
            //    console.log(e);//ReferenceError: e is not defined   引用錯誤,找不到e, 它的做用域鏈是inner---全局
                console.log(a+b+c+d);//18
            }
            function outter(e) {
                var a = 5;//inner()的做用域鏈是inner---全局,因此這個a至關於無效
                var b = 6;//inner()的做用域鏈是inner---全局,因此這個a至關於無效
                inner(7);
            }
            outter(999);//這個999無效,裏面的e根本找不到
        </script>

 

  在多層的嵌套做用域中能夠定義同名的標識符,這叫做「遮蔽效應」,內部的標識符「遮蔽」了外部的標識符對象

  經過 window.a 這種技術能夠訪問那些被同名變量所遮蔽的全局變量。但非全局的變量若是被遮蔽了,不管如何都沒法被訪問到

        <script type="text/javascript">
            var a = 'Lily';
            var b = 'Lucy';
            function outer() {
                var b = 'Jesica';
                var c = 'Susan';
                function inner(c) {
                    console.log(a);//Lily  
                    console.log(window.b);//Lucy
                    console.log(b);//Jesica
                    console.log(c);//Jenifer
                }
                inner('Jenifer');
            }
            outer();
        </script>    

 

執行環境

  執行環境(execution context),也叫執行上下文。每一個執行環境都有一個變量對象(variable object),保存函數可訪問的全部變量和數據(也就是函數的做用域鏈上的全部數據和變量)。咱們的代碼訪問不到它,它是給引擎使用的。

  執行環境棧,當執行進入一個函數時,函數的執行環境就會被推入一個棧中。而在函數執行完以後,棧將其執行環境移除,它裏面的變量和數據會被標記清除,等待垃圾回收,再把控制權返回給以前的執行環境。javascript 程序中的執行正是由這個機制控制着。

  須要注意的是若是當前執行環境(存放當前做用域鏈裏的數據和變量)找不到變量,那就是找不到了,不會往以前的那個執行環境查找,和做用域鏈是不同的

  代碼的執行順序也不全是一行一行的執行,而是和函數的調用順序有關:

  • 代碼進入全局執行環境,全局執行環境放入環境棧;
  • 當執行到一個函數時,就把這個函數的執行環境推入到環境棧頂端,以前的執行環境日後;
  • 全局執行環境最早進入,因此一直在底端;就和棧的概念差很少;
  • 函數執行完以後,再把它的執行環境從做用域鏈頂端移除,它保存的數據和函數都被標記清除,等待垃圾回收;
  • 控制權交給以前的執行環境,繼續往下執行;
  • 當頁面關閉時,全局執行環境才銷燬;
 1 <script type="text/javascript">
 2     var a = 1;
 3     var b = 2;
 4     var c = 3;
 5     var d = 4;
 6     function inner(d) {//它的做用域鏈是inner---全局
 7         var c = 8;
 8         console.log(a);//1  當前做用域找不到a,去全局做用域找到了a=1
 9         console.log(b);//2  當前做用域找不到b,去全局做用域找到了b=2
10         console.log(c);//8  當前做用域找到了c=8
11         console.log(d);//7  當前做用域找到了d=7,形參也是局部做用域
12     //    console.log(e);//ReferenceError: e is not defined   引用錯誤,找不到e, 它的做用域鏈是inner---全局
13         console.log(a+b+c+d);//18
14     }
15     function outter(e) {
16         var a = 5;//inner()的做用域鏈是inner---全局,因此這個a至關於無效
17         var b = 6;//inner()的做用域鏈是inner---全局,因此這個a至關於無效
18         inner(7);
19     }
20     outter(999);//這個999無效,裏面的e根本找不到
21 </script>

 

  以上代碼的執行順序:

  • 代碼執行進入全局執行環境,並對全局執行環境中的代碼進入聲明提高;    
  • 執行第2行,賦值 a=1; 而後第3行賦值 b=2; 而後第4行賦值 c=3; 而後第5行賦值 d=4;
  • 執行第20行,調用 outer(999) 函數,而後進入outer(999) 函數執行環境,聲明提高,並將實參999傳給形參 e;如今環境棧中有兩個執行環境,outer(999) 是當前執行環境;
  • 執行第16行,賦值 a=5; 而後第17行賦值 b=6;    
  • 執行第18行,調用 inner(7) 函數,而後進入 inner(7) 函數執行環境,聲明提高,並將實參7傳給形參d;
  • 執行第7行,賦值 c=8; 而後運算並輸出;

代碼優化

  因爲在做用域鏈上查找變量是須要消耗性能的,咱們應該儘快的找到變量,因此在函數多層嵌套的時候,咱們應儘量的使用函數內部的局部變量;

  咱們在函數內部使用全局變量能夠說是一種跨做用域操做,若是某個跨做用域的值在函數的內部被屢次使用,那麼咱們就把它存儲到局部變量裏,這樣能夠提升性能。

相關文章
相關標籤/搜索