1. 程式人生 > >圖例詳解那道 setTimeout 與迴圈閉包的經典面試題

圖例詳解那道 setTimeout 與迴圈閉包的經典面試題

轉自:www.jianshu.com/p/9b4a54a98660

http://www.toutiao.com/i6430009354899948034/

前言

如果寫過js程式碼的人對於setTimeout方法一定不會感到陌生。setTimeout是一種定時器,在前端開發中有很多的應用場景,比如在購物車結算成功後,等待幾秒會自動跳轉至列表頁。今天我們就深入的看下setTimeout的實現原理。

Javascript中的setTimeout黑魔法

Javascript之setTimeout

基本用法

根據W3C的標準解釋,setTimeout是定義一個在指定時間後觸發的函式。

我們先來看看setTimeout的基本用法,實現這樣一個簡單的效果,點選一個button,在3秒後頁面上的文字消失。

Javascript中的setTimeout黑魔法

setTimeout基本用法

由於這段程式碼非常基礎,這裡不做過多講述。

setTimeout(fn, 0)

上面一部分說到setTimeout是相當於給函式定義一個‘鬧鐘’,當到了指定的時間後就會自動執行函式。但是如果我們將時間設定為0,即出現setTimeout(fn, 0)這樣的程式碼,情況是怎麼樣的呢?是會立即執行嗎?

我們可以通過以下一段程式碼來進行測試。

Javascript中的setTimeout黑魔法

測試程式碼

如果和我們猜測的一樣,立即執行的話,上面的測試程式碼會按照1 > 2 > 3的順序輸出,但是實際執行後,我們發現輸出結果的順序為1 > 3 > 2,而且不管執行多少次結果都不變。

出現了這樣的結果,就證明了setTimeout(0)並不是立即執行的,那這又該怎麼解釋呢?

JS單執行緒執行

為了解釋上面這個問題,我們要追溯到JS執行過程,我們都知道JS是單執行緒執行,所有的非同步事件,包括自定義的頁面DOM事件,定時器,Ajax請求都會被新增到一個任務列表按照順序執行。

因為JS指令碼檔案是執行在瀏覽器端的,我們的JS引擎雖然是單執行緒的,但是對於瀏覽器來說確是多執行緒的。瀏覽器中不僅包括JS引擎,還包括網路請求Ajax,瀏覽器渲染等,它們都有特定的執行緒去執行。

setTimeout並不能作為多執行緒使用,可以通過以下一段程式碼來證明。

Javascript中的setTimeout黑魔法

測試程式碼

對於以上一段程式碼,如果setTimeout可以作為多執行緒使用,則新的執行緒會在一秒後將isEnd屬性設定false,那麼在一秒後會alert出end字串。

但是實際情況確是,頁面從未打印出end字串,而且頁面會呈現鎖死狀態,這是因為isEnd變數值並未修改為false,相當於執行while(true),最終頁面會崩潰。這也就能證明JS引擎是單執行緒執行狀態。

事件佇列

既然JS引擎是單執行緒執行,那麼setTimeout定義的事件該具體何時觸發呢?

這裡我們需要深入到瀏覽器核心設計,在核心中涉及到一個事件佇列的概念,我們可以直接看如下這張圖。

Javascript中的setTimeout黑魔法

事件佇列

從上面這張圖很容易看出,在瀏覽器核心中包含了各式各樣的執行緒,有瀏覽器GUI渲染執行緒,Javascript引擎執行緒,網路請求執行緒。

在當JS引擎執行到其他執行緒相關的程式碼時,就會執行其他執行緒的程式碼,在其他執行緒執行完畢後需要JS引擎重新執行時,就會在JS引擎的事件佇列裡新增一個任務。

現在我們來看看setTimeout(0)做了什麼?它會開啟一個定時器執行緒,並不會影響後續的程式碼執行,這個定時器執行緒會在事件佇列後面新增一個任務,例如上面圖中的t3。等到前面的t1,t2執行完後再去執行t3,因此在前面第二部分內容中的輸出順序為1 > 3 > 2。

setInterval

既然說到了setTimeout,就不得不提到setInterval,setInterval同樣作為一種定時器,是在指定的時間間隔後執行相應的函式。

一種最常見的場景是頁面上的倒計時實現。這裡我們實現一個簡單的效果,指定一個時間,並進行倒計時。

Javascript中的setTimeout黑魔法

倒計時

對比

setTimeout與setInterval雖然都是定時器,但是在執行上還是有不一樣的。

  1. setTimeout是指定的時間後執行一次;setInterval是在每隔指定的時間後執行多次。

  2. setTimeout(fn1, t1),fn1的執行時間是大於或等於t1的;setInterval(fn2, t2),fn2的執行會始終嘗試在t2時間後執行,如果網路請求較大的話,會出現fn2連續執行的情況。