定義:setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式。
舉個(gè)栗子:

但是setTimeout真的有那么簡(jiǎn)單嗎?到底setTimeout是不是異步執(zhí)行的呢?
沒這么簡(jiǎn)單,再看一個(gè)栗子:

我們?cè)趕etTimeout里面指定了0ms,是希望這段代碼能立即執(zhí)行,但是實(shí)際上并沒有效果,而是先打印出了2,然后才是1,最后大約1s的時(shí)間打印3。
這是不是就說明setTimeout就是異步的呢?
如果是異步的同時(shí)執(zhí)行多個(gè)setTimeout()應(yīng)該同時(shí)會(huì)執(zhí)行,請(qǐng)?jiān)倏聪旅娴睦踝樱?/p>

從上面的代碼可以知道,JS不是多線程的,那js就是單線程的?有么有異步執(zhí)行呢?
出現(xiàn)上面所有誤區(qū)的最主要一個(gè)原因是:我們潛意識(shí)中認(rèn)為,JavaScript引擎有多個(gè)線程在執(zhí)行,JavaScript的定時(shí)器回調(diào)函數(shù)是異步執(zhí)行的.
而事實(shí)上的,JavaScript使用了障眼法,在多數(shù)時(shí)候騙過了我們的眼睛,這里得澄清一個(gè)事實(shí):
JavaScript引擎是單線程運(yùn)行的,瀏覽器無論在什么時(shí)候都只且只有一個(gè)線程在運(yùn)行JavaScript程序。
除了主JavaScript執(zhí)行進(jìn)程外,還需要一個(gè)在進(jìn)程下一次空閑時(shí)執(zhí)行的代碼隊(duì)列(這個(gè)隊(duì)列就是監(jiān)聽執(zhí)行回調(diào)的)。
隨著頁(yè)面生命周期推移,代碼會(huì)按照?qǐng)?zhí)行順序添加入隊(duì)列,例如當(dāng)按鈕被按下的時(shí)候他的事件處理程序會(huì)被添加到隊(duì)列中,并在下一個(gè)可能時(shí)間內(nèi)執(zhí)行。JavaScript中沒有任何代碼是立即執(zhí)行的,但一旦進(jìn)程空閑則盡快執(zhí)行。
所以,定時(shí)器工作方式是當(dāng)特定時(shí)間過去后將代碼插入,但這并不意味著它會(huì)馬上執(zhí)行,只能表示它盡快執(zhí)行。
設(shè)定一個(gè)150ms后執(zhí)行的定時(shí)器,不代表150ms后它會(huì)馬上執(zhí)行,它只表示在150ms后被加入到執(zhí)行隊(duì)列中,如果這個(gè)時(shí)間點(diǎn)執(zhí)行隊(duì)列是空閑的,那么這段代碼就會(huì)被執(zhí)行;其他情況下,代碼可能明顯地等待更長(zhǎng)時(shí)間才會(huì)去執(zhí)行。
看下面這個(gè)栗子:

上面的例子里,一般的理解應(yīng)該是先打印0ms的再打印500ms的,但是,其實(shí)js在解析的時(shí)候遇到了setTimeout方法,第一次過了0ms后把延時(shí)0ms的優(yōu)先加入執(zhí)行隊(duì)列里,再在500ms后把延時(shí)500ms的其放入待執(zhí)行的隊(duì)列里,跳過去順序執(zhí)行下面的代碼,當(dāng)主線程中的代碼執(zhí)行完成后,js引擎去檢索待執(zhí)行的隊(duì)列有沒有待執(zhí)行的代碼,這時(shí)候就會(huì)發(fā)現(xiàn)setTimeout方法并順序執(zhí)行,(因?yàn)橐呀?jīng)過了定時(shí)器規(guī)定的延時(shí)時(shí)間,所以會(huì)立即執(zhí)行,兩個(gè)定時(shí)器的延時(shí)都是一樣的)。
JS執(zhí)行隊(duì)列總結(jié)
(1)所有同步任務(wù)都在主線程上執(zhí)行。
(2)主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列",看看里面有哪些事件。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入主線程執(zhí)行棧,開始執(zhí)行。
(4)主線程不斷重復(fù)上面的第三步。
setTimeout方法、事件和回調(diào)函數(shù)(異步函數(shù))
"任務(wù)隊(duì)列"是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),排在前面的事件,優(yōu)先被主線程讀取。主線程的讀取過程基本上是自動(dòng)的,只要執(zhí)行棧一清空,"任務(wù)隊(duì)列"上第一位的事件就自動(dòng)進(jìn)入主線程。但是,由于"定時(shí)器"功能和事件驅(qū)動(dòng)等,主線程首先要檢查一下執(zhí)行時(shí)間或事件是否被觸發(fā),才能返回主線程。
當(dāng)一個(gè)JS文件中有很多回調(diào)函數(shù)的時(shí)候,我們無法確認(rèn)哪個(gè)回調(diào)會(huì)先進(jìn)入任務(wù)隊(duì)列。所以,遇到回調(diào)的時(shí)候要小心處理啦。