1. 程式人生 > >閉包經典面試題

閉包經典面試題

閉包應該是前段面試中經常碰到的面試題,很多人都會在這個問題上被問住。如果想要弄清楚就要掌握閉包的概念;

首先看面試題:

for (var i = 1; i <= 5; i++) {

  setTimeout( function timer() {

      console.log(i);

  }, 1000 );

}

上面的程式碼會輸出什麼?怎麼改動上述程式碼,使其依次輸出1、2、3、4、5

答案是  程式會先輸出一個"5",後面每隔1000毫秒輸出一個6,

setTimeout()相關知識

在我們學習setTimeout的時候就知道,setTimeout有兩個引數,第一個引數是回撥函式,第二個引數是毫秒數,表示要執行回撥函式所要延遲的時間。但我們還需要知道的是,setTimeout會返回一個Id,即這個定時器的Id,在上面的程式碼中其實已經建立了5個定時器,但是預設只返回了最後的一個Id,所以會先輸出一個5。

接下來就該討論為什麼會輸出5個數字6,而不是1、2、3、4、5了。

因為setTimeout()函式要等執行完函式呼叫棧中的程式碼,然後立即呼叫定時器。這是因為,我們的定時器都被放在了一個被稱為佇列的資料結構中,等待上下文的可執行程式碼執行完畢後,才開始執行定時器,也就是定時器才剛開始計時。所以在定時器的方法執行的時候,變數i已經變成了6,所以輸出的全部是6。

那麼怎麼樣才能輸出1、2、3、4、5呢?

因為5個定時器所打印出來的是同一個i變數,所以想要實現輸出不同的數字,就需要把每個定時器所訪問的變數獨立起來,這就用到了JavaScript的閉包。閉包用途很多,可以很好地區分開各個作用域,避免變數的混淆,但是濫用閉包也會導致效能問題。那麼通過下面的修改就可以了;


for (var i = 1; i <= 5; i++) {

    (function(i){

        setTimeout( function timer() {

              console.log(i);

          },  1000 );

    })(i);

}

塊級作用域--關鍵字let

使用閉包可以得到正確的結果,原因就是改變了i的作用域,那如果我們把迴圈中的每個setTimeout都獨立成一個作用域是不是也能實現同樣的結果呢?我們都知道,在JavaScript中,每個函式是一個獨立的作用域,但是“{}”是不能形成獨立作用域的。

在ES6中提出了一個新的關鍵字let,就可以宣告一個僅對當前“{}”內部有作用的變數。輸出的結果是一樣。

for (let i = 1; i <= 5; i++) {

  setTimeout( function timer() {

      console.log(i);

  }, 1000 );

}