Scheduling: setTimeout and setInterval
setTimeout
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
// 1 秒後執行函式
function sayHi() {
alert('Hello');
}
setTimeout(sayHi, 1000);
// 帶入參數到函式
function sayHi(phrase, who) {
alert( phrase + ', ' + who );
}
setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John
// 如果第 1 個參數是字串會視為函式,但不推薦
setTimeout("alert('Hello')", 1000);
// 這樣的寫法比較好
setTimeout(() => alert('Hello'), 1000);
// setTimeout 要帶入函式而非結果
// wrong!
setTimeout(sayHi(), 1000);
Canceling with clearTimeout
// 取消 setTimeout 的設定
let timerId = setTimeout(() => alert("never happens"), 1000);
alert(timerId); // timer identifier
clearTimeout(timerId);
alert(timerId); // same identifier (doesn't become null after canceling)
setInterval
// 每隔一段一時間就會執行一次
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
// repeat with the interval of 2 seconds
let timerId = setInterval(() => alert('tick'), 2000);
// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
// 當出現 alert/confirm/prompt,秒數仍在計算
Recursive setTimeout
// 規律性執行程式碼有 2 種選擇,一種用 setInterval,另一種用 recursive setTimeout。
/** instead of:
let timerId = setInterval(() => alert('tick'), 2000);
*/
let timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
// 使用 recursive setTimeout 比較靈活,可以根據情況設定下一次執行時間。
let delay = 5000;
let timerId = setTimeout(function request() {
...send request...
if (request failed due to server overload) {
// increase the interval to the next run
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);
// 透過 recursive setTimeout 可以精準執行,setInterval 無法
let i = 1;
setInterval(function() {
func(i);
}, 100);
透過 recursive setTimeout 每次延遲時間可以精準執行,setInterval 無法。
// setInterval
let i = 1;
setInterval(function() {
func(i);
}, 100);
// 如果函式執行時間超過延遲時間,JavaScript 引擎會等待函式執行完然後立刻執行下一個函式。
// 因為執行函式的時間被計算在延遲時間內,造成延遲時間不能精確執行。
// setTimeout
let i = 1;
setTimeout(function run() {
func(i);
setTimeout(run, 100);
}, 100);
// setTimeout 會等函式執行完才開始計算延遲時間,所以可以準確執行。
Garbage collection
// 當 setTimeout/setInterval 沒有被清除,會一直存在記憶體內不會消失,如果函式指向外部變數
// 外部變數也不會消失,這會造成效能問題
// the function stays in memory until the scheduler calls it
setTimeout(function() {...}, 100);
setTimeout(…,0)
// setTimeout(func) 等目前程式碼執行完畢才會執行,換言之是異步函式
setTimeout(() => alert("World"));
alert("Hello");
// Hello
// World
Splitting CPU-hungry tasks
// 如果有一個很吃資源的執行腳本,會造成整個網頁當掉,這時可以分開程式碼,像是前 100 行先執行
// 後 100 行用 setTimeout 執行
// 下面程式碼執行時網頁當掉
let i = 0;
let start = Date.now();
function count() {
// do a heavy job
for (let j = 0; j < 1e9; j++) {
i++;
}
alert("Done in " + (Date.now() - start) + 'ms');
}
count();
// 改成這樣執行時網頁仍可運作
// 第一次運算 i=1...1,000,000,第二次運算 i=1,000,001..2,000,000,直到 i=1,000,000,000
let i = 0;
let start = Date.now();
function count() {
// do a piece of the heavy job (*)
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
} else {
setTimeout(count); // schedule the new call (**)
}
}
count();
// 在開始執行前就先預設新的呼叫函式,這樣耗費時間較少。
let i = 0;
let start = Date.now();
function count() {
// move the scheduling at the beginning
if (i < 1e9 - 1e6) {
setTimeout(count); // schedule the new call
}
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
}
}
count();
Minimal delay of nested timers in-browser
// 在網頁端 HTML5 規定,經過 5 次巢狀 setTimeout 函式延遲時間至少為 4 ms,
// 但在 server 端沒有這個限制
let start = Date.now();
let times = [];
setTimeout(function run() {
times.push(Date.now() - start); // 保存上次调用的延时
if (start + 100 < Date.now()) alert(times); // 100 毫秒之后,显示延时信息
else setTimeout(run, 0); // 没超过 100 毫秒则再进行调度
}, 0);
// 2,3,5,8,13*,19,24,29,35,40,44,49,55,60,65,71,76,81,87,97,101
Allowing the browser to render
// 執行一個很大的腳本,當更改網頁內容時,會等到函式執行完才顯示解果,用 setTimeout 可以觀察到
// 內容持續改變,可以用來展示進度。
// normal
<div id="progress"></div>
<script>
let i = 0;
function count() {
for (let j = 0; j < 1e6; j++) {
i++;
// 将当前 i 值放到 <div> 内
// (innerHTML 在以后具体章节会讲到,这行代码看懂应该没问题)
progress.innerHTML = i;
}
}
count();
</script>
// setTimeout
<div id="progress"></div>
<script>
let i = 0;
function count() {
// do a piece of the heavy job (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e9) {
setTimeout(count);
}
}
count();
</script>
Last updated