您好,登錄后才能下訂單哦!
最近筆者在開發的過程中,因為需要對頁面進行動態渲染,又不想一直刷新頁面數據,而是讓瀏覽器根據客戶需要動態對渲染數據,這個時候就用到了setInterval這個函數,可是這個函數和setTimeout()相比又有什么區別呢?我們知道:
setInterval()執行方法其實是將需執行的代碼加入到任務隊列,直到輪到代碼執行時,確定時間是否已經到了,若到達則執行代碼
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);
上面這段代碼的執行之后,你會發現setInterval的end與start時間跳動非常大,并不是我們設置的1000ms。由于setInterval是一開始就標定了執行的時間點,當所注冊的函數(func)超過,因此不會是固定的1000ms。
setTimeout()的用法大致與setInterval相同,不同的在于定時執行,我們同樣來測試延遲執行的現象
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);
觀察下圖可以發現 setTimeout 所設置的時間間隔,會因為目前任務隊列所執行的代碼而可能發生延誤執行的情況,我們可以發現上面這段代碼,執行func的end與start時間間隔基本上是符合我們所設定的1000ms
透過現象看本質,我們不妨看下這兩個函數的源碼
從函數聲明可以知道setInterval這里允許傳參,允許我們傳入一個方法,讓其在一定時間(number)后執行,其時間等待依賴于調度器_scheduler
,而方法的執行是由_fnAndFlush
來負責調控的,不過由于有_requeuePeriodicTimer
這個方法的存在使得時間間隔不是我們所設固定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;
}
先來看下_fnAndFlush
的代碼,這個函數其實是將我們的所需要執行的函數刷入內存中,等待瀏覽器的執行
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;
};
}
我們不妨來看下_requeuePeriodicTimer
這個方法所完成的功能的特點。其會將瀏覽器內存中存在的函數加入隊列中執行(如果存在的話),函數功能完成過程中已經進入下個scheduler的執行周期,即函數在執行過程中還有一個計時器在等待下個函數的執行。正如實驗中,我們觀察到的所有的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);
}
};
}
對于setTimeout這里允許傳參,允許我們傳入一個方法,讓其在一定時間(number)后執行,其時間等待依賴于調度器_scheduler,而調度器正是讓我們的函數時間間隔符合我們設置的1000ms的原因,而方法的執行則是由_fnAndFlush來負責調控的,因為setTimeout并不會將執行函數推入隊列中,因此計時器不會運行直到前一個周期的函數都執行完畢之后才開始進入下一個周期,正如實驗代碼中所寫的等待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/
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。