概念

JavaScript语言是单线程的,这意味着同一时间只能执行一个任务

浏览器是多线程的,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript引擎线程
  • 定时触发器线程
  • 事件触发线程
  • 异步http请求线程

GUI 渲染线程 与 JavaScript引擎线程互斥,JS引擎执行任务时永远不会进行渲染(render),仅在任务完成后才会绘制对 DOM 的更改。

如果一项任务执行花费的时间过长,浏览器将无法执行其他任务,无法处理用户事件,因此,在一定时间后浏览器会在整个页面抛出一个如“页面未响应”之类的警报,建议你终止这个任务。这种情况常发生在有大量复杂的计算或导致死循环的程序错误

异步任务

为了不让某些耗时较长的任务造成阻塞,使浏览器进入假死状态,产生了异步任务

所有任务可以分为同步任务与异步任务两种,当执行同步任务遇到一个异步任务时,就在event table(事件表)中注册回调函数,同步任务继续执行。期间异步任务完成时,回调函数会被放入event queue(事件队列)

异步任务不是JavaScript引擎线程处理,比如计数器由浏览器的定时触发器线程计数;XMLHttpRequest在连接后通过浏览器新开一个线程请求,检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理

宏任务与微任务

宿主环境(浏览器、node)提供的方法是宏任务(macrotask),也叫tasks

  • script
  • setTimeout
  • setInterval
  • requestAnimationFrame
  • I/O
  • UI rendering

语言标准(js引擎)提供的是微任务(microtask),也叫jobs

  • 原生Promise
  • Object.observe(已废弃)
  • MutationObserver

浏览器的事件循环过程

  1. 从宏任务队列中取出script代码(最早的任务),将同步任务放入执行栈中,执行栈中的代码产生的异步任务会被加入到任务队列
  2. 当执行栈中的代码执行完,即栈为空时,将先从微任务队列(microtask queue)中取出一个任务,放入执行栈中执行
  3. 当微任务队列中的任务都执行完成后,从宏任务队列(microtask queue)中取出一个任务,放入执行栈中执行,执行完后重复2
  4. 如果宏任务队列为空,则休眠直到出现宏任务

执行栈清空后总是会先执行完微任务队列中的任务,再从宏任务队列取出一个任务

例子

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

setTimeout为宏任务,Promise为微任务被加入到各自的任务队列中,输出script end后,执行栈为空,先去微任务队列中执行Promise,执行完后再去执行宏任务队列中的setTimeout。

链接

https://imweb.io/topic/58e3bfa845e5c13468f567d5

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

https://juejin.im/post/6844903670291628046

https://juejin.im/post/6844904088459575304


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

前端优化方法 上一篇
Vue插槽 下一篇