1. 河豚號(hào) > 生活百科 >

js延遲1秒的方法(javascript延時(shí)函數(shù))

異步

簡(jiǎn)單地說,JavaScript 是單線程執(zhí)行的語(yǔ)言,但在使用中有很多異步執(zhí)行的情況。異步的本質(zhì)是用其他方式(相對(duì)同步)控制程序的執(zhí)行順序,這與其他語(yǔ)言中的多線程模型不同,所以常常有人對(duì)非順序 JavaScript 代碼的運(yùn)行結(jié)果感到困惑不解。

一段簡(jiǎn)單的小程序

任何使用過 JavaScript 的程序員都能說出下面這段代碼的輸出:

console.log("A");

setTimeout(() => {

console.log("B");

}, 100);

console.log("C");

先后順序是 A、C、B,因?yàn)榈诙€(gè)參數(shù)的作用是指定延遲的毫秒數(shù),這段代碼只有一個(gè) setTimeout,所以不會(huì)讓人迷惑。

對(duì)類似程序的解釋通常是由 setTimeout 設(shè)置一個(gè)定時(shí)器,在指定毫秒數(shù)后調(diào)用回調(diào)函數(shù)。然而,它的執(zhí)行機(jī)制并不是這么簡(jiǎn)單。實(shí)際上,setTimeout 的作用是在指定的毫秒數(shù)之后,在得到機(jī)會(huì)時(shí),將 callback 放入 Event Loop Queue。

Event Loop

首先要拋出一些概念,通常所說的 JavaScript Engine 是指負(fù)責(zé)執(zhí)行一個(gè)一個(gè) chunk 的程序,它依賴宿主環(huán)境的調(diào)度,也需要通過宿主環(huán)境與操作系統(tǒng)產(chǎn)生關(guān)聯(lián)并得到支持。JavaScript Engine 是 JavaScript Runtime(Hosting Environment) 的一部分。

每個(gè) chunk 通常是以 function 為單位,一個(gè) chunk 執(zhí)行完成后,才會(huì)執(zhí)行下一個(gè) chunk。下一個(gè) chunk 是什么呢?取決于當(dāng)前 Event Loop Queue 中的隊(duì)首。Event Loop Queue 中存放的都是消息,每個(gè)消息關(guān)聯(lián)著一個(gè)函數(shù),JavaScript Engine 就按照隊(duì)列中的消息順序執(zhí)行它們,也就是執(zhí)行 chunk。

所以上面的 setTimeout 實(shí)際執(zhí)行起來更接近這樣:

chunk1執(zhí)行:由 setTimeout 啟動(dòng)定時(shí)器(100毫秒)

chunk2執(zhí)行:得到機(jī)會(huì),將 callback 放入 Event Loop Queue

chunk3執(zhí)行:此 callback 執(zhí)行

不難發(fā)現(xiàn),得到機(jī)會(huì)很重要!這也就可以解釋用 setTimeout 延遲 1000 不一定是準(zhǔn)確的,而是會(huì)至少延遲一秒。因?yàn)槿绻€有其他的任務(wù)在前面,它要等待那些任務(wù)對(duì)應(yīng)的消息都出隊(duì),也就是程序都執(zhí)行完成,它才能將 callback 放入隊(duì)列。也就是實(shí)際延遲會(huì)大于或等于一秒。

通常所說的觸發(fā)了一個(gè)事件,就是指這個(gè) event listener 得到了執(zhí)行。與 setTimeout 這個(gè)例子中的概念一樣,這也是一次 chunk 的執(zhí)行。像這樣一個(gè)一個(gè)執(zhí)行 chunk 的過程就叫 Event Loop。

還有一個(gè)經(jīng)常提到的概念叫「無阻塞」,JavaScript 中的無阻塞就是指這種 Event Loop 模型。除去 alert 或同步 Ajax 請(qǐng)求等歷史原因造成的問題,程序總是不會(huì)出現(xiàn)阻塞;也就是說 JavaScript Engine 總是可以處理下一個(gè)任務(wù),如處理用戶對(duì)瀏覽器的操作。

一些簡(jiǎn)單的小例子

將 setTimeout 加入 try 語(yǔ)句之中,結(jié)果會(huì)如何?

try {

setTimeout(() => {

throw new Error("Error - from try statement");

}, 0);

} catch (e) {

console.error(e);

}

try catch 與 setTimeout 不在同一個(gè) chunk,所以……你懂的。

再看下一個(gè)。

下面的堆棧信息會(huì)輸出 C – B – A 嗎?

