好程序員web前端教程分享JavaScript的執行機制!

  好程序員web前端教程分享JavaScript的執行機制對於Web工程師來講了解JavaScript的執行機制是很重要的。下面就讓咱們一塊兒來看一看吧!javascript

 

1、關於JavaScript前端

 

javascript是一門 單線程 語言,在最新的HTML5中提出了Web-Worker,但javascript是單線程這一核心仍未改變。因此一切javascript版的"多線程"都是用單線程模擬出來的,一切javascript多線程都是紙老虎!java

 

2JavaScript事件循環node

 

既然js是單線程,那就像只有一個窗口的×××須要排隊一個一個辦理業務,同理js任務也要一個一個順序執行。若是一個任務耗時過長,那麼後一個任務也必須等着。那麼問題來了,假如咱們想瀏覽新聞,可是新聞包含的超清圖片加載很慢,難道咱們的網頁要一直卡着直到圖片徹底顯示出來?所以聰明的程序員將任務分爲兩類:程序員

 

1)同步任務web

2)異步任務ajax

 

當咱們打開網站時,網頁的渲染過程就是一大堆同步任務,好比頁面骨架和頁面元素的渲染。而像加載圖片音樂之類佔用資源大耗時久的任務,就是異步任務。關於這部分有嚴格的文字定義,但本文的目的是用最小的學習成本完全弄懂執行機制,因此咱們用導圖來講明:chrome

 

導圖要表達的內容用文字來表述的話:promise

 

·     同步和異步任務分別進入不一樣的執行"場所",同步的進入主線程,異步的進入Event Table並註冊函數。瀏覽器

 

·     當指定的事情完成時,Event Table會將這個函數移入Event Queue

 

·     主線程內的任務執行完畢爲空,會去Event Queue讀取對應的函數,進入主線程執行。

 

·     上述過程會不斷重複,也就是常說的Event Loop(事件循環)

 

咱們不由要問了,那怎麼知道主線程執行棧爲空啊?js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去Event Queue那裏檢查是否有等待被調用的函數。

 

說了這麼多文字,不如直接一段代碼更直白:

 

1let data = [];

2$.ajax({

3    url:www.javascript.com,

4    data:data,

5    success:() => {

6        console.log('發送成功!');

7    }

8})

9console.log('代碼執行結束');

上面是一段簡易的ajax請求代碼:

 

·     ajax進入Event Table,註冊回調函數success

 

·     執行console.log('代碼執行結束')

 

·     ajax事件完成,回調函數success進入Event Queue

 

·     主線程從Event Queue讀取回調函數success並執行。

 

相信經過上面的文字和代碼,你已經對js的執行順序有了初步瞭解。接下來咱們來研究進階話題:setTimeout

 

3、關於setTimeout

 

大名鼎鼎的setTimeout無需再多言,你們對他的第一印象就是異步能夠延時執行,咱們常常這麼實現延時3秒執行:

 

1setTimeout(() => {

2    console.log('延時3');

3},3000)

漸漸的setTimeout用的地方多了,問題也出現了,有時候明明寫的延時3秒,實際卻56秒才執行函數,這又咋回事啊?

 

先看一個例子:

 

1setTimeout(() => {

2    task();

3},3000)

4console.log('執行console');

根據前面咱們的結論,setTimeout是異步的,應該先執行console.log這個同步任務,因此咱們的結論是:

 

1//執行console

2//task()

 

去驗證一下,結果正確!而後咱們修改一下前面的代碼:

 

1setTimeout(() => {

2    task()

3},3000)

4

5sleep(10000000)

乍一看其實差很少嘛,但咱們把這段代碼在chrome執行一下,卻發現控制檯執行task()須要的時間遠遠超過3秒,說好的延時三秒,爲啥如今須要這麼長時間啊?

這時候咱們須要從新理解setTimeout的定義。咱們先說上述代碼是怎麼執行的:

 

·     task()進入Event Table並註冊,計時開始。

 

·     執行sleep函數,很慢,很是慢,計時仍在繼續。

 

·     3秒到了,計時事件timeout完成,task()進入Event Queue,可是sleep也太慢了吧,還沒執行完,只好等着。

 

