眾所周知,JavaScript是單線程。當(dāng)JavaScript和用戶界面更新在同一個(gè)進(jìn)程中運(yùn)行团甲,JavaScript的執(zhí)行必然會(huì)阻止UI更新,反之亦然黍聂。
一般躺苦,可以通過(guò)控制JavaScript執(zhí)行時(shí)間(不超過(guò)100毫秒)來(lái)盡快更新UI,但是产还,總是有可能需要處理比較復(fù)雜的JavaScript程序匹厘,這時(shí),可以采用定時(shí)器安排代碼延遲執(zhí)行脐区,其能夠幫助你把長(zhǎng)時(shí)間運(yùn)行的腳步分解成一系列的小任務(wù)愈诚。
下面分兩個(gè)部分講解:
1. 事件輪詢(event loop)
關(guān)于事件輪詢的帖子很多(比如老阮的http://www.ruanyifeng.com/blog/2014/10/event-loop.html)。
參考下圖:
- 所有同步任務(wù)在主線程上執(zhí)行牛隅,形成執(zhí)行棧炕柔;
- 一旦觸發(fā)異步事件,比如媒佣,DOM event(點(diǎn)擊按鈕)匕累,ajax事件,定時(shí)器等等默伍,那么哩罪,該事件的處理結(jié)果(即callback事件)會(huì)放在另外一個(gè)隊(duì)列中-任務(wù)隊(duì)列授霸。
- 一旦執(zhí)行棧中同步任務(wù)執(zhí)行完畢,系統(tǒng)會(huì)讀取“任務(wù)隊(duì)列”际插,按照先入先出順序,依次執(zhí)行異步任務(wù)显设。
通過(guò)事件輪詢機(jī)制框弛,異步任務(wù)是不會(huì)阻塞界面更新,允許UI能夠盡快的響應(yīng)后續(xù)變化捕捂。
多個(gè)異步事件是無(wú)序執(zhí)行的的瑟枫,除了setTimeout/setInterval可以指定延遲時(shí)間,其他異步事件都無(wú)法確定何時(shí)被加入到“任務(wù)隊(duì)列”中指攒。
下面我們專門研究下setTimeout慷妙。
2. setTimeout
看一個(gè)簡(jiǎn)單的例子:
var f1 = function(){
console.log('this is setTimeout1!');
};
var f2 = function(){
console.log('this is setTimeout2!');
};
var f3 = function(){
console.log('this is setTimeout3!')
};
var f4 = function(){
console.log('this is setTimeout4!')
};
function func1(){
console.log('this is func1!');
setTimeout(f1, 0);
setTimeout(f2, 200);
console.log('this is func1-end!');
func2();
function func2(){
console.log('this is func2!');
setTimeout(f3, 0);
setTimeout(f4, 100);
console.log('this is func2-end!');
}
}
func1();
// 打印結(jié)果
this is func1!
this is func1-end!
this is func2!
this is func2-end!
this is setTimeout1!
this is setTimeout3!
this is setTimeout4!
this is setTimeout2!
利用Chrome developer timeline分析JavaScript執(zhí)行順序:
可見,在UI繪制完畢之前允悦,會(huì)執(zhí)行func1函數(shù)中的console.log(XXX)膝擂,到了UI繪制完畢后,才開始執(zhí)行第一個(gè)setTimeout回調(diào)函數(shù)-f1隙弛。
根據(jù)延遲時(shí)間的不同架馋,以及執(zhí)行setTimeout的時(shí)間點(diǎn),會(huì)確定異步事件的“任務(wù)隊(duì)列”中的排隊(duì)順序全闷。
注意叉寂,setTimeout第二個(gè)參數(shù)表示任務(wù)何時(shí)被添加到“任務(wù)隊(duì)列”,而不是一定會(huì)在這段時(shí)間后執(zhí)行总珠。
從上圖可見屏鳍,f1在第69.7ms時(shí)才被執(zhí)行,f1執(zhí)行完之后繼續(xù)執(zhí)行f3局服。
f4和f2的執(zhí)行時(shí)間如下圖:
3. 小結(jié)
根據(jù)event loop原理钓瞭,利用setTimeout可以延遲代碼執(zhí)行,并且不阻塞UI更新腌逢。
如果一個(gè)JavaScript執(zhí)行時(shí)間非常長(zhǎng)降淮,那么我們可以考慮用定時(shí)器分解任務(wù),不過(guò)搏讶,必須滿足下面兩個(gè)條件才適合用setTimeout:
- 處理過(guò)程不需要同步
- 數(shù)據(jù)不需要按順序處理
偽代碼如下:
function saveDocument(id) {
var tasks = [openDocument, writeText, closeDocument, updateUI];
setTimeout(function(){
var task = tasks.shift();
task(id);
if (tasks.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}