setTimeout(function A() {

setTimeout(function B() {

setTimeout(function C() {

throw new Error("Error - from function C");

}, 0);

}, 0);

}, 0);

它們并不對(duì)應(yīng)同一條 Event Loop Queue 中的消息,分別有各自的調(diào)用棧,所以錯(cuò)誤棧里面只有 C。

Job Queue

Job 是 ES6 中新增的概念,它與 Promise 的執(zhí)行有關(guān),可以理解為等待執(zhí)行的任務(wù);Job Queue 就是這種類型的任務(wù)的隊(duì)列。JavaScript Runtime 對(duì)于 Job Queue 與 Event Loop Queue 的處理有所不同。

相同點(diǎn):

都用作先進(jìn)先出隊(duì)列

相異點(diǎn):

每個(gè) JavaScript Runtime 可以有多個(gè) Job Queue,但只有一個(gè) Event Loop Queue

當(dāng) JavaScript Engine 處理完當(dāng)前 chunk 后,優(yōu)先執(zhí)行所有的 Job Queue,然后再處理 Event Loop Queue

ES6 中,一個(gè) Promise 就是一個(gè) PromiseJob,一種 Job。

再來觀察一段小程序:

console.log("A");

setTimeout(() => {

console.log("A - setTimeout");

}, 0);

new Promise((resolve) => {

resolve();

})

.then(() => {

return console.log("A - Promise 1");

})

.then(() => {

return console.log("B - Promise 1");

});

new Promise((resolve) => {

resolve();

})

.then(() => {

return console.log("A - Promise 2");

})

.then(() => {

return console.log("B - Promise 2");

})

.t**粗體文字**hen(() => {

return console.log("C - Promise 2");

});

console.log("AA");

在原生支持 Promise 的環(huán)境,輸出是這樣:

A

AA

A – Promise 1

A – Promise 2

B – Promise 1

B – Promise 2

C – Promise 2

A – setTimeout

理解這個(gè)輸出:

A 與 AA 最先輸出,因?yàn)樗鼈儾皇钱惒饺蝿?wù),屬于第一個(gè) chunk。

Promise 1 與 Promise 2 先于 setTimeout 執(zhí)行,因?yàn)?Job Queue 的執(zhí)行優(yōu)先于 Event Loop Queue。

Promise 1 與 Promise 2 各自的輸出都是順序的,因?yàn)?Job Queue 是先進(jìn)先出隊(duì)列,同一 Job Queue 中的任務(wù)順序執(zhí)行。

Promise 1 與 Promise 2 的后續(xù)任務(wù)是交錯(cuò)的,因?yàn)?Promise 1 與 Promise 2 都是獨(dú)立的 PromiseJob(job 的其中一種),屬于不同的 Job Queue,它們之間的順序規(guī)范中沒有規(guī)定。

并發(fā)

文章開頭,我說「簡(jiǎn)單地說,JavaScript 是單線程執(zhí)行的語(yǔ)言」,現(xiàn)在可以說得稍微復(fù)雜一點(diǎn)了:JavaScript Engine 對(duì) JavaScript 程序的執(zhí)行是單線程的,但是 JavaScript Runtime(整個(gè)宿主環(huán)境)并不是單線程的;而且,幾乎所有的異步任務(wù)都是并發(fā)的,例如多個(gè) Job Queue、Ajax、Timer、I/O(Node)等等。

上面說的是 JavaScript Runtime 層面,JavaScript 執(zhí)行本身,也有一些特殊情況,例如:一個(gè) Web Worker 或者一個(gè)跨域的 iframe,也是獨(dú)立的線程,有各自的內(nèi)存空間(棧、堆)以及 Event Loop Queue。要與這些不同的線程通信,只能通過 postMessage。一次 postMessage 就是在另一個(gè)線程的 Event Loop Queue 中加入一條消息。

參考資料

Concurrency model and Event Loop

ECMAScript® 2015 Language Specification

You Don’t Know JS: Async & Performance

JavaScript異步編程:設(shè)計(jì)快速響應(yīng)的網(wǎng)絡(luò)應(yīng)用

本文由網(wǎng)上采集發(fā)布,不代表我們立場(chǎng),轉(zhuǎn)載聯(lián)系作者并注明出處:http://beyondyourquote.com/shbk/39387.html

聯(lián)系我們

在線咨詢:點(diǎn)擊這里給我發(fā)消息

微信號(hào):15705946153

工作日:9:30-18:30,節(jié)假日休息