你應該知道的setTimeout秘密

計時器setTimeout是我們經(jīng)常會用到的骡楼,它用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計算表達式洪囤。

語法:setTimeout(code, millisec, args);

注意:如果code為字符串括堤,相當于執(zhí)行eval()方法來執(zhí)行code残腌。

當然孵坚,這一篇文章并不僅僅告訴你怎么用setTimeout,而且理解其是如何執(zhí)行的烹玉。

1、setTimeout原理

先來看一段代碼:

var end = 0; 
setTimeout(function() {  console.log(new Date() - start); }, 500); 
while (new Date() - start <= 1000) {}```

在上面的代碼中阐滩,定義了一個`setTimeout`定時器二打,延時時間是500毫秒。

你是不是覺得打印結(jié)果是: 500

可事實卻是出乎你的意料掂榔,打印結(jié)果是這樣的(也許你打印出來會不一樣继效,但肯定會大于1000毫秒):
![setTimeout](http://upload-images.jianshu.io/upload_images/621214-f3001fbb9989ccde?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

這是為毛呢?究其原因装获,這是因為 JavaScript是單線程執(zhí)行的莲趣。也就是說,在任何時間點饱溢,有且只有一個線程在運行JavaScript程序喧伞,無法同一時候運行多段代碼。

再來看看瀏覽器下的JavaScript绩郎。

瀏覽器的內(nèi)核是多線程的潘鲫,它們在內(nèi)核控制下相互配合以保持同步,一個瀏覽器至少實現(xiàn)三個常駐線程:JavaScript引擎線程肋杖,GUI渲染線程溉仑,瀏覽器事件觸發(fā)線程。

- `JavaScript引擎`是基于事件驅(qū)動單線程執(zhí)行的状植,JavaScript引擎一直等待著任務隊列中任務的到來浊竟,然后加以處理,瀏覽器無論什么時候都只有一個JavaScript線程在運行JavaScript程序津畸。

- `GUI渲染線程`負責渲染瀏覽器界面振定,當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(Reflow)時,該線程就會執(zhí)行肉拓。但需要注意后频,GUI渲染線程與JavaScript引擎是互斥的,當JavaScript引擎執(zhí)行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JavaScript引擎空閑時立即被執(zhí)行卑惜。

- `事件觸發(fā)線程`膏执,當一個事件被觸發(fā)時,該線程會把事件添加到待處理隊列的隊尾露久,等待JavaScript引擎的處理更米。這些事件可來自JavaScript引擎當前執(zhí)行的代碼塊如setTimeout、也可來自瀏覽器內(nèi)核的其他線程如鼠標點擊毫痕、Ajax異步請求等征峦,但由于JavaScript的單線程關系,所有這些事件都得排隊等待JavaScript引擎處理(當線程中沒有執(zhí)行任何同步代碼的前提下才會執(zhí)行異步代碼)镇草。

到這里,我們再來回顧一下最初的例子:

```var start = new Date();
 var end = 0;
 setTimeout(function() {  console.log(new Date() - start); }, 500);
 while (new Date() - start <= 1000) {}```

雖然`setTimeout`的延時時間是500毫秒瘤旨,可是由于`while`循環(huán)的存在梯啤,只有當間隔時間大于1000毫秒時,才會跳出while循環(huán)存哲,也就是說因宇,在1000毫秒之前,while循環(huán)都在占據(jù)著JavaScript線程祟偷。也就是說察滑,只有等待跳出while后,線程才會空閑下來修肠,才會去執(zhí)行之前定義的setTimeout贺辰。

最后 ,我們可以總結(jié)出嵌施,`setTimeout`只能保證在指定的時間后將任務(需要執(zhí)行的函數(shù))插入任務隊列中等候饲化,但是不保證這個任務在什么時候執(zhí)行。一旦執(zhí)行javascript的線程空閑出來吗伤,自行從隊列中取出任務然后執(zhí)行它吃靠。

因為javascript線程并沒有因為什么耗時操作而阻塞,所以可以很快地取出排隊隊列中的任務然后執(zhí)行它足淆,也是這種隊列機制巢块,給我們制造一個異步執(zhí)行的假象。

**2巧号、setTimeout的好搭檔“0”**

也許你見過下面這一段代碼:

```setTimeout(function(){ // statement}, 0);```

上面的代碼表示立即執(zhí)行族奢。本意是立刻執(zhí)行調(diào)用函數(shù),但事實上丹鸿,上面的代碼并不是立即執(zhí)行的歹鱼,這是因為setTimeout有一個最小執(zhí)行時間,當指定的時間小于該時間時卜高,瀏覽器會用最小允許的時間作為setTimeout的時間間隔弥姻,也就是說即使我們把setTimeout的延遲時間設置為0南片,被調(diào)用的程序也沒有馬上啟動。

不同的瀏覽器實際情況不同庭敦,IE8和更早的IE的時間精確度是15.6ms疼进。不過,隨著HTML5的出現(xiàn)秧廉,在高級版本的瀏覽器(Chrome伞广、ie9+等),定義的最小時間間隔是不得低于4毫秒疼电,如果低于這個值嚼锄,就會自動增加,并且在2010年及之后發(fā)布的瀏覽器中采取一致蔽豺。

所以說区丑,當我們寫為 `setTimeout(fn,0)` 的時候,實際是實現(xiàn)插隊操作修陡,要求瀏覽器“盡可能快”的進行回調(diào)沧侥,但是實際能多快就完全取決于瀏覽器了。

那`setTimeout(fn, 0)`有什么用處呢魄鸦?其實用處就在于我們可以改變?nèi)蝿盏膱?zhí)行順序宴杀!因為瀏覽器會在執(zhí)行完當前任務隊列中的任務,再執(zhí)行setTimeout隊列中積累的的任務拾因。

