js的執行機制

Yunruohan發表於2020-11-26

1.為什麼JS是單執行緒的

js是執行於瀏覽器的指令碼語言,因其經常涉及操作dom,如果是多執行緒的,如果一個執行緒修改dom,另一個執行緒刪除dom,那麼瀏覽器就不知道該先執行哪個操作,所以js執行的時候會按照一個任務一個任務來執行,那麼任務是怎麼排列的呢

2.為什麼JS的任務要分類為同步任務和非同步任務

試想一下,如果js的任務都是同步的,那麼遇到定時器、網路請求等這型別需要延時執行回撥函式的任務會發生什麼?頁面會像癱瘓一下暫停下來等待這些需要需要時間的程式碼執行完畢,基於此,又引入了非同步任務,每個非同步任務都必須引入

  • 同步任務:同步任務不需要進行等待可立即看到執行結果,比如console
  • 非同步任務:非同步任務需要等待一定的時候才能看到結果,比如setTimeout、網路請求

  • 例子①

示例一我們可以看到既有同步任務也有非同步任務,那麼在主執行緒執行的過程中就會把兩個定時器中的回撥函式註冊到非同步事件佇列裡面,最後輸出1 2 3 4,但是為什麼是第二個setTimeout先輸出,很明顯從程式碼中可以看到是因為等待的時間不同,但是如果是網路請求呢,那時間不像是定時器一樣提前規定好的,為了解決這個問題設定了訊息佇列,也可以理解為事件監聽器

console.log(1) // 同步任務
setTimeout(() => { // 非同步任務
  console.log(4)
},  2000)
setTimeout(() => { // 非同步任務
  console.log(3)
},  1000)
console.log(2) // 同步任務

3.為什麼非同步任務中要設定訊息佇列(事件監聽)

事件監聽器可以監聽非同步任務的狀態,如果已經可以執行回撥就會將對應的任務放到事件佇列中


  • 例子①

示例一假設了兩個網路請求,監聽器會先監聽到第二個請求得到響應,那麼會先執行第二個的回撥,所以下面這段程式碼的輸出是1 2 3 4

console.log(1) // 同步任務
ajax().then(() => { // 非同步任務且假設3s後得到響應
  console.log(4)
})
ajax().then(() => { // 非同步任務且假設1s後得到響應
  console.log(3)
})
console.log(2) // 同步任務

4.為什麼非同步任務的事件中要區分巨集任務和微任務

假設如果非同步任務的回撥是在操作dom,但是頁面渲染也是一個任務,如果把dom渲染的任務排到非同步任務的隊尾,那麼頁面同樣會出現癱瘓,所以js就規定一些可以插隊完成的任務,這型別的任務稱為微任務,比如說dom渲染,後面又引入promise.then,

每個巨集任務後面都會存在微任務,如下圖所示,遇到一段程式碼,首先執行所有同步程式碼,然後再讀取巨集任務佇列,拿上面的例子舉例,console.log(1)和console.log(2)作為同步任務最先執行,然後執行巨集任務1,第一個ajax,事件監聽器會判斷出哪一個事件先返回結果最終將回撥函式放到主執行緒執行棧

  • 巨集任務:HTML解析、滑鼠事件、鍵盤事件、網路請求、執行主執行緒js程式碼和定時器(new Promise(xxx)中xxx是同步程式碼)
  • 微任務:promise.then,dom渲染,async,process.nextTick

5.下面通過幾個例子理解巨集任務和微任務


例子①:下面的執行結果是1 2 3,原因是1 2是同步任務,3是非同步任務

console.log(1)
setTimeout(() => {
  console.log(3)
})
console.log(2)

例子②:下面的執行結果是1 2 3 4,原因是1 2是同步任務,3 4是非同步任務,且第二個定時器先執行回撥

console.log(1)
setTimeout(() => {
  console.log(4)
}, 2000)
setTimeout(() => {
  console.log(3)
}, 1000)
console.log(2)

例子③:下面的執行結果是1 2 3 4 5,原因是1 2 3是同步任務,4 5是非同步任務,且4是微任務,console.log(2)執行結束後巨集任務佇列中的第一個任務的微任務就是console.log(4),第二個巨集任務是定時器,所以4先一步注入到執行棧

console.log(1)
new Promise((resolve, reject) => {
  console.log(2)
  resolve()
}).then(() => {
  console.log(4)
})
setTimeout(() => {
  console.log(5)
})
console.log(3)

例子④:

console.log(1)
new Promise((resolve, reject) => {
  console.log("請求即將進行")
  setTimeout(() => {
    console.log("已請求到資料")
    resolve()
  },  1000)
}).then(() => {
  console.log(3)
})
setTimeout(() => {
  console.log(4)
},  2000)
console.log(2)

// 執行結果
1
請求即將進行
2
已請求到資料
3
4

例子⑤:

console.log('script start')
async function async1() {
  await async2()
  console.log('async1 end')
}
function async2() {
  console.log('async2 end')
}
setTimeout(function() {
  console.log('setTimeout')
})
async1()
new Promise(resolve => {
  console.log('promise1')
  resolve()
}).then(function() {
  console.log('promise2')
}).then(function() {
  console.log('promise3')
})
new Promise(resolve => {
  console.log('promise4')
  setTimeout(()=>{
    resolve()
  })
}).then(function() {
  console.log('promise5')
}).then(function() {
  console.log('promise6')
})
console.log('script end')
// 執行結果
// script start async2 end   promise1 promise4 script end  async1 end promise2  promise3  setTimeout promise5 promise6 

例子⑥:

{var obj = {
  A: function A() {
    console.log(this.A)
  },
  B: () => {
    console.log(this.B)
  },
  C:()=> {
    console.log(this.C)
  },
  D: () => {
    console.log(this.D)
  },
  E: 'E',
  F: 'F'
}
var A = 'A', B = 'B';
const C = 'C', D = 'D';
function test() {
  obj.A();
  setTimeout(obj.B)
  Promise.resolve()
    .then(() => {
      obj.C.bind(obj)()
      return Promise.reject()
    })
    .then(obj.D)
    .catch(() => {
      console.log(obj.E)
    })
    .then(() => {
      console.log(obj.F)
    })
}}
test(); 
// function A undefined E F B

 

相關文章