【js】關於 setTimeout(0)所引發的……
在看vue的時候,看到了vue關於非同步更新的原理,裡面提到了setTimeout(0)。於是我想起來之前面試的時候被問過這個問題,並且在很久之後我隨便查了查敷衍了事= =,到底是敷衍誰。於是昨天又去google了一下這個問題……果然發現了很多不得了的事(笑哭)。
之前知道,js是單執行緒的,所以在一段時間內只能執行一個任務。然後我們又知道settimeout和setinterver都是存在時延的,因為可能在計時器到達時間後,執行緒正在做其他的任務。所以要等到這個任務結束了才開始執行回撥函式。我以為是因為這樣,所以settimeout(0)由於時延,導致了在下一條語句之後才能執行。。回想了下,那時候用的是百度,以後一定要告別度娘……
首先要說的一個是,現代瀏覽器一般要求settimeout這樣的定時器,時延時間在4毫秒以上,如果小於4毫秒,就要往上加。(另外對於setInterval,H5規定最少時間為10毫秒)所以之前的理解從一定程度上……沒什麼問題TAT。
但是如果從一個稍微複雜的情況下去分析,那就可以發現……這個問題下的js機制相關的問題了。
以下是例子:
這是比較多引用的一個例子,原始碼在這兒:
看了這個例子覺得很神奇,但是看到原文得出的結論覺得匪夷所思。結論認為,在第一個例子,onmousedown的時候,由於單執行緒,在執行onmousedown,所以就拋棄了focus和select。這個說法顯然是非常不正確的。<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>setTimeout</title> <script type="text/javascript" > (function(){ function get(id){ return document.getElementById(id); } window.onload = function(){ get('makeinput').onmousedown = function(){ var input = document.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('value', 'test1'); get('inpwrapper').appendChild(input); input.focus(); input.select(); } get('makeinput2').onmousedown = function(){ var input = document.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('value', 'test1'); get('inpwrapper2').appendChild(input); setTimeout(function(){ input.focus(); input.select(); }, 0); } get('input1').onkeypress = function(){ get('preview1').innerHTML = this.value; } get('input2').onkeypress = function(){ setTimeout(function(){ get('preview2').innerHTML = get('input2').value; },0 ); } } })(); </script> </head> <body> <h1><code>DEMO1</code></h1> <h2>1、未使用 <code>setTimeout</code>(未選中文字框內容)</h2> <button id="makeinput">生成 input</button> <p id="inpwrapper"></p> <h2>2、使用 <code>setTimeout</code>(立即選中文字框內容)</h2> <button id="makeinput2">生成 input</button></h2> <p id="inpwrapper2"></p> -------------------------------------------------------------------------- <h1><code>DEMO2</code></h1> <h2>1、未使用 <code>setTimeout</code>(只有輸入第二個字元時,前一個字元才顯示出來)</h2> <input type="text" id="input1" value=""/><div id="preview1"></div> <h2>2、使用 <code>setTimeout</code>(輸入時,字元同時顯示出來)</h2> <input type="text" id="input2" value=""/><div id="preview2"></div> </body> </html>
這時候提到js的執行機制:event loop。
瀏覽器最起碼有三個執行緒,js執行緒,GUI執行緒,瀏覽器事件觸發執行緒。js執行緒和GUI執行緒是互斥的,當頁面需要渲染(迴流,重繪等)的時候,js執行緒就掛起。js執行的時候GUI執行緒就先快取。瀏覽器事件觸發捕獲相關事件,然後把事件加入到js事件佇列尾部。
把js執行緒看做一個佇列,裡面執行的都是同步的任務。對於非同步的任務,當非同步的任務到了可以執行的時候(比如事件觸發了,計時器的時間到了),就把這個任務取出來加到一個佇列裡面。當主執行緒中的任務執行完了,就到這個非同步佇列裡面取任務執行。
如上所述的過程就是一個事件迴圈的過程,一系列event事件做完了,再取非同步的佇列做。做完了再來一輪。(我覺得是按時間順序,然後先處理同步事件,再處理非同步事件這樣)
所以上面那個例子,沒有執行select()和focus()只是因為執行之後,btn.focus又重新佔據了焦點。
關於settimeout(0),我之前一直以為是像++x++這樣有點無趣,有點變態地考察知識點的方式。然而其實人家還是有正經用途的。由於事件迴圈機制,可以利用settimeout(0)實現非同步,並且可以改變一些事件實現的順序。比如在用promise的時候,通過設定settimeout(0)把同步的呼叫變成非同步的。詳情可以參見promise指南中的例子
參考資料: