乐趣区

关于前端:JavaScript-EventLoop

JavaScript EventLoop

EventLoop 即事件循环机制,是指浏览器或 Node 的一种解决 JavaScript 单线程运行时不会阻塞的一种机制,也就是咱们常常应用异步的原理。

JavaScript 运行机制

  • 所有同步工作都在主线程上执行,造成一个 执行栈(Execution Context Stack)
  • 主线程之外,还存在 工作队列 (Task Queue)。只有异步工作有了运行后果,就在 工作队列之中搁置一个事件
  • 一旦 执行栈 中的所有 同步工作执行结束 ,零碎就会读取 工作队列 ,看看外面有哪些事件。如果有那些对应的 异步工作 ,于是完结期待状态, 进入执行栈,开始执行。
  • 主线程一直反复下面的第三步
  • 一个事件循环中有一个或者是多个工作队列

总结:调用栈中的同步工作都执行结束,栈内被清空了,就代表主线程闲暇了。
这个时候就会去工作队列中依照程序读取一个工作放入到栈中执行。
每次栈内被清空,都会去读取工作队列有没有工作,有就读取执行,始终循环读取~执行的操作。

EventLoop 事件循环

介绍

主线程从“工作队列”中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:

  • JavaScript 中有两种异步工作:宏工作 (MacroTask) 微工作(MicroTask)
  • 主线程会一直从工作队列中按程序取工作执行,每执行完一个工作都会查看『微工作』队列是否为空(执行完一个工作的具体标记是函数执行栈为空),如果不为空则会一次性执行完所有『微工作』。
  • 而后再进入下一个循环去工作队列中取下一个工作执行。

MacroTask 宏工作

  • script 全副代码、setTimeoutsetIntervalsetImmediate(浏览器临时不反对,只有 IE10 反对,具体可见 MDN。)、I/OUI Rendering

MicroTask 微工作

  • Process.nextTick(Node 独有)、PromiseObject.observe(废除)MutationObserver(具体应用形式查看这里)

同步工作和异步工作

  JavaScript 单线程工作被分为同步工作和异步工作,同步工作会在调用栈中依照程序期待主线程顺次执行。异步工作会在异步工作有了后果后,将注册的回调函数放入工作队列中,期待主线程闲暇的时候(调用栈被清空),被读取到栈内期待主线程的执行。

事件循环的过程模型

  • 抉择以后要执行的工作队列,抉择工作队列中最先进入的工作,如果工作队列为空即 null,则执行跳转到微工作的执行步骤。
  • 将事件循环中的工作设置为以后抉择工作
  • 执行工作
  • 将事件循环中以后运行工作设置为 null
  • 将曾经运行实现的工作从工作队列中删除
  • Microtasks 查看步骤,进入微工作检查点。

    • 设置微工作检查点标记为 true。
    • 当事件循环微工作执行队列不为空时:抉择一个 最先进入的微工作队列 的微工作,将事件循环的微工作设置为 以后抉择的微工作
    • 运行微工作
    • 将曾经执行实现的微工作设置为 null
    • 移除微工作队列中的以后运行实现的微工作
    • 清理 IndexDB 事务
    • 设置进入微工作检查点的标记为 false。
  • 更新界面渲染
  • 返回第一步

总结

  • 执行栈在执行完 同步工作 后,查看 执行栈 是否为空,如果为空,就会去查看 微工作队列 是否为空,如果为空的话,就执行 宏工作 ,否则就一次性执行完 所有微工作
  • 每次单个 宏工作 执行结束后,查看 微工作队列 是否为空,如果不为空的话,会依照 先入先出 的规定全副执行完 微工作 后,设置 微工作队列 为 null,而后再执行宏工作,如此循环。

举个栗子

代码

console.log('script start');

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

Promise.resolve()
  .then(function () {console.log('promise1');
  })
  .then(function () {console.log('promise2');
  });

console.log('script end');

执行后果

script start
script end
promise1
promise2
setTimeout

执行步骤

  • 第一次执行:执行同步代码,将宏工作和微工作划分到各自队列中。
  • 第二次执行:执行宏工作后,检测到 微工作队列 中不为空,执行 Promise1,执行实现 Promise1 后,调用 Promise2.then,放入 微工作队列 中,再执行 Promise2.then
  • 第三次执行:当 微工作队列 中为空时,执行 宏工作,执行 setTimeout callback,打印日志。
  • 第四次执行:清空工作队列和调用栈