通過設置任務在延遲到0s后執(zhí)行旺罢,就能改變?nèi)蝿請?zhí)行的先后順序,延遲該任務發(fā)生绢记,使之異步執(zhí)行主经。

來看一個網(wǎng)上很流行的例子:

```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);
}; ```

`實例:[實例](http://ghmagical.com/iframe/modal/code/:javascript:setTimeout)

當你往兩個表單輸入內(nèi)容時,你會發(fā)現(xiàn)未使用setTimeout函數(shù)的只會獲取到輸入前的內(nèi)容庭惜,而使用setTimeout函數(shù)的則會獲取到輸入的內(nèi)容罩驻。

這是為什么呢?

因為當按下按鍵的時候护赊,JavaScript 引擎需要執(zhí)行 keydown 的事件處理程序惠遏,然后更新文本框的 value 值,這兩個任務也需要按順序來骏啰,事件處理程序執(zhí)行時节吮,更新 value值(是在keypress后)的任務則進入隊列等待,所以我們在 keydown 的事件處理程序里是無法得到更新后的value的判耕,而利用 setTimeout(fn, 0)透绩,我們把取 value 的操作放入隊列,放在更新 value 值以后,這樣便可獲取出文本框的值帚豪。

未使用setTimeout函數(shù)碳竟,執(zhí)行順序是:`onkeydown => onkeypress => onkeyup

`使用setTimeout函數(shù),執(zhí)行順序是:`onkeydown => onkeypress => function => onkeyup`

雖然我們可以使用`keyup`來替代`keydown`狸臣,不過有一些問題莹桅,那就是長按時,`keyup`并不會觸發(fā)烛亦。

長按時诈泼,keydown、keypress煤禽、keyup的調(diào)用順序:

```keydown
keypress
keydown
keypress
...
keyup```

也就是說keyup只會觸發(fā)一次铐达,所以你無法用keyup來實時獲取值。

我們還可以用`setImmediate()`來替代`setTimeout(fn,0)`:

```if (!window.setImmediate) {  
    window.setImmediate = function(func, args){  
      return window.setTimeout(func, 0, args);  
   };  
  window.clearImmediate = window.clearTimeout;
 }```

setImmediate()`方法用來把一些需要長時間運行的操作放在一個回調(diào)函數(shù)里檬果,在瀏覽器完成后面的其他語句后瓮孙,就立刻執(zhí)行這個回調(diào)函數(shù),必選的第一個參數(shù)func汁汗,表示將要執(zhí)行的回調(diào)函數(shù)衷畦,它并不需要時間參數(shù)栗涂。

