js經典面試題--變量提高、執行環境、做用域鏈

js經典面試題--變量提高、執行環境、做用域鏈

今天記錄一個js的經典面試題,該編程題涉及到了js的變量提高、執行環境、做用域鏈問題。前端

一、變量提高
js沒有塊級做用域,使用var聲明的變量會自動添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境。若是初始化變量時沒有使用var變量,該變量會自動被添加到全局環境。下面兩幅圖是等價的,結果都是控制檯打印出1 2 3 4 5
圖片描述面試

二、 執行環境
每一個函數都有本身的執行環境。當執行流進入一個函數時(即調用該函數),函數的環境就會被推入一個環境棧中。而在函數執行以後,將其環境彈出棧,把控制權返回給以前的執行環境。全局執行環境是最外圍的一個執行環境。全局執行環境被認爲是window對象,全局執行環境直到應用程序退出--例如關閉網頁或瀏覽器---時纔會被銷燬。編程

function a(){
    //執行a功能代碼
  }
  a();       //函數a的環境被推入一個環境棧中。
  
  function b(){
    //執行b功能代碼
    var c=function(){
      //執行c功能代碼
      function d(){
        //執行d功能代碼
      }
      retrun d();
    }
    return c();
  }
  b();     //函數b、c、d依次被推入一個環境棧中,當調用b()函數時,其依次被彈出

其執行的具體流程以下圖所示:
圖片描述瀏覽器

三、做用域鏈
當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈。做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一致延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。函數

var a=1;
function b(){
    //執行b功能代碼
    var bVar=1;
    var c=function(){
      //執行c功能代碼
      var cVar=2;
      function d(){
        //執行d功能代碼
        var dVar=3;
        cVar=3;
      }
      retrun d();
    }
    return c();
 }
 b();

圖片描述

以上代碼共涉及4個執行環境:全局環境,b()的局部環境、c()的局部環境、d()的局部環境。全局環境有一個變量a和一個函數b()。b()的局部環境中有一個變量bVar和一個函數c.....依次。位於最裏邊的函數能夠訪問外部環境的全部變量和函數,由於外部環境是它的父執行環境。總結:內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境沒法訪問到內部環境中的任何變量和函數。這些環境之間的聯繫是線性、有次序的。每一個環境均可以向上搜索做用域鏈,以查詢變量和函數名(服從就近原則);但任何環境都不能經過向下搜索做用域而進入另外一個執行環境。spa

經過上面介紹執行環境與做用域的兩幅圖能夠看出,瀏覽器在執行js時,首先會將window對象(全局執行環境)壓入環境棧,每次執行一個函數時,被調用的函數(按照調用的前後順序)依次壓入環境棧中。而壓入棧中的環境相似於容器,往棧底方向的容器包含了上面的容器。容器中存放的是本身的變量和函數以及上面的容器。咱們能夠把容器的玻璃的材質想象爲車窗戶(能夠從裏邊看到外面,可是沒法從外面看到裏邊),當在某個環境(容器)中執行代碼塊時,就比如咱們站在當前容器裏,此時咱們能夠看到外部容器(父級環境)的變量和函數,但卻看不到內部容器的任何東西,這就是做用域鏈。code


下面進入正題,說下我對該面試題的理解對象

1    var foo = {n:1};
2    (function (foo) {
3        console.log(foo.n);
4        foo.n=3;
5        var foo = {n:2};
6        console.log(foo.n);
7    })(foo);
8    console.log(foo.n);

上面的代碼其實能夠寫成這樣:blog

1    var foo = {n:1};
2    (function (foo) {
3         var foo;
4        console.log(foo.n);
5        foo.n=3;
6        var foo = {n:2};
7        console.log(foo.n);
8    })(foo);
9    console.log(foo.n);

一、聲明一個變量,爲引用類型
2和八、聲明一個匿名函數,並當即執行,傳遞的參數是第1行中的foo。將一個對象類型賦值給一個新的變量,因爲對象是引用類型,實質上是指將對象的地址賦值給該變量(也就是說這兩個變量指向同一個地址空間),所以改變新的變量中的屬性值或方法,對應的原來對象的值也會改變。
三、原題中的第5行,因爲存在變量提高,所以會在函數開始就聲明,此時爲undefined;然而因爲一個變量的聲明優先級低於形參,因此這行沒有任何效果
四、打印形參的foo.n,打印1
五、改變第1行變量foo的屬性n的值爲3;
六、從新聲明並定義了一個變量,開闢了新的內存空間,n爲2
七、因爲js中的代碼是自上而下執行,因此此時輸出2
九、上面的函數調用結束後,局部變量被銷燬,而以前的內存空間值已經變爲3,因此輸出3
因此最終的結果爲:1 2 3圖片

相關文章
相關標籤/搜索