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

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

我們在setTimeout里面指定了0ms,是希望這段代碼能立即執(zhí)行,但是實際上并沒有效果,而是先打印出了2,然后才是1,最后大約1s的時間打印3。
這是不是就說明setTimeout就是異步的呢?
如果是異步的同時執(zhí)行多個setTimeout()應(yīng)該同時會執(zhí)行,請再看下面的栗子:

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

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