天天看點

進階前端進階 JavaScript中的事件循環機制

一、簡單講解

這個大家應該或多或少都知道的

for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i); // 輸出10個10
  });
}      

解析:先執行for循環,循環疊加i,然後再執行setTimeout 10遍,是以會輸出10個10

變體:

for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i); // 依次輸出0-9
  });
}      

我們知道var跟let本質差別就是作用域的問題,是以會聯想到let導緻的,但在這一點上,并不是作用域問題,因為從事件循環機制來解釋是有問題的

以上的代碼等價于

for (var i = 0; i < 10; i++) {
  (function (i) {
    setTimeout(() => {
      console.log(i);
    });
  })(i); // 立即執行函數,以上的let應該是将代碼特殊處理了下
}      

立即執行函數:函數聲明後立即執行。

從事件循環的角度來說,應該先執行for循環将i疊加到10,執行完後再去執行for循環體(setTimeout),此時的i變成了10,是以每次輸出都是10,

這裡使用let,應該是特殊處理了一下的。

二、深入講解

我們知道JavaScript語言是單線程的,至于為啥是單線程?

假設有兩個線程,一個在頁面上新增一個div,另一個線程在頁面上删除div,那最終聽誰的?

那JavaScript怎麼實作異步的呢?

在JavaScript中,有兩類任務:同步任務和異步任務。

同步任務:普通的任務,依次從上往下執行。

異步任務:又分為宏任務、微任務。

宏任務:setTimeout跟setInterval

微任務:Promise().then() 這裡要注意一下,Promise方法裡面的是同步任務,then裡面的才是微任務

執行順序:先執行同步任務,遇到異步任務,将其放入到宏任務或者微任務隊列中,然後優先執行微任務,接下來再去執行宏任務。

簡單的例子:

setTimeout(function() {
    console.log(1)
}, 0);

new Promise(function(resolve, reject) {
    console.log(2); // 這裡是同步任務
    resolve();
}).then((res) => {
    console.log(3);
})
console.log(4);
// 輸出結果是 2  4  3  1      

解析:從上往下執行,setTimeout是宏任務,放到宏任務隊列中,

Promise裡面的是同步任務,是以先執行,輸出2,then裡面的是微任務,放到微任務隊列中,

最後一個是同步任務,執行,輸出4,

然後執行微任務,輸出3

最後執行宏任務,輸出1

來點難度的:

console.log(1);

setTimeout(function () {
  console.log(2);
}, 0);

setTimeout(function () {
  console.log(3);

  setTimeout(function () {
    console.log(4);
  }, 0);
}, 0);

new Promise(function (resolve, reject) {
  console.log(5);
  resolve();
}).then((res) => {
  console.log(6);
  new Promise(function (resolve, reject) {
    console.log(7);
    resolve();
  }).then((res) => {
    console.log(8);
  });
});

new Promise(function (resolve, reject) {
  console.log(9);
  resolve();
})
  .then((res) => {
    console.log(10);
  })
  .then((res) => {
    console.log(11);
  });

console.log(12);
// 輸出 1  5   9   12
//      6  7  10   8   11
//      2  3  4      

解析:原理跟上面一樣,不過需要注意的是,8跟11的順序:,在這裡then的層級,将各個Promise裡面第一層的then放在一起,第二層的then放在一起,依此類推,

然後依次執行第一層的,執行完第一層,接下來執行完第二層,依此類推。

第一層(6,7,10)

第二層(8,11)

是以先輸出8, 再輸出11的。

再來一個(也最可能出錯或者無法了解的):

console.log(1);
async function fn() {
  console.log(2);
  await console.log(3);
  console.log(4); // 這一步,你應該會有問題
}
setTimeout(() => {
  console.log(5);
}, 0);
fn();
new Promise((resolve) => {
  console.log(6);
  resolve();
}).then(() => {
  console.log(7);
});
console.log(8);
// 輸出:1  2  3   6   8   4  7  5      

解析:async await是Promise的文法糖,隻是針對寫法上的優化

将async await翻譯成Promise:

async function fn() {
  console.log(2);
  await console.log(3);
  console.log(4); // 這一步,你應該會有問題
}
// 等價于
function fn() {
  return new Promise((resolve, reject) => {
    console.log(2);
    resolve(
      (() => {
        console.log(3);
      })() // 立即執行函數
    );
  }).then(() => {
    console.log(4);
  });
}      

再來一道加深鞏固的:

console.log(1);
async function fn() {
  console.log(2);
  await console.log(3);
  await console.log(4);
  console.log(5);
}
setTimeout(() => {
  console.log(6);
}, 2000);
setTimeout(() => {
  console.log(7);
}, 1000);
fn();
new Promise((resolve) => {
  console.log(8);
  new Promise((resolve) => {
    console.log(9);
    resolve();
  }).then(() => {
    console.log(10);
  });
  resolve();
}).then(() => {
  console.log(11);
  new Promise((resolve) => {
    console.log(12);
    resolve();
  }).then(() => {
    console.log(13);
  });
});
console.log(14);
// 輸出 1  2  3  8  9  14
//      4  10 11 12 5  13
//      7  6      

繼續閱讀