# JavaScript經典考題 - setTimeout(Event Loop) & Scope feat.IIFE ## 1. 試問下列輸出為何? ```javascript= for(var i = 1; i <= 5; i++) { setTimeout(function() { console.log(i) }, 0) } ``` ### 答案: > 6 > 6 > 6 > 6 > 6 ### 解說: 1. **setTimeout 結構:setTimeout( callback function, [ 時間 ], [ 參數1, 參數2, ... ] )** :::warning [ 時間 ] 為Optional,決定執行setTimeout之前的等待時間,default 為 0 ::: :::warning [ 參數1, 參數2, ... ] 為Optional,決定帶入 callback function 的參數為何 ::: :::warning setTimeout 為 瀏覽器 提供的 WebAPI ( ex: document、XMLHttpRequest ) ::: :::warning return:回傳代表此setTimeout的ID(Number),可用在clearTimeout() ::: 2. setTimeout 在 **Event Loop** 裡屬於 queues 非同步事件( ex.click、setTimeout、ajax ) - 執行時會先等"時間"倒數完後 - 才把"callback function"丟到 **Callback Queue / Task Queue** - 必須等到 **Call Stack** 所有函式執行完畢 - 才會把"callback function"丟回 **Call Stack** ![img alt](https://prashantb.me/content/images/2017/01/js_runtime.png) >https://prashantb.me/javascript-call-stack-event-loop-and-callbacks/ :::danger Heap:在程式中宣告、定義變數、函式…等的記憶體位置 ::: >[Event Loop 演示工具](http://latentflip.com/loupe/?code=Zm9yKGxldCBpID0gMTsgaSA8PSA1OyBpKyspIHsNCiAgc2V0VGltZW91dChmdW5jdGlvbigpIHsgY29uc29sZS5sb2coaSl9LDUwMDApDQp9!!!) 3. 在此 for迴圈 為 ```javascript for(var i = 1; i <= 5; i++) { ... } ``` - var 作用域為 ==function scope==,當 i 再被賦值的時候,不會被綁在for的{...}裡,也表示 var i 在此不會留下作用域 4. 在此 callback function 為 ```javascript function() { console.log(i) } ``` - 當被丟回 Call Stack 時,由於<font color="red">var i 在此迴圈不會留下作用域</font>,for迴圈已經跑完,i=6 - 此時console.log(i)應該輸出 **<font color="green">"執行時當下的i",也就是迴圈執行完後i=6</font>** :::warning 簡單來說, console.log(i); 的 i 變數會去這個 callback function 的"外層"做存取,而 for迴圈 並不會等待每個 setTimeout 結束才繼續,當 callback function 執行時,拿到的 i 是跑完 for迴圈 的 6 ::: >[重新認識 JavaScript: Day 18 Callback Function 與 IIFE](https://ithelp.ithome.com.tw/articles/10192739) ## 2. 試問下列輸出為何? ```javascript= for(let i = 1; i <= 5; i++) { setTimeout(function() { console.log(i) }, 0) } ``` ### 答案: > 1 > 2 > 3 > 4 > 5 ### 解說: 1. 在此 for迴圈 為 ```javascript for(let i = 1; i <= 5; i++) { ... } ``` - let 作用域為 ==block scope==,當 i 再被賦值的時候,會被綁在for的{...}裡,此時每次迴圈都會留下"塊作用域" 2. 在此 callback function 為 ```javascript function() { console.log(i) } ``` - 當被丟回 Call Stack 時,因為<font color="red">let i每次迴圈都會留下"塊作用域"</font> - 此時console.log(i)應該輸出 **<font color="green">"每次迴圈所綁定作用域的i"</font>** ## 3. 試問下列輸出為何? ```javascript= for(var i = 1; i <= 5; i++) { setTimeout( function(x){ console.log(x) }, 0, i) } ``` ### 答案: > 1 > 2 > 3 > 4 > 5 ### 解說: 3. 在此 setTimeout 為 ```javascript setTimeout( function(x){ console.log(x) }, 0, i) ``` - 每當執行 callback function 時,<font color="red">帶入 i 給參數 x</font> 4. 在此 for迴圈 為 ```javascript for(var i = 1; i <= 5; i++) {...} ``` - 迴圈每次執行所帶入 callback function 的參數 x,因為 **<font color="green">" i 被賦予的值不同"</font>** 而有所不同 ## 4. 試問下列輸出為何? ```javascript= for(var i = 1; i <= 5; i++) { (function (x) { setTimeout(function() { console.log(x) }, 0) })(i) } ``` ### 答案: > 1 > 2 > 3 > 4 > 5 ### 解說: 1. 在此使用了 IIFE 立即函式 ```javascript (function (x) { setTimeout(function() { console.log(x) }, 0) })(i) ``` :::warning **IIFE** (Immediately Invoked Function Expression,立即函式) ,是一個定義完"立即"執行的 JavaScript function <br> **結構**: 大致分為兩部分 ##### 1. 使用 "( )"分組運算子 包起來的 anonymous function(匿名函式) ###### - 使外層無法訪問function內的變數,以避免裡面的變數污染到 global scope (減少"全域變數"的產生 與 避免變數名稱的衝突) ###### - 若立即執行之後有再呼叫此函式的需求,也可以使用函式宣告賦予function名稱 ##### 2. 在(function([參數])(...))後面使用function expression 的 "([呼叫時欲帶入的參數])" ###### - 以立即 Invoke(呼叫/調用) 此函式 ```javascript (function(){ //doSomething... })(); //or (function(參數名稱){ //doSomething... })(參數值); //or (function 函式名稱 (參數名稱){ //doSomething... })(參數值); ``` ::: 2. 在此 console.log(i); 所被帶入的 i 將會是每次迴圈被賦予不同值的i :::warning 也就是每次迴圈裡,函式被立即呼叫時所代入的 **<font color="green">"每次回迴圈當下的 i "</font>**,而 i 被當作參數傳入function(x){...},而留有作用域 ::: ## 5. 試問下列輸出為何? ```javascript= let i = 0; while(i++ <5) { setTimeout(()=> { console.log(i) }, i*1000); } ``` ### 答案: > 5 > 5 > 5 > 5 > 5 ### 如何達到輸出秒數的效果(假設環境不會造成 setTimeout 時間誤差) 1. 給予 setTimeout 執行環境 ```javascript= let i = 0; while(i++ <5) { let x = i; setTimeout(()=> { console.log(x) }, i*1000); } ``` 2. 使用立即函式,鎖住作用域,並帶入參數 ```javascript= let i = 0; while(i++ <5) { ((i)=>{ setTimeout(()=>{ console.log(i) }, i*1000); })(i); } ``` 3. 給予 setTimeout 執行時的參數 ```javascript= let i = 0; while(i++ <5) { setTimeout((x)=> { console.log(x) }, i*1000,i); } ``` <br><br><br><br><br> ###### tags: `JavaScript` `JavaScript 面試考題`