當我在 Firefox 和 Chrome 中運行這段代碼時,結果是不同的:
function run() {
setTimeout(() => console.log("1"), 0);
setTimeout(() => console.log("2"), 100);
let start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
setTimeout(() => {
console.log("3");
}, 0);
start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
setTimeout(() => {
console.log("4");
}, 0);
}
run();
在 Chrome(和 Node.js)中,列印如下:
1
3
2
4
在 Firefox 中,列印如下:
1
2
3
4
但是如果我洗掉第 2 行 ( setTimeout(() => console.log("1"), 0);),那么每個平臺上都會列印相同的內容:
2
3
4
如何解釋這些不同的結果?
謝謝!
uj5u.com熱心網友回復:
解釋:沒關系。
何時將延遲的“訊息”添加到事件回圈訊息佇列中的細節是實作細節,而不是記錄在案的保證。當您的函式將控制權交還給事件回圈時,您的所有setTimeout呼叫都可以執行(其中三個計劃立即運行,其中一個計劃在 100 毫秒內運行)并且您已經保證它一直在自您安排以來至少 400 毫秒。
兩者之間的區別可能很簡單,就像他們是否選擇在新專案插入之前或之后立即查找已經準備好的延遲任務(從延遲佇列移動到主“準備就緒”訊息佇列)主訊息佇列。Chrome 選擇在計劃后 立即移動3(因此3進入,然后是 deferred 2),Firefox 緊隨其后(在它進入之前移動)。23
在不違反任何書面保證的情況下,它們都可以在下一個版本中進行更改。不要依賴它,不要期望它穩定。 雖然保證立即計劃的任務以 FIFO 順序執行,但不能保證延遲的任務何時被移動到“準備就緒”訊息佇列中。規范似乎要求1,3并按4該順序執行(因為它們都立即準備好,而不是延遲),只有2靈活的順序,但即使這樣也不是真正的保證;對于“即時”setTimeout任務實際上可能不會立即安排的各種方式,它可能會變得很奇怪。
您可能對 MDN 檔案感興趣,了解為什么setTimeout需要比預期更長的時間;它通過副作用解釋了事件回圈的許多作業原理,即使它仔細地不保證您正在探索的細節。
uj5u.com熱心網友回復:
我不能給你一個完整的詳細解釋,但是setTimeoputand的第二個引數setInterval并不意味著它會在那個時候準確地執行它。他們會把它放在一個佇列中,這樣后臺就可以執行它。
瀏覽器有一個生命周期,何時執行特定步驟來更新資料和樣式。
我只能向您發送此 youtube 鏈接,這有助于我了解更多資訊:
https://www.youtube.com/watch?v=MCi6AZMkxcU
uj5u.com熱心網友回復:
1, 3, 4, 2是規范當前預期的行為。
但是鑒于 2/3 瀏覽器不遵循這種行為,這實際上可以被認為是一個規范錯誤,我剛剛針對它打開了一個問題:https ://github.com/whatwg/html/issues/8034
問題的癥結在于規格要求
等到該演算法的任何呼叫都完成了,這些呼叫具有相同的global和orderingIdentifier,在此之前開始,并且其毫秒數等于或小于這個。
本段中的問題是毫秒變數。
Firefox、Safari 和 Chrome 的當前穩定頻道都使用預定時間進行訂購。
因此,當事件回圈再次獲得控制權時,它會看到所有計時器都已準備好被呼叫,并按照預定的呼叫順序為每個計時器排隊:
"1": scheduled-time = t=0 0 = 0
"2": scheduled-time = t=0 100 = 100
"3": scheduled-time = t=200 0 = 300
"4": scheduled-time = t=400 0 = 400
但是,規格要求的是:
"1": milliseconds = 0
"3": milliseconds = 0
"4": milliseconds = 0
"2": milliseconds = 100
如果您需要查看代碼,下面是不使用 setTimeout 的重寫(但不會執行所有范圍檢查、呼叫嵌套、邪惡 eval 等實際演算法所做的事情):
顯示代碼片段
// We use a MessageChannel to hook on each iteration of the event loop
function postTask(cb) {
const channel = postTask.channel ??= new MessageChannel();
const { port1, port2 } = channel;
port1.addEventListener("message", (evt) => { cb() }, { once: true });
port1.start();
port2.postMessage("");
}
const timers = new Set();
let ended = false; // So we can stop our loop after some time
function timeoutChecker() {
const now = performance.now();
const toCall = Array.from(timers)
.filter(({ startTime, millis }) => startTime millis <= now)
.sort((a, b) => a.millis - b.millis);
while(toCall.length) {
const timer = toCall.shift();
timers.delete(timer);
timer.callback();
}
if (!ended) {
postTask(timeoutChecker);
}
}
function myTimeout(callback, millis) {
const startTime = performance.now();
timers.add({ startTime, millis, callback });
}
// Begin our loop
postTask(timeoutChecker);
// OP's code
function run() {
myTimeout(() => console.log("1"), 0);
myTimeout(() => console.log("2"), 100);
let start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
myTimeout(() => {
console.log("3");
}, 0);
start = Date.now();
while (Date.now() - start < 200) {
// do nothing
}
myTimeout(() => {
console.log("4");
}, 0);
}
run();
// all should be done after 1s
setTimeout(() => ended = true, 1000);
至于為什么您有時可能會"2"在"4"Chrome 和 node.js 中看到,這是因為它們確實將 0ms 超時限制為 1ms(認為他們正在努力在 Chrome 中洗掉它)。因此,當事件回圈在 處獲得控制權時t=400,此log("4")超時可能尚未滿足計時器條件。
最后關于 Chrome 的分支問題,我必須承認我完全不確定那里會發生什么。運行平分(針對 Canary 分支)我找不到當前穩定分支行為發生的單個修訂,所以這一定是分支設定的事情。[1]:https ://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#run-steps-after-a-timeout
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/495398.html
標籤:javascript 设置超时 事件循环
下一篇:如何在pine腳本中使用var?
