平時我們或多或少會遇到這樣的場景:延遲幾秒執行某個操作,或者每隔幾秒執行某個操作。實現也很簡單使用setTimeout 和setInterval兩個函數就可以解決大多數問題。但我們真的了解setTimeout 嗎?或者說了解其背后的事件循環機制嗎?
- setTimeout()接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數。
console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);
上面代碼執行結果是1,3,2. 原因是setTimeout()將第二行推遲到1000毫秒之后執行。
理解這個很簡單,代碼先打印1,然后啟動一個定時器定時1秒,然后打印3,接著1秒后時間到執行定時器中的回調函數打印2。
如果我們修改下代碼
console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);
func(); //假設這個函數比較耗時,需要2秒的時間
console.log(4);
再修改下代碼
console.log(1);
setTimeout(function(){console.log(2);},0);
console.log(3);
func(); //假設這個函數比較耗時,需要2秒的時間
console.log(4);
這兩段代碼的執行結果分別是什么呢?
A: 1,2,3,4 B: 1.3,4,2 C:1,3,2,4
我們來看看setTimeout 使用時要特別注意的點:
setTimeout其實只是將事件(定時事件)插入到“任務隊列”中,必須等到當前代碼(執行棧)執行完,主線程才會去執行它的指定的回調。
所以上面兩端代碼的打印結果都是1,3,4,2。
來分析下首先代碼打印1,然后執行setTimeout將定時事件(第一個規定1秒后執行,第二個規定了0秒后執行即“立即執行”)插入到“任務隊列”,接著運行打印3,再然后運行func函數這里耗時2秒中,注意這里定時事件不會觸發,不管是1秒還是0秒,然后打印4。至此執行棧執行完成,主線程開始查看任務隊列,發現有定時事件,符合條件后執行相應回調,即打印2。隨意上面兩段代碼的區別是一個打印完4后需要等1秒后打印2,另一個打印4后就立即打印2了。
經過上面的代碼分析其實我們可以得出一個結論:setTimeout指定的時間不是從運行完setTimeout開始計算而是從當前執行棧執行完開始計算。
setTimeout(function(){console.log(1);}, 2000);
func();//該函數耗時5秒。
也就說上面的代碼打印1需要等待7秒。
特別說明:HTML5標準規定了setTimeout()的第二個參數的最小值(最短間隔),不得低于4毫秒,如果低于這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設為10毫秒。所以指定setTimeout第二個參數為0其實會自動變為4,當然一般情況下可以認為兩者效果等價。
引申setTimeout運行的本質其實是往“任務隊列”中插入了一個事件。這個和點擊事件onClick 等其他事件一樣,其實都是往"任務隊列"中插入事件。js 檢查任務隊列需要在當前執行棧執行完成后,主線程空了才去檢查任務隊列中。所以如果我們像上面的例子中有一個耗時的函數func導致執行棧執行完需要很長事件,那么不光是setTimeout會出現你意料之外的結果,而且頁面上的一些其他的點擊事件都會失效,給用戶一種卡死個感覺。解決方法就是盡量優化代碼減少運行事件,或者將函數包裝成異步執行函數。