·     sleep終於執行完了,task()終於從Event Queue進入了主線程執行。

 

上述的流程走完,咱們知道setTimeout這個函數,是通過指定時間後,把要執行的任務(本例中爲task())加入到Event Queue中,又由於是單線程任務要一個一個執行,若是前面的任務須要的時間過久,那麼只能等着,致使真正的延遲時間遠遠大於3秒。

 

咱們還常常遇到setTimeout(fn,0)這樣的代碼,0秒後執行又是什麼意思呢?是否是能夠當即執行呢?

 

答案是不會的,setTimeout(fn,0)的含義是,指定某個任務在主線程最先可得的空閒時間執行,意思就是不用再等多少秒了,只要主線程執行棧內的同步任務所有執行完成,棧爲空就立刻執行。舉例說明:

 

1//代碼1

2console.log('先執行這裏');

3setTimeout(() => {

4    console.log('執行啦')

5},0);

 

1//代碼2

2console.log('先執行這裏');

3setTimeout(() => {

4    console.log('執行啦')

5},3000);

 

代碼1的輸出結果是:

 

1//先執行這裏

2//執行啦

3代碼2的輸出結果是:

4

5//先執行這裏

6// ... 3s later

7// 執行啦

關於setTimeout要補充的是,即使主線程爲空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒。有興趣的同窗能夠自行了解。

 

4、關於setInterval

 

上面說完了setTimeout,固然不能錯過它的孿生兄弟setInterval。他倆差很少,只不事後者是循環的執行。對於執行順序來講,setInterval會每隔指定的時間將註冊的函數置入Event Queue,若是前面的任務耗時過久,那麼一樣須要等待。

 

惟一須要注意的一點是,對於setInterval(fn,ms)來講,咱們已經知道不是每過ms秒會執行一次fn,而是每過ms秒,會有fn進入Event Queue。一旦setInterval的回調函數fn執行時間超過了延遲時間ms,那麼就徹底看不出來有時間間隔了。

 

5Promiseprocess.nextTick(callback)

 

傳統的定時器咱們已經研究過了,接着咱們探究Promiseprocess.nextTick(callback)的表現。

 

Promise的定義和功能本文再也不贅述,不瞭解的讀者能夠學習一下阮一峯老師的Promise。而process.nextTick(callback)相似node.js版的"setTimeout",在事件循環的下一次循環中調用callback回調函數。

 

咱們進入正題,除了廣義的同步任務和異步任務,咱們對任務有更精細的定義:

 

·     macro-task(宏任務):包括總體代碼scriptsetTimeoutsetInterval

 

·     micro-task(微任務)Promiseprocess.nextTick

 

不一樣類型的任務會進入對應的Event Queue,好比setTimeoutsetInterval會進入相同的Event Queue

 

事件循環的順序,決定js代碼的執行順序。進入總體代碼(宏任務)後,開始第一次循環。接着執行全部的微任務。而後再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行全部的微任務。聽起來有點繞,咱們用文章最開始的一段代碼說明:

 

 1setTimeout(function() {

 2    console.log('setTimeout');

 3})

 4

 5new Promise(function(resolve) {

 6    console.log('promise');

 7}).then(function() {

 8    console.log('then');

 9})

10

11console.log('console');

·     這段代碼做爲宏任務,進入主線程。

 

·     先遇到setTimeout,那麼將其回調函數註冊後分發到宏任務Event Queue(註冊過程與上同,下文再也不描述)

 

·     接下來遇到了Promisenew Promise當即執行,then函數分發到微任務Event Queue

 

·     遇到console.log(),當即執行。

 

·     好啦,總體代碼script做爲第一個宏任務執行結束,看看有哪些微任務?咱們發現了then在微任務Event Queue裏面,執行。

 

·     ok,第一輪事件循環結束了,咱們開始第二輪循環,固然要從宏任務Event Queue開始。咱們發現了宏任務Event

QueuesetTimeout對應的回調函數,當即執行。 結束。

 

事件循環,宏任務,微任務的關係如圖所示:

 

