js執行機制(promise,setTimeout執行順序)

1.關于javaacript

javascript是一門單線程語言,所以javascript是按語句的執行順序執行的。
雖然js是單線程,但是我們可以將任務分成兩類
1.同步任務:需要執行的任務在主線程上排隊,一次執行
2.異步任務:沒有立馬執行但是需要被執行的任務,放在 任務隊列里面,

2.javascript事件循環

當我們打開網站的時候,網頁的渲染其實是一堆同步任務,不如頁面骨架和頁面元素的渲染,但是想圖片音樂等占用資源大耗時久的任務就是異步任務,
異步執行:

  • 1.所有同步任務都是在主線程上執行,形成一個很執行棧
  • 2.主線程之外,還存在一個任務隊列(task queue)只要異步任務有了運行結果,就在“任務隊列”之中放置一個事件。
  • 3.一旦“執行棧”中的所有同步任務執行完畢,系統就會讀取“任務隊列”,看看里面有哪些事件。那些對應的異步任務,就結束等待狀態,進入執行棧開始被執行。
  • 4.主線程不斷重復以上三步。
    js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調用的函數。
    ![N]M]_N{OL(HBZ`B89REKMQ9.png](https://upload-images.jianshu.io/upload_images/9374643-598ff6c029970ff9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 同步和異步分別進入到不同的執行場所,同步進入到主線程,異步的進入到event table 并注冊函數
  • 當指定事件完成之后,event table 會將函數移入到event queue
  • 當主線程的任務執行完畢之后,會把event queue里面讀取對應的函數進入主線程執行
  • 上述過程會不斷循環,形成event loop(事件循環)

3.setTimeout

在使用setTimeout的時候,經常會發現設定的時間與自己設定的時間有差異,貼段代碼看一下

setTimeout(() => {
    task();
},3000)
console.log('執行console');

// 執行console 
// task()

上文所說的setTimeout是一個異步的所以會先執行console這個同步任務
但是,如果改成下面這段會發現執行時間遠遠超過預定的時間

setTimeout(() => {
    task()
},3000)

sleep(10000000)

這是為啥??
我們來看一下是怎么執行的:

  • task()進入到event table里面注冊計時
  • 然后主線程執行sleep函數,但是非常慢。計時仍然在繼續
  • 3秒到了。task()進入event queue 但是主線程依舊沒有走完
  • 終于過了10000000ms之后主線程走完了,task()進入到主線程
    所以可以看出其真實的時間是遠遠大于3秒的

還會遇到一種情況,就是setTimeout(fn(),0),這樣的代碼其含義主要是在這個任務會在主線程最早可得的空閑時間執行,換句話說就是主線程的任務執行結束之后立馬執行

console.log('先執行這里');
setTimeout(() => {
    console.log('執行啦')
},0);

// 先執行這里
// 執行啦

HTML5標準規定了setTimeout()的第二個參數的最小值(最短間隔),不得低于4毫秒,如果低于這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設為10毫秒。另外,對于那些DOM的變動(尤其是涉及頁面重新渲染的部分),通常不會立即執行,而是每16毫秒執行一次。

4.promise 和 process.nextTick(callback)

process.nextTick(callback)類似node.js版的"setTimeout",在事件循環的下一次循環中調用 callback 回調函數。

除了廣義的同步任務和異步任務,我們可以分的更加精細一點:

  • macro-task(宏任務):包括整體代碼script,setTimeout,setInterval
  • micro-task(微任務):Promise,process.nextTick
    不同的任務會進入到不同的event queue。比如setTimeout和setInterval會進入相同的Event Queue。


    事件循環,宏任務,微任務的關系圖

    不嗶嗶,搞段代碼瞅瞅:

setTimeout(function() {
    console.log('setTimeout');
})

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

console.log('console');

// promise
// console
// then
// setTimeout
  • 首先會遇到setTimeout,將其放到宏任務event queue里面
  • 然后回到 promise , new promise 會立即執行, then會分發到微任務
  • 遇到 console 立即執行
  • 整體宏任務執行完成,接下來判斷是否有微任務
    ,剛剛放到微任務里面的then,執行
  • ok,第一輪事件結束,進行第二輪,剛剛我們放在event queue 的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')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})


// 1,7,6,8,2,4,3,5,9,11,10,12

驚不驚喜,意不意外
我們來分析一下

  • 首先先執行console.log(1) 然后將setTimeout放到宏任務event queue里面 記作 setTimeout 1 ,接著 看到 process.nextTick ,將其放到微任務里面 ,記作 process 1,然后 看到new promise 立即執行輸出9 ,將里面的then 放到 微任務里面 記作 then 2, 繼續,遇到 setTimeout 放到宏任務里面記作 setTimeout 2 。目前輸出的是:1,7,
  • OK, 接下來,開始判斷是否有微任務,剛剛放入到微任務event queue的進入到主程序開始執行,process 1 , then 2 目前輸出的是:6,8
  • 接下來,微任務的event queue 空了,進行下一輪事件,將剛剛放到宏任務的 setTimeout 1 進入到主線程
    遇到 console 立即執行, 遇到 process.nextTick 放到微任務 event queue 里面 記作 process1, 接著遇到 new Promise 立即執行, 將 then 放到event queue 里面 記作 then 2,OK,當前宏任務里的任務執行完了,判斷是否有微任務,發現有 process1, then 2 兩個微任務 , 一次執行 目前輸出的是:2,4,3,5
  • 目前主線程里的任務都執行結束了,又開始第三輪事件循環,同上(字太多,省略。。。。) 目前輸出的是:9,11,10,12

注意: 以上所說只能是在瀏覽器中的執行順序,

5.node.js的Event Loop

Node.js也是單線程的Event Loop,但是它的運行機制不同于瀏覽器環境。瀏覽器的Event loop是在HTML5中定義的規范,而node中則由libuv庫實現。
node.js運行機制

  • 1.v8引擎解析JavaScript腳本。
  • 2.解析后的代碼,調用Node API。
  • 3.libuv庫負責Node API的執行。它將不同的任務分配給不同的線程,形成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
  • 4.V8引擎再將結果返回給用戶。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容