聊聊一道簡單的javascript面試題

下面是一道很入門的js面試題:前端

for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i)
  }, 10 * i)
}
複製代碼

幾乎每一個前端在初學js的都會遇到這個問題, 有一段時間也是面試必問的題, 固然如今看到這段代碼幾乎不用想, 輸出確定是10*10.面試

緣由也是很簡單: 變量提高. js沒有塊級做用域, 因此在for循環中定義的i提高爲全局的了, 另外for循環是同步執行的, 全部當setTimeout內部的匿名函數執行的時候i已是10了.閉包

那怎麼解決呢? 也沒啥疑問, 閉包或者用let:異步

// 閉包
for (var i = 0; i < 10; i++) {
  void function (j) {
    setTimeout(function () {
      console.log(j)
    }, 10 * j)
  }(i)
}

// let
for (let i = 0;  i < 10; i++) {
  setTimeout(function () {
    console.log(i)
  }, 10 * i)
}
複製代碼

爲何上面的方法能解決呢? 閉包那個不用多說, 由於js有函數做用域. i做爲參數傳入, 直接綁定到匿名函數上, 做用域鏈到此截至, i再提高也跟他不要緊了. 至於let, 實際上是js內部實現的問題了, 簡單講就是let會生成不一樣的i實例, 10個匿名函數其實分別獲得的是10個不一樣的i實例, 最終獲取的固然是理想值了.函數

固然這篇文章不會在這裏簡單的結束. 咱們再深刻一點, 來看看第一段代碼的執行過程把:ui

image

咱們能夠看到, for 每執行一次, 就會調用setTimeout延遲若干秒向事件隊列推入一個匿名函數, 可是由於for是同步的, 因此推入的匿名函數不是立馬執行的, 而是要等for循環結束, 固然for執行時間很快, 可是影響卻不小, 因爲做用域問題, i被提高了, 當for結束了全局i就是10, 這時候call stack也空了, 匿名函數開始依次推入到call stack執行, 因爲引用的都是變量i, 而i已是10了, 輸出10*10沒毛病. 若是用let呢? 根據mdn, 每次for都會建立一個新的i binding, 也就是說匿名函數引用的是不一樣的i實例. 結果不言而喻.spa

image

因此, 一個簡簡單單的面試題仍是有不少可挖掘的點, 好比上面咱們就涉及了做用域, 異步同步, 事件循環等等, 每一個點均可以深刻說不少: let const的暫存死區, 異步的執行順序, 事件循環的基本實現等等. 但願有機會能夠深刻討論. code

image
相關文章
相關標籤/搜索