js作用域與執行環境(前端基礎系列)


一、作用域(what?)

  官方解釋是:“一段程序代碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的代碼范圍就是這個名字的作用域。”
  單從文字理解比較難懂,舉個栗子:
        

function outer(){
  // 聲明變量
  var name = "ukerxi";
  
  // 定義內部函數
  function inner() {
    console.log(name); // 可以訪問到 name 變量
  }
}
console.log(name); // 報錯,undefined

  其中變量name聲明在 oute r函數中,當在 outer 中定義一個 inner 函數進行輸出 name,可以得到正確的值,而在 outer 外進行輸出 name 出現 undefined 錯誤;在此可以看出 outer 函數即為 name 變量的作用域(證明過程比較粗略,但結論還是正確的--_--);

 

二、作用域(why?)

  作用域的使用,提高了程序的邏輯的局部性,增強程序的可靠性,以及避免命名沖突;為代碼的模塊化開發提供便利;根據上面提到的函數作用域,name 變量被局限在了 outer 函數中,在其他的函數中也可以定義相同名字的變量,兩者之間不會互相影響;

 

三、js 中的作用域

  先說ES5版本及更低版本的,因為在 ES6 上,重新定義了幾個決定作用域的關鍵字;

  1. 沒有塊級作用域
    在javaScript中,不像C、java等擁有塊級作用域;常見塊級作用域,例如:
    // C語言實現
    for(int i= 0; i<10; i++){
        // 中括號里面就是塊級作用域
    }
    
    if(ture){   
        int i = 1;
        // 這里也是塊級作用域
    }
    printf("%d/n",i); // --“use an undefined variable:i”
    //這里是訪問不到for語句中的i與if語句中的i變量的

    當然這些在javaScript中是沒有的,一般來說只有塊級作用域;所以使用時須注意作用域的影響,例如:
    for(var i= 0; i<10; i++){
        // do something
    }
    
    console.log(i); // --10

    在程序設計過程中可以使用函數作用域,進行模擬塊級作用域;例如:
    function loop(){
        for(var i= 0; i<10; i++){
            // do something
        }
    }
    loop();
    console.log(i); // --undefined

    javaScript是靈活可變的,同樣上面這個例子,可以使用自執行函數重寫實現,這樣就減少了調用這一步;
    (function (){
        for(var i= 0; i<10; i++){
            // do something
        }
    }());
    console.log(i); // --undefined
  2. ES6中的作用域
    在ES6中新增加了(let,const)關鍵字,進行定義變量,解決了沒有塊級作用域的限制;
    let:let允許你聲明一個作用域被限制在塊級中的變量、語句或者表達式。與var關鍵字不同的是,它聲明的變量只能是全局或者整個函數塊的。let聲明的變量只在其聲明的塊或子塊中可用,這一點,與var相似。二者之間最主要的區別在於var聲明的變量的作用域是整個封閉函數。
    具體如下:
    function range () {
      // let 和var 相同的地方,都有函數作用域
      var name = 'ukerxi';
      let nameOuter = 'outer';
    
      for (var j = 0; j < 1; j++) {
        console.log(name)
      }
      console.log("輸出j變量", j); // ==> 1
      for (let i = 0; i < 1; i++) {
        console.log(nameOuter)
      }
      console.log("輸出i變量", i); // 報錯 undefined
    }

    可以看出,使用let 定義的i變量,在for語句外進行輸出時,會進行報錯,說明i不在該作用域內,i的作用域在for包裹的作用內;

  3. 作用域鏈
    每個函數都有自己的執行環境,包含當前環境的變量訪問關系,與之相關聯的就是“變量對象”,如果是當前函數的變量對象,也可稱為“活動對象”;此對象中包含了,當前函數可訪問的變量及函數;變量對象,最開始包含的對象是參數的arguments對象,然后是在函數中定義的其他變量及方法;例如:
    function fn(name){
        var text = "test";
    }
    // 變量對象中包含:命名函數fn變量、參數name、內部變量test


    當然這個變量對象是不可訪問的,只提供后台引擎編譯執行使用;當定義有多個變量對象嵌套,這些變量對象就組成了作用域鏈;例如:

    var name = "global";
    function super() {
        var name = "super";
        function sub(){
             var name = "sub";
        }
    }

    作用域鏈:

    在作用域最前端的是活動對象,而最后端是全局執行環境window(瀏覽器宿主中);變量訪問原則是,根據作用域前端往上進行搜索,如果提前搜索到變量,則停止搜索,例如上面這個例子中,name變量的值是"sub"因為其在最前端的變量對象中已經定義了,就不會往上繼續檢索;

  4. 延長作用域
    有兩種方法可以將作用域進行延長:
    ①、try-catch 語句的catch塊
    ②、with 語句

    兩個語句都是在原本的作用域最前端進行添加一個變量對象;例如:
    var name = "global";
    function test(){
        var name = "sub";
        with(window){
            console.log(name);
        }
    }
    
    test(); // -- "global"

    作用域鏈:


    所以檢索變量時,會先在最前端的window變量對象中檢索;當然,在嚴格模式下已經禁用了with語句,編程時,最好向后兼容,廢棄使用with語句;

  5. 執行環境只與函數的聲明及定義位置有關
    當一個函數定義后其執行環境與作用域鏈就已經確定了,不會因為執行位置改變而改變,具體例子:
    var name = "global";
    function getName(){
        console.log(name);
    }
    
    function test (){
        var name = "inner";
        getName();
    }
    
    // 執行test
    test(); // -- global

    運行test 函數,其中test 函數執行的是 getName 進行輸出 name 變量,輸出的是全局變量的信息;即當 getName 定義時就已經確定了自己的作用域及執行環境,因而不會因為執行位置的不同而輸出不同的信息;當然有一種情況不一樣,那就是靈活的 this

  6. this的動態綁定
    與作用域鏈及執行環境不同,this是根據執行時的接受者進行綁定的,改變this的幾種方法:
    ①、new 關鍵字
    ②、call / apply 方法
    ③、直接調用構造函數

    具體例子如下:
    // 聲明一個類
    function Person (){
        this.name = "ukerxi";
    }
    // 使用new關鍵字,使this執行新建對象
    // 其實是構造函數默認返回this
    var men1 = new Person(); // this綁定到men1上
    
    // 聲明一個空對象,使用call/apply 進行綁定
    var men2 = {};
    Person.call(men2); // this綁定到men2上
    
    // 直接執行構造函數
    Person(); // this綁定到window上(使用嚴格模式則會報錯,this指向undefined)

     

【結束語】

   系列文章,包括了原創,翻譯,轉載等各類型的文章;一方面是為了自己總結,另一方面頁希望可以共享知識;在技術方面有輸入,也要有所輸出,才能更進一步!文章基於自己的實踐、閱讀及理解,如有不合理及錯誤的地方,煩請各大佬評論指出,以便改正,感謝!

 


免責聲明!

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



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