最近筆者在開(kāi)發(fā)的過(guò)程中,因?yàn)樾枰獙?duì)頁(yè)面進(jìn)行動(dòng)態(tài)渲染,又不想一直刷新頁(yè)面數(shù)據(jù),而是讓瀏覽器根據(jù)客戶(hù)需要?jiǎng)討B(tài)對(duì)渲染數(shù)據(jù),這個(gè)時(shí)候就用到了setInterval這個(gè)函數(shù),可是這個(gè)函數(shù)和setTimeout()相比又有什么區(qū)別呢?我們知道:
創(chuàng)新互聯(lián)是一家從事企業(yè)網(wǎng)站建設(shè)、成都做網(wǎng)站、成都網(wǎng)站制作、行業(yè)門(mén)戶(hù)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)制作的專(zhuān)業(yè)網(wǎng)站建設(shè)公司,擁有經(jīng)驗(yàn)豐富的網(wǎng)站建設(shè)工程師和網(wǎng)頁(yè)設(shè)計(jì)人員,具備各種規(guī)模與類(lèi)型網(wǎng)站建設(shè)的實(shí)力,在網(wǎng)站建設(shè)領(lǐng)域樹(shù)立了自己獨(dú)特的設(shè)計(jì)風(fēng)格。自公司成立以來(lái)曾獨(dú)立設(shè)計(jì)制作的站點(diǎn)上1000家。setInterval()執(zhí)行方法其實(shí)是將需執(zhí)行的代碼加入到任務(wù)隊(duì)列,直到輪到代碼執(zhí)行時(shí),確定時(shí)間是否已經(jīng)到了,若到達(dá)則執(zhí)行代碼
var startTime=new Date();
var func = function(){
console.log('start: ' + (new Date()-startTime));
for(var i=0; i<1000000000; i++){}
console.log('end: ' + (new Date()-startTime));
};
setInterval(func,1000);
上面這段代碼的執(zhí)行之后,你會(huì)發(fā)現(xiàn)setInterval的end與start時(shí)間跳動(dòng)非常大,并不是我們?cè)O(shè)置的1000ms。由于setInterval是一開(kāi)始就標(biāo)定了執(zhí)行的時(shí)間點(diǎn),當(dāng)所注冊(cè)的函數(shù)(func)超過(guò),因此不會(huì)是固定的1000ms。
setTimeout()的用法大致與setInterval相同,不同的在于定時(shí)執(zhí)行,我們同樣來(lái)測(cè)試延遲執(zhí)行的現(xiàn)象
var startTime=new Date();
var func = function(){
console.log('start: ' + (new Date()-startTime));
for(var i=0; i<1000000000; i++){};
console.log('end: ' + (new Date()-startTime));
setTimeout(func,1000);
};
setTimeout(func,1000);
觀察下圖可以發(fā)現(xiàn) setTimeout 所設(shè)置的時(shí)間間隔,會(huì)因?yàn)槟壳叭蝿?wù)隊(duì)列所執(zhí)行的代碼而可能發(fā)生延誤執(zhí)行的情況,我們可以發(fā)現(xiàn)上面這段代碼,執(zhí)行func的end與start時(shí)間間隔基本上是符合我們所設(shè)定的1000ms
透過(guò)現(xiàn)象看本質(zhì),我們不妨看下這兩個(gè)函數(shù)的源碼
從函數(shù)聲明可以知道setInterval這里允許傳參,允許我們傳入一個(gè)方法,讓其在一定時(shí)間(number)后執(zhí)行,其時(shí)間等待依賴(lài)于調(diào)度器_scheduler
,而方法的執(zhí)行是由_fnAndFlush
來(lái)負(fù)責(zé)調(diào)控的,不過(guò)由于有_requeuePeriodicTimer
這個(gè)方法的存在使得時(shí)間間隔不是我們所設(shè)固定1000ms
....
case 'setInterval':
task.data!['handleId'] = this._setInterval(
task.invoke, task.data!['delay']!,
Array.prototype.slice.call((task.data as any)['args'], 2));
break;
....
private _setInterval(fn: Function, interval: number, args: any[]): number {
let id = Scheduler.nextId;
let completers = {onSuccess: null as any, onError: this._dequeuePeriodicTimer(id)};
let cb = this._fnAndFlush(fn, completers);
// Use the callback created above to requeue on success.
completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id);
// Queue the callback and dequeue the periodic timer only on error.
this._scheduler.scheduleFunction(cb, interval, args, true);
this.pendingPeriodicTimers.push(id);
return id;
}
先來(lái)看下_fnAndFlush
的代碼,這個(gè)函數(shù)其實(shí)是將我們的所需要執(zhí)行的函數(shù)刷入內(nèi)存中,等待瀏覽器的執(zhí)行
private _fnAndFlush(fn: Function, completers: {onSuccess?: Function, onError?: Function}):
Function {
return (...args: any[]): boolean => {
fn.apply(global, args);
if (this._lastError === null) { // Success
if (completers.onSuccess != null) {
completers.onSuccess.apply(global);
}
// Flush microtasks only on success.
this.flushMicrotasks();
} else { // Failure
if (completers.onError != null) {
completers.onError.apply(global);
}
}
// Return true if there were no errors, false otherwise.
return this._lastError === null;
};
}
我們不妨來(lái)看下_requeuePeriodicTimer
這個(gè)方法所完成的功能的特點(diǎn)。其會(huì)將瀏覽器內(nèi)存中存在的函數(shù)加入隊(duì)列中執(zhí)行(如果存在的話(huà)),函數(shù)功能完成過(guò)程中已經(jīng)進(jìn)入下個(gè)scheduler的執(zhí)行周期,即函數(shù)在執(zhí)行過(guò)程中還有一個(gè)計(jì)時(shí)器在等待下個(gè)函數(shù)的執(zhí)行。正如實(shí)驗(yàn)中,我們觀察到的所有的start間隔大致是1000ms
private _requeuePeriodicTimer(fn: Function, interval: number, args: any[], id: number): Function {
return () => {
// Requeue the timer callback if it's not been canceled.
if (this.pendingPeriodicTimers.indexOf(id) !== -1) {
this._scheduler.scheduleFunction(fn, interval, args, true, false, id);
}
};
}
對(duì)于setTimeout這里允許傳參,允許我們傳入一個(gè)方法,讓其在一定時(shí)間(number)后執(zhí)行,其時(shí)間等待依賴(lài)于調(diào)度器_scheduler,而調(diào)度器正是讓我們的函數(shù)時(shí)間間隔符合我們?cè)O(shè)置的1000ms的原因,而方法的執(zhí)行則是由_fnAndFlush來(lái)負(fù)責(zé)調(diào)控的,因?yàn)閟etTimeout并不會(huì)將執(zhí)行函數(shù)推入隊(duì)列中,因此計(jì)時(shí)器不會(huì)運(yùn)行直到前一個(gè)周期的函數(shù)都執(zhí)行完畢之后才開(kāi)始進(jìn)入下一個(gè)周期,正如實(shí)驗(yàn)代碼中所寫(xiě)的等待1000ms
...
case 'setTimeout':
task.data!['handleId'] = this._setTimeout(
task.invoke, task.data!['delay']!,
Array.prototype.slice.call((task.data as any)['args'], 2));
break;
...
private _setTimeout(fn: Function, delay: number, args: any[], isTimer = true): number {
let removeTimerFn = this._dequeueTimer(Scheduler.nextId);
// Queue the callback and dequeue the timer on success and error.
let cb = this._fnAndFlush(fn, {onSuccess: removeTimerFn, onError: removeTimerFn});
let id = this._scheduler.scheduleFunction(cb, delay, args, false, !isTimer);
if (isTimer) {
this.pendingTimers.push(id);
}
return id;
}
參考資料:
https://www.jeffjade.com/2016/01/10/2016-01-10-javacript-setTimeout/
https://www.jeffjade.com/2016/01/10/2016-01-10-javaScript-setInterval/
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線(xiàn),公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性?xún)r(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專(zhuān)為企業(yè)上云打造定制,能夠滿(mǎn)足用戶(hù)豐富、多元化的應(yīng)用場(chǎng)景需求。