咱們來分析一段較複雜的代碼,看看你是否真的掌握了js的執行機制:

 

 1console.log('1');

 2

 3setTimeout(function() {

 4    console.log('2');

 5    process.nextTick(function() {

 6        console.log('3');

 7    })

 8    new Promise(function(resolve) {

 9        console.log('4');

10        resolve();

11    }).then(function() {

12        console.log('5')

13    })

14})

15process.nextTick(function() {

16    console.log('6');

17})

18new Promise(function(resolve) {

19    console.log('7');

20    resolve();

21}).then(function() {

22    console.log('8')

23})

24

25setTimeout(function() {

26    console.log('9');

27    process.nextTick(function() {

28        console.log('10');

29    })

30    new Promise(function(resolve) {

31        console.log('11');

32        resolve();

33    }).then(function() {

34        console.log('12')

35    })

36})

第一輪事件循環流程分析以下:

 

·     總體script做爲第一個宏任務進入主線程,遇到console.log,輸出1

 

·     遇到setTimeout,其回調函數被分發到宏任務Event Queue中。咱們暫且記爲setTimeout1

 

·     遇到process.nextTick(),其回調函數被分發到微任務Event Queue中。咱們記爲process1

 

·     遇到Promisenew Promise直接執行,輸出7then被分發到微任務Event Queue中。咱們記爲then1

 

·     又遇到了setTimeout,其回調函數被分發到宏任務Event Queue中,咱們記爲setTimeout2

 

宏任務Event Queue

 

微任務Event Queue

 

setTimeout1

 

process1

 

setTimeout2

 

then1

 

·     上表是第一輪事件循環宏任務結束時各Event Queue的狀況,此時已經輸出了17

 

·     咱們發現了process1then1兩個微任務。

 

·     執行process1,輸出6

 

·     執行then1,輸出8

 

好了,第一輪事件循環正式結束,這一輪的結果是輸出1768。那麼第二輪時間循環從setTimeout1宏任務開始:

 

·     首先輸出2。接下來遇到了process.nextTick(),一樣將其分發到微任務Event Queue中,記爲process2new

Promise當即執行輸出4then也分發到微任務Event Queue中,記爲then2

 

宏任務Event Queue

 

微任務Event Queue

 

setTimeout2

 

process2

 

null

 

then2

 

·     第二輪事件循環宏任務結束,咱們發現有process2then2兩個微任務能夠執行。

 

·     輸出3

 

·     輸出5

 

·     第二輪事件循環結束,第二輪輸出2435

 

·     第三輪事件循環開始,此時只剩setTimeout2了,執行。

 

·     直接輸出9

 

·     將process.nextTick()分發到微任務Event Queue中。記爲process3

 

·     直接執行new Promise,輸出11

 

·     將then分發到微任務Event Queue中,記爲then3

 

宏任務Event Queue

 

微任務Event Queue

 

null

 

process3

 

null

 

then3

 

·     第三輪事件循環宏任務執行結束,執行兩個微任務process3then3

 

·     輸出10

 

·     輸出12

 

第三輪事件循環結束,第三輪輸出9111012

 

整段代碼,共進行了三次事件循環,完整的輸出爲176824359111012(請注意,node環境下的事件監聽依賴libuv與前端環境不徹底相同,輸出順序可能會有偏差)

 

 

 

6、寫在最後

 

(1)js的異步

 

咱們從最開頭就說javascript是一門單線程語言,不論是什麼新框架新語法糖實現的所謂異步,其實都是用同步的方法去模擬的,緊緊把握住單線程這點很是重要。

 

(2)事件循環Event Loop

 

事件循環是js實現異步的一種方法,也是js的執行機制。

 

(3)javascript的執行和運行

 

執行和運行有很大的區別,javascript在不一樣的環境下,好比node,瀏覽器,Ringo等等,執行方式是不一樣的。而運行大多指javascript解析引擎,是統一的。

 

(4)setImmediate

 

微任務和宏任務還有不少種類,好比setImmediate等等,執行都是有共同點的,有興趣的同窗能夠自行了解。

 

(5)最後的最後

 

javascript是一門單線程語言

Event Loopjavascript的執行機制

 

緊緊把握兩個基本點,以認真學習javascript爲中心,早日實現成爲前端高手的偉大夢想!

相關文章
相關標籤/搜索