前言:這是一道很經典的Js面試題,涉及到閉包、變量做用域、setTimeout等知識,對於深刻理解這些內容頗有幫助html
//問題描述:請寫出最終的輸出值,並解釋緣由 var value1 = 0, value2 = 0, value3 = 0; for ( var i = 1; i <= 3; i++) { var i2 = i; (function() { var i3 = i; setTimeout(function() { value1 += i; value2 += i2; value3 += i3; }, 1); })(); } setTimeout(function() { console.log(value1, value2, value3); }, 100);
//輸出結果:value1=12; value2=9; value3=6
首先,爲了下面解釋這道題,咱們先來補充一些預備知識(大神級人物可跳過這部分)面試
官方解釋:所謂「閉包」,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。閉包
function a(){ var i=0; function b(){ alert(++i); } return b; } var c=a(); c();
方言版:當函數a的內部函數b被外部變量c引用時,就造成了閉包異步
閉包的做用:咱們知道,js裏定義在函數內部的是局部變量,外部是沒法直接訪問的,而它的內部函數能夠訪問,那麼內部函數返回一個值,就至關於相似在外部也能訪問局部變量。函數
閉包的特色:爲了使b中可以訪問i的值,i不會被內存回收,就實現了內存常駐。對於理解這道題很重要。oop
閉包的缺點:內部閉包函數能夠訪問外部函數的變量,因此外部函數的變量不能被釋放,若是閉包嵌套過多,會致使內存佔用大,出現內存溢出。學習
做用域和做用域鏈:關於做用域這裏不作過多解釋,js中根據做用域可分爲全局變量和局部變量。而對於做用域鏈的簡單理解,能夠認爲當一個函數建立以後,從它的執行環境(當前對象)一直到全局對象創建了一個鏈表,可用的變量都掛載在上面。而函數須要查找某個變量值時,變回按照從當前直到全局對象來進行查找。線程
簡介:setTimeout是js中常見的一個函數,屬於window下的方法(一般,你們會省略window)
語法:setTimeout(code,millisec) 參數一爲代碼,參數二爲毫秒數
做用:設定一個時間, 時間到了以後, 就會執行一個指定的函數或表達式,且只執行一次。code
好吧,setTimeout不是這部分核心,核心是解釋js的單線程和事件輪詢機制。
咱們知道,js是單線程的,也就是說全部的任務要排隊執行。
在js中有同步和異步執行——
同步執行:是指前一個任務執行完,而後下一個任務繼續執行,都在主線程裏。
異步執行:則是把事情放進「任務隊列」(或叫事件隊裏),而不是在主線程中,它們經過事件輪詢(Event Loop)和回調來實現調入主線程執行。
繼續回到setTimeout,語法裏面的code就是異步執行的部分。
關於setTimeout更詳細的內容,可點擊這裏學習setTimeout那些事
關於事件輪詢的學習,請點擊這裏理解事件輪詢htm
上面都是些基礎知識,接下來進入正題。
首先,咱們拿到題目,要注意到第一個setTimeout裏面匿名函數,這部分實際上是放在for循環以後纔會執行的,由於它是一個異步執行的函數,被放到了事件隊列裏最後執行。並且,每次setTimeout裏面的函數執行時能夠近似理解爲是一次實例化。
value1
在計算value1時,須要用到i,這裏涉及到做用域鏈的知識,最內層的函數沒有i的值,它會沿着鏈式結構一直向上查找,最終發現i是for循環執行以後的值。此時,i的循環完成,最後一次i++以後,i已經變成了4。這樣,setTimeout執行3次實例化,每次i的值是不變的,最終值爲value1=4+4+4=12。
value2
相似於value1,在執行setTimeout裏的函數時,須要找到i2的值,最終咱們找到的是for循環到第三次時i2=i=3。(i2不會等於4,由於到最後一次i++以後,已經不會再進入循環體了)。因此相似上面,value2的值是3+3+3=9。
value3
這部分就涉及到閉包的理解了。在循環過程當中,經過當即執行函數建立了閉包,每次i3都會被賦予當次循環時i的值並保存,i3的值依次爲1,2,3,最終value3=1+2+3=6。
以上爲我的解釋,若有錯誤,還望各位指正。