js執行機制

在寫js代碼的時候咱們每每都但願js代碼的執行順序是按照本身所想那樣執行,但結果總事與願違。因此今天就想花時間搞懂js的事件執行機制究竟是什麼樣的。javascript

EventLoop事件循環機制

js中事件分爲宏任務和微任務兩類,任務又分爲同步任務和異步任務。他們之間的執行順序將依次進行說明html

js中事件的同步異步的執行順序

js中事件分爲兩類java

  • 同步事件
  • 異步事件
  • js執行順序:先執行同步再執行異步
  1. js在執行時若是遇到同步任務就直接放入主線程執行
  2. 若是遇到異步任務就將其放入event table 中並註冊函數
  3. 當異步事件完成後會將他的放入Event Queue中等待執行(這裏特別說明宏任務和微任務各自有一個Event Queue)

那麼等到何時才能執行呢?就是等到主線程的任務執行完畢爲空的時候。那咱們怎麼知道何時主線程爲空呢?js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去Event Queue那裏檢查是否有等待被調用的函數。 用流程圖來歸納上述步驟:node

  • 代碼示例
let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('發送成功!');
    }
})
console.log('代碼執行結束');
複製代碼
  1. 從上往下,先遇到$.ajax異步請求,因此ajax進入event table中註冊回調函數success
  2. 而後執行同步任務console.log("代碼執行結束")
  3. ajax請求完成,回調函數success進入Event Queue
  4. 主線程任務執行完畢,從Event Queue中讀取回調函數success並執行

js中事件的宏任務與微任務的執行順序

js中任務分爲兩類ajax

  • 宏任務:包括總體代碼script,setTimeout,setInterval
  • 微任務:Promise.then(非new Promise),process.nextTick(node中)
  • js執行順序:先執行宏任務再執行微任務
  1. js執行事件時先執行線程或Event Queue的宏任務
  2. 當前事件的宏任務執行完畢查看此線程或Event Queue是否有微任務,有的話執行微任務,沒有的話執行新的宏任務

用流程圖表示上述流程:promise

  • 代碼示例
setTimeout(function() {
    console.log('setTimeout');
},1000)

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');
複製代碼
  1. 首先遇到了setTimeout函數,他是異步任務,因此將它放入event table中註冊回調函數,又由於setTimeout是宏任務,因此1秒後將setTimeout放入宏任務的Event Queue中等待執行。
  2. 遇到同步代碼new promise放入主線程直接開始執行
  3. 執行new promise的console.log('promise')而後看到.then是微任務所以將其放入微任務的Event Queue中
  4. 接下來執行同步代碼console.log('console')
  5. 主線程的宏任務,已經執行完畢,接下來要執行微任務,所以會執行Event Queue中的Promise.then,到此,第一輪事件循環執行完畢
  6. 第二輪事件循環開始,先執行宏任務,即setTimeout的回調函數,而後查找是否有微任務,沒有,時間循環結束

如今看一個比較複雜的例子瀏覽器

console.log('1');
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
輸出:
1
2
4
3
5
複製代碼
  1. 同步代碼console.log('1')直接放入主線程執行
  2. setTimeout異步代碼放到event table中註冊回調函數,setTimeout事件完成將其回調函數放入宏任務的Event Queue中等待執行
  3. 而後主線程爲空,先在宏任務的Event Queue中讀取回調函數運行,直接執行console.log('2');
  4. 而後遇到process.nextTick放入微任務的Event Queue中
  5. 遇到new Promise執行console.log('4')而後將.then放入微任務的Event Queue中
  6. 宏任務執行完畢,查看微任務的Event Queue中是否有等待執行的微任務
  7. 執行process.nextTick的console.log('3')
  8. 執行.then的console.log('5')

js中的同步和異步

你們都知道js是是一門單線程語言,因此他的語句確定是一句一句執行的。可是js中又存在同步操做和異步操做,那麼就會有疑問,js是如何經過單線程的方式來實現異步操做的呢?bash

什麼是同步操做

當函數執行的時候,按照函數內部的順序依次執行,好比:若是此調用的函數是很耗時的,但它依然會等待調用函數的返回值,直到拿到預期的結果(即拿到了預期的返回值或者看到了預期的效果)爲止纔會執行後面的操做,那麼這個函數就是同步的。markdown

//在函數返回時,得到了預期值,即2的平方根
Math.sqrt(2);
//在函數返回時,得到了預期的效果,即在控制檯上打印了'hello'
console.log('hello');

複製代碼

什麼是異步操做

若是函數是異步的,發出調用以後,立刻返回,可是不會立刻返回預期結果。調用者沒必要主動等待,當被調用者獲得結果以後會經過回調函數主動通知調用者。網絡

//讀取文件
fs.readFile('hello.txt', 'utf8', function(err, data) {
    console.log(data);
});
//網絡請求
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回調函數
xhr.open('GET', url);
xhr.send(); // 發起函數
複製代碼

上述示例中讀取文件函數 readFile和網絡請求的發起函數 send都將執行耗時操做,雖然函數會當即返回,可是不能馬上獲取預期的結果,由於耗時操做交給其餘線程執行,暫時獲取不到預期結果(後面介紹)。而在JavaScript中經過回調函數 function(err, data) { console.log(data); }和 onreadystatechange ,在耗時操做執行完成後把相應的結果信息傳遞給回調函數,通知執行JavaScript代碼的線程執行回調。

瀏覽器

前面說到js是一門單線程語言,他是怎麼實現異步操做的呢。JS的運行一般是在瀏覽器中進行的,具體由JS引擎去解析和運行。下面咱們來具體瞭解一下瀏覽器。 瀏覽器的內核是多線程的。 一個瀏覽器一般由如下幾個常駐的線程:

  • 渲染引擎線程:顧名思義,該線程負責頁面的渲染
  • JS引擎線程:負責JS的解析和執行
  • 定時觸發器線程:處理定時事件,好比setTimeout, setInterval
  • 事件觸發線程:處理DOM事件
  • 異步http請求線程:處理http請求

須要注意的是,渲染線程和JS引擎線程是不能同時進行的。 渲染線程在執行任務的時候,JS引擎線程會被掛起。由於JS能夠操做DOM,若在渲染中JS處理了DOM,瀏覽器可能就不知所措了。

JS引擎能夠說是JS虛擬機,負責JS代碼的解析和執行。之因此說JavaScript是單線程,就是由於瀏覽器在運行時只開啓了一個JS引擎線程來解析和執行JS。那爲何只有一個引擎呢?若是同時有兩個線程去操做DOM,瀏覽器是否是又要不知所措了。 因此,雖然JavaScript是單線程的,但是瀏覽器內部不是單線程的。一些I/O操做、定時器的計時和事件監聽(click, keydown...)等都是由瀏覽器提供的其餘線程來完成的。也就是以前說的Event Queue。

參考文章

JavaScript異步機制詳解
這一次,完全弄懂 JavaScript 執行機制
簡單總結下JS中EventLoop事件循環機制

相關文章
相關標籤/搜索