用for迴圈執行setTimeout方法印出1~5有哪些解法
前言
我們常用跑迴圈setTimeout來理解var和let的差異,我們知道var的作用域是以function來分界,let是以{}來分界,那現在我們來挑戰不用let,選擇用var來作為解決方案,那該如何解決這個問題呢?
問題
使用var不做任何處理,想取得第一秒時取得1、第二秒取得2、…、第五秒取得5
1 | for (var i = 1; i <= 5; i++) { |
卻發現第一秒卻取得6、第二秒也是取得6,一直到第五秒也是取得6。
為什麼會發生這樣的問題?因為setTimeout屬於非同步函式,不會馬上執行到(會先被放在queue裡面),加上i變數是用var宣告的,進行持續累加,累加超過5跳出迴圈,要注意var是全域變數,最後i++,所以i變成了6,已經污染到全域了。
可以在這段程式碼後面是者印出i來看看,我們可以取得迴圈內的變數i的值console.log(i),會發現印出6
我們一般常會用的解法可能是把var改為let就好了
1 | for (let i = 1; i <= 5; i++) { |
就能拿到預期的結果,第一秒時取得1、第二秒取得2、…、第五秒取得5。
現在我就只想用var不想用let來實驗有沒有辦法得到上述需求
解法1:使用setTimeout傳入參數方式處理
1 | for (var i = 1; i <= 5; i++) { |
setTimeout我們一般使用可能只會傳兩個參數,但setTimeout可以傳入第三個參數,如此一來就不會被全域的問題影響到i值
解法2:使用bind方式處理
1 | for (var i = 1; i <= 5; i++) { |
這個方式是用bind方式,因為沒有要綁任何對象,所以bind方法第一個參數填上null即可,但二個參數就要填上setTimeout傳入的參數i,如此一來也能達到需求
參考:MDN文件:bind
解法3:使用閉包(closure)
1 | for (var i = 1; i <= 5; i++) { |
透過傳入i給立即函式的方式,立即函式裡面用x變數將i變數的值保存下來,進而達到預期效果。
註:閉包要有巢狀的function才算是閉包哦
小結
以上三種方式就是今天限制自己不用let方式,只使用var來達到預期的結果。不過在正式專案中,還是推薦用let的方式來做這個需求會更簡潔,也更好懂,像筆者的一些專案是不會看到任何var的蹤影。