注意:目前只有IE10支持此方法知牌,當然,在Nodejs中也可以調(diào)用此方法斤程。

**3角寸、setTimeout的一些秘密****

3.1 setTimeout中回調(diào)函數(shù)的this**

由于setTimeout() 方法是瀏覽器 window 對象提供的,因此第一個參數(shù)函數(shù)中的this其實是指向window對象忿墅,這跟變量的作用域有關扁藕。

看個例子:

```var a = 1; 
var obj = {  
a: 2, 
 test: function() {  setTimeout(function(){  console.log(this.a);  }, 0);  
} 
}; 
obj.test(); // 1```

不過我們可以通過使用bind()方法來改變setTimeout回調(diào)函數(shù)里的this

```var a = 1; 
var obj = { 
 a: 2, 
 test: function() {  
setTimeout(function(){  
console.log(this.a);  
}.bind(this), 0);  
}
 }; 
obj.test(); // 2```

相關文章:[JS中的call、apply疚脐、bind方法](http://ghmagical.com/article/page/id/UPLfoGI9vJ91)

**3.2 setTimeout不止兩個參數(shù)**

我們都知道亿柑,setTimeout的第一個參數(shù)是要執(zhí)行的回調(diào)函數(shù),第二個參數(shù)是延遲時間(如果省略棍弄,會由瀏覽器自動設置望薄。在IE,F(xiàn)ireFox中呼畸,第一次配可能給個很大的數(shù)字痕支,100ms上下,往后會縮小到最小時間間隔蛮原,Safari卧须,chrome,opera則多為10ms上下。)

其實花嘶,setTimeout可以傳入第三個參數(shù)笋籽、第四個參數(shù)....,它們表示神馬呢察绷?其實是用來表示第一個參數(shù)(回調(diào)函數(shù))傳入的參數(shù)干签。

```setTimeout(function(a, b){  
console.log(a); // 3 
console.log(b); // 4},0, 3, 4);```

原文鏈接:[你應該知道的setTimeout秘密](http://ghmagical.com/article/page/id/H61NOVU0RZ9Y)

如果你有疑問或建議,歡迎在下面的評論區(qū)評論拆撼!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末容劳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子闸度,更是在濱河造成了極大的恐慌竭贩,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莺禁,死亡現(xiàn)場離奇詭異留量,居然都是意外死亡,警方通過查閱死者的電腦和手機哟冬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門楼熄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浩峡,你說我怎么就攤上這事可岂。” “怎么了翰灾?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵缕粹,是天一觀的道長。 經(jīng)常有香客問我纸淮,道長平斩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任咽块,我火速辦了婚禮绘面,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侈沪。我一直安慰自己揭璃,他們只是感情好,可當我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布峭竣。 她就那樣靜靜地躺著塘辅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪皆撩。 梳的紋絲不亂的頭發(fā)上扣墩,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天哲银,我揣著相機與錄音,去河邊找鬼呻惕。 笑死荆责,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的亚脆。 我是一名探鬼主播做院,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼濒持!你這毒婦竟也來了键耕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤柑营,失蹤者是張志新(化名)和其女友劉穎屈雄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體官套,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡酒奶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奶赔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惋嚎。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖站刑,靈堂內(nèi)的尸體忽然破棺而出另伍,到底是詐尸還是另有隱情,我是刑警寧澤笛钝,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布质况,位于F島的核電站愕宋,受9級特大地震影響玻靡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜中贝,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一囤捻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邻寿,春花似錦蝎土、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒜撮,卻和暖如春暴构,著一層夾襖步出監(jiān)牢的瞬間跪呈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工取逾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耗绿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓砾隅,卻偏偏與公主長得像误阻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子晴埂,可洞房花燭夜當晚...
    茶點故事閱讀 43,566評論 2 349

推薦閱讀更多精彩內(nèi)容