事件环EventLoop

111 阅读3分钟

禁止转载,侵权必究!

一.浏览器的事件环

1.浏览器采⽤的就是多进程模型

  • 每个⽹⻚运⾏在⾃⼰的进程中,如果⼀个⽹⻚发⽣故障,只会影响该⽹⻚,⽽不会对整个浏览器造成影响。
  • 每个⽹⻚都在它⾃⼰的隔离环境中运⾏,因此不同⽹⻚之间的代码不能相互访问。

2. 浏览器进程的组成

  • 浏览器主进程(管理浏览器的整体界⾯)
  • 渲染进程 (每个⻚⾯⼀个,负责呈现⻚⾯内容及响应⽤户交互)
  • ⽹络进程 (加载资源的进程)
  • 插件进程(独⽴的进程)
  • GPU 绘图进程 (通过 GPU 来处理图形渲染和图形处理的过程)

3.渲染进程

  • js 引擎线程 (执⾏ js 代码)
  • 渲染线程 (渲染⻚⾯、布局、画⻚⾯)
  • ⽹络线程(处理⻚⾯的⽹络请求)
  • GPU 线程 (使⽤ GPU 进⾏图形渲染)
  • 合成线程(将多个图层合并为单个图像)
  • 事件触发线程 (调度任务)

在 js 执⾏的过程中还会创建⼀些其他的线程 (定时器、http 请求、事件)

4.异步任务

将异步任务进行分类

执行时机:(他们的执行时机不同)

  • 宏任务: setTimeout ajax 请求 event 事件 setImmediate 脚本<script> ui 渲染 i/o MessageChannel(消息通道)
  • 微任务: Promise.then(原生的 promise) mutationObserver(h5 提供的 api, 如根据页面高度把内容填满) process.nextTick
  • requestAnimationFrame(动画) requestIDleCallback (react 中 fiber 的实现就是) 可以认为是宏任务?

5.宏任务/微任务执行顺序

  • 浏览器内部有很多的宏任务队列 (默认在执行渲染的过程中,会被单独的一个宏任务队列来管理) 所以在剖析代码运行的时候我们就当只有一个宏任务队列
  • 宏任务是最新执行的(我要先执行一个脚本) -> 可能会调用一些 webApi -> 成功后会将这些回调放入到宏任务队列中 (不是立刻放入) -> 可能调用了一些主环境提供的方法 promise.then / mutationObserver -> 立刻放入微任务队列中
  • 宏任务执行的时候 会对应产生一个微任务队列
  • 当当前的宏任务执行完毕后,会清空微任务队列(清空的过程可能会产生 宏任务、微任务,产生的宏任务逻辑和上面一致,产生的微任务会被放到当前队列中)
  • 微任务清空后,会在宏任务队列中取出来一个继续执行

宏任务和微任务的调度操作靠的就是 eventLoop 来实现的

eventLoop.jpg

二.经典实例

微任务和 GUI 渲染

document.body.style.background = "red";
console.log(1);
Promise.resolve().then(() => {
  console.log(2);
  document.body.style.background = "yellow";
});
console.log(3);
// 1 3 2 黄色

事件任务

button.addEventListener("click", () => {
  console.log("listener1");
  Promise.resolve().then(() => console.log("microtask1"));
});
button.addEventListener("click", () => {
  console.log("listener2");
  Promise.resolve().then(() => console.log("microtask2"));
});
button.click();
// 默认的输出和点击后的输出
// click1() click2() 不会产生对应的宏任务,是在主线程中执行的   // listener1 listener2 task1 task2
// 点击的话: 默认会产生两个宏任务,一次执行 // listener1 task1 listener2 task2

定时器任务

Promise.resolve().then(() => {
  console.log("Promise1");
  setTimeout(() => {
    console.log("setTimeout2");
  }, 0);
});
setTimeout(() => {
  console.log("setTimeout1");
  Promise.resolve().then(() => {
    console.log("Promise2");
  });
}, 0);
// Promise1 setTimeout1 Promise2 setTimeout2

任务执行

console.log(1);
async function async() {
  console.log(2);
  await console.log(3); // yield console.log(3)
  // Promise.resolve(console.log(3)).then(()=>  console.log(4))
  console.log(4);
}
setTimeout(() => {
  console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
  console.log(6);
  resolve(7);
});
promise.then((res) => {
  console.log(res);
});
async();
console.log(8); // 1 6 2 3 8 7 4 5

原来的promise 如果返回的是promise 则会给这个promise再产生一个微任务

Promise.resolve()
  .then(() => {
    console.log(0);
    return new Promise((resolve) => {
      // x = Promise.resolve('a') => x.then('a') // queueMicrotask(() =>Promise.resolve('a'))
      resolve("a");
    });
  })
  .then((res) => {
    console.log(res);
  });
Promise.resolve()
  .then(() => {
    console.log(1);
  })
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  })
  .then(() => {
    console.log(4);
  })
  .then(() => {
    console.log(5);
  });
// 0 1 2 3 a 4 5