图例

setTimeout(function () {console.log('timer1');
  Promise.resolve()
  .then(function () {console.log('promise1');
  });
});

setTimeout(function () {console.log('timer2');
  Promise.resolve()
  .then(function () {console.log('promise2');
  });
});

再举个栗子

// 例子
console.log('script start');

async function async1() {await async2();
  console.log('async1 end');
}
async function async2() {console.log('async2 end');
}

async1();

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

new Promise((resolve) => {console.log('Promise');
  resolve();})
  .then(function () {console.log('promise1');
  })
  .then(function () {console.log('promise2');
  });

console.log('script end');

// 后果
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout


// 只需将 async1 的执行了解为
function async1() {return async2().then(() => {console.log('async1 end');
  });
}

{% video ./eventloop_demo.mp4 %}

执行步骤如上所示

  • 开始执行

    • 首先咱们执行同步代码,先打印 script start
    • 再打印 async2 end,将 async2.then 放入微工作队列中。
    • 继续执行,将 setTimeout 放入宏工作队列中。
    • 再打印 Promise,将 Promise.then 放入微工作队列中。
    • 最初打印 script end
  • 执行实现后,查看微工作队列不为空,依照先进先出准则继续执行。

    • 执行 async2.then 打印 async1 end
    • 执行 Promise.then 打印 promise1,并将 promise1.then 放入微工作队列中。
  • 此时查看微工作队列持续不为空

    • 执行 promise1.then 打印 promise2
  • 最初执行宏工作队列中的工作

    • 执行 setTimeout,延迟时间到后,将其回调函数放入工作队列中。
  • 执行回调函数

    • 打印 setTimeout

习题

题目

console.log('script start');

async function async1() {await async2();
  console.log('async1 end');
}
async function async2() {console.log('async2 end');
}

async1();

setTimeout(function () {console.log('setTimeout1');
}, 100);

setTimeout(function () {console.log('setTimeout2');
}, 10);

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

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

new Promise((resolve) => {console.log('Promise');
  setTimeout(function () {console.log('Promise-setTimeout');
  }, 10);
  resolve();})
  .then(function () {setTimeout(function () {console.log('promise1-setTimeout');
    }, 10);
    console.log('promise1');
  })
  .then(function () {setTimeout(function () {console.log('promise2-setTimeout');
    }, 0);
    console.log('promise2');
  });

console.log('script end');

答案

<details>

<summary> 先思考再查看答案哦~</summary>

运行后果

script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout3
setTimeout4
promise2-setTimeout
setTimeout2
Promise-setTimeout
promise1-setTimeout
setTimeout1

运行步骤

  • 开始执行

    • 首先咱们执行同步代码,先打印 script start
    • 再打印 async2 end,将 async2.then 放入微工作队列中。
    • 继续执行,将 setTimeout1setTimeout2setTimeout3setTimeout4 顺次放入宏工作队列中。
    • 再打印 Promise,将 Promise.then 放入微工作队列中,将 Promise-setTimeout 放入宏工作队列中。
    • 最初打印 script end
  • 执行实现后,查看微工作队列不为空,依照先进先出准则继续执行。

    • 执行 async2.then 打印 async1 end
    • 执行 Promise.thenpromise1-setTimeout 放入宏工作队列中,打印 promise1,并将 promise1.then 放入微工作队列中。
  • 此时查看微工作队列持续不为空

    • 执行 promise1.thenpromise2-setTimeout 放入宏工作队列中,打印 promise2
  • 最初执行宏工作队列中的工作

    • 顺次执行【setTimeout1setTimeout2setTimeout3setTimeout4Promise-setTimeoutpromise1-setTimeoutpromise2-setTimeout】。
    • 期待延迟时间到后,将其回调函数放入工作队列中。
  • 顺次执行回调函数

    • 打印 setTimeout3
    • 打印 setTimeout4
    • 打印 promise2-setTimeout
    • 打印 setTimeout2
    • 打印 Promise-setTimeout
    • 打印 promise1-setTimeout
    • 打印 setTimeout1

</details>

参考起源

  • 并发模型与事件循环
  • EventLoop
  • Node EventLoop
  • JavaScript 垃圾回收
  • 阮一峰 EventLoop
退出移动版