計時器setTimeout
是咱們常常會用到的,它用於在指定的毫秒數後調用函數或計算表達式。javascript
語法:setTimeout(code, millisec, args);
java
注意:若是code爲字符串,至關於執行eval()
方法來執行code。chrome
固然,這一篇文章並不只僅告訴你怎麼用setTimeout
,並且理解其是如何執行的。瀏覽器
先來看一段代碼:多線程
var start = new Date(); var end = 0; setTimeout(function() { console.log(new Date() - start); }, 500); while (new Date() - start <= 1000) {}
在上面的代碼中,定義了一個setTimeout
定時器,延時時間是500毫秒。app
你是否是以爲打印結果是: 500異步
可事實倒是出乎你的意料,打印結果是這樣的(也許你打印出來會不同,但確定會大於1000毫秒):
函數
這是爲毛呢?究其緣由,這是由於 JavaScript是單線程執行的。也就是說,在任什麼時候間點,有且只有一個線程在運行JavaScript程序,沒法同一時候運行多段代碼。this
再來看看瀏覽器下的JavaScript。spa
瀏覽器的內核是多線程的,它們在內核控制下相互配合以保持同步,一個瀏覽器至少實現三個常駐線程:JavaScript引擎線程,GUI渲染線程,瀏覽器事件觸發線程。
JavaScript引擎
是基於事件驅動單線程執行的,JavaScript引擎一直等待着任務隊列中任務的到來,而後加以處理,瀏覽器不管何時都只有一個JavaScript線程在運行JavaScript程序。
GUI渲染線程
負責渲染瀏覽器界面,當界面須要重繪(Repaint)或因爲某種操做引起迴流(Reflow)時,該線程就會執行。但須要注意,GUI渲染線程與JavaScript引擎是互斥的,當JavaScript引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JavaScript引擎空閒時當即被執行。
事件觸發線程
,當一個事件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JavaScript引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeout、也可來自瀏覽器內核的其餘線程如鼠標點擊、Ajax異步請求等,但因爲JavaScript的單線程關係,全部這些事件都得排隊等待JavaScript引擎處理(當線程中沒有執行任何同步代碼的前提下才會執行異步代碼)。
到這裏,咱們再來回顧一下最初的例子:
var start = new Date(); var end = 0; setTimeout(function() { console.log(new Date() - start); }, 500); while (new Date() - start <= 1000) {}
雖然setTimeout
的延時時間是500毫秒,但是因爲while
循環的存在,只有當間隔時間大於1000毫秒時,纔會跳出while循環,也就是說,在1000毫秒以前,while循環都在佔據着JavaScript線程。也就是說,只有等待跳出while後,線程纔會空閒下來,纔會去執行以前定義的setTimeout。
最後 ,咱們能夠總結出,setTimeout
只能保證在指定的時間後將任務(須要執行的函數)插入任務隊列中等候,可是不保證這個任務在何時執行。一旦執行javascript的線程空閒出來,自行從隊列中取出任務而後執行它。
由於javascript線程並無由於什麼耗時操做而阻塞,因此能夠很快地取出排隊隊列中的任務而後執行它,也是這種隊列機制,給咱們製造一個異步執行的假象。
也許你見過下面這一段代碼:
setTimeout(function(){ // statement}, 0);
上面的代碼表示當即執行。本意是馬上執行調用函數,但事實上,上面的代碼並非當即執行的,這是由於setTimeout有一個最小執行時間,當指定的時間小於該時間時,瀏覽器會用最小容許的時間做爲setTimeout的時間間隔,也就是說即便咱們把setTimeout的延遲時間設置爲0,被調用的程序也沒有立刻啓動。
不一樣的瀏覽器實際狀況不一樣,IE8和更早的IE的時間精確度是15.6ms。不過,隨着HTML5的出現,在高級版本的瀏覽器(Chrome、ie9+等),定義的最小時間間隔是不得低於4毫秒,若是低於這個值,就會自動增長,而且在2010年及以後發佈的瀏覽器中採起一致。
因此說,當咱們寫爲 setTimeout(fn,0)
的時候,實際是實現插隊操做,要求瀏覽器「儘量快」的進行回調,可是實際能多快就徹底取決於瀏覽器了。
那setTimeout(fn, 0)
有什麼用處呢?其實用處就在於咱們能夠改變任務的執行順序!由於瀏覽器會在執行完當前任務隊列中的任務,再執行setTimeout隊列中積累的的任務。
經過設置任務在延遲到0s後執行,就能改變任務執行的前後順序,延遲該任務發生,使之異步執行。
來看一個網上很流行的例子:
document.querySelector('#one input').onkeydown = function() { document.querySelector('#one span').innerHTML = this.value; }; document.querySelector('#second input').onkeydown = function() { setTimeout(function() { document.querySelector('#second span').innerHTML = document.querySelector('#second input').value; }, 0); };
`實例:實例
當你往兩個表單輸入內容時,你會發現未使用setTimeout函數的只會獲取到輸入前的內容,而使用setTimeout函數的則會獲取到輸入的內容。
這是爲何呢?
由於當按下按鍵的時候,JavaScript 引擎須要執行 keydown 的事件處理程序,而後更新文本框的 value 值,這兩個任務也須要按順序來,事件處理程序執行時,更新 value值(是在keypress後)的任務則進入隊列等待,因此咱們在 keydown 的事件處理程序裏是沒法獲得更新後的value的,而利用 setTimeout(fn, 0),咱們把取 value 的操做放入隊列,放在更新 value 值之後,這樣即可獲取出文本框的值。
未使用setTimeout函數,執行順序是:`onkeydown => onkeypress => onkeyup
使用setTimeout函數,執行順序是:
onkeydown => onkeypress => function => onkeyup`
雖然咱們可使用keyup
來替代keydown
,不過有一些問題,那就是長按時,keyup
並不會觸發。
長按時,keydown、keypress、keyup的調用順序:
keydown keypress keydown keypress ... keyup
也就是說keyup只會觸發一次,因此你沒法用keyup來實時獲取值。
咱們還能夠用setImmediate()
來替代setTimeout(fn,0)
:
if (!window.setImmediate) { window.setImmediate = function(func, args){ return window.setTimeout(func, 0, args); }; window.clearImmediate = window.clearTimeout; }
setImmediate()`方法用來把一些須要長時間運行的操做放在一個回調函數裏,在瀏覽器完成後面的其餘語句後,就馬上執行這個回調函數,必選的第一個參數func,表示將要執行的回調函數,它並不須要時間參數。
注意:目前只有IE10支持此方法,固然,在Nodejs中也能夠調用此方法。
3.1 setTimeout中回調函數的this
因爲setTimeout() 方法是瀏覽器 window 對象提供的,所以第一個參數函數中的this實際上是指向window對象,這跟變量的做用域有關。
看個例子:
var a = 1; var obj = { a: 2, test: function() { setTimeout(function(){ console.log(this.a); }, 0); } }; obj.test(); // 1
不過咱們能夠經過使用bind()方法來改變setTimeout回調函數裏的this
var a = 1; var obj = { a: 2, test: function() { setTimeout(function(){ console.log(this.a); }.bind(this), 0); } }; obj.test(); // 2
3.2 setTimeout不止兩個參數
咱們都知道,setTimeout的第一個參數是要執行的回調函數,第二個參數是延遲時間(若是省略,會由瀏覽器自動設置。在IE,FireFox中,第一次配可能給個很大的數字,100ms上下,日後會縮小到最小時間間隔,Safari,chrome,opera則多爲10ms上下。)
其實,setTimeout能夠傳入第三個參數、第四個參數....,它們表示神馬呢?實際上是用來表示第一個參數(回調函數)傳入的參數。
setTimeout(function(a, b){ console.log(a); // 3 console.log(b); // 4},0, 3, 4);
原文連接:你應該知道的 setTimeout 祕密
若是你有疑問或建議,歡迎在下面的評論區評論!