事件环Event loop

概念

进程

问题:什么是进程?

一个cpu进程,为了任务正常运行分配调度出一个单位,来供其运行一个任务,进程是计算机调度的基本单位,进程包含多个线程,线程在进程中运行

在浏览器中的进程:

它是由多个进程组成的应用程序,分为主进程和辅助进程,每个tab页都会开启一个进程

  • 主进程:用户界面
  • tab页:

    • 渲染进程(浏览器内核Renderer渲染引擎)
    • 网络进程(网络请求)
    • GPU进程(动画与3D绘制)
    • 插件工具进程(devtool)

渲染进程:

它包括GUI渲染线程(渲染页面)和JS引擎线程(处理JS脚本程序),两者线程运行互斥

  • JS引擎线程工作的时候,GUI渲染线程(有队列)会空闲停止工作
  • JS引擎线程工作完成时,GUI渲染线程(有队列)会继续工作,渲染更新

线程

在渲染进程中的GUI渲染线程:

  • 解析HTML,CSS
  • 构建DOM/render
  • 初始布局与绘制
  • 重绘与回流

在渲染进程中的JS内核引擎线程:

  • 一个主线程与多个辅助线程配合
  • 一个浏览器只有一个JS引擎
  • 解析JS脚本,运行JS代码

在渲染进程中的事件触发线程:

事件触发线程即事件环Event Loop线程

  • 用户交互事件
  • setTimeout
  • Ajax

在渲染进程中的宏任务和微任务:

创建线程的目的是为了实现异步的执行条件

问题:Mutation Observer是什么?

Mutation Observer(变动观察器)是监视DOM变动的接口。当DOM对象树发生任何变动时,Mutation Observer会得到通知。

宏任务:

  • 宿主提供的异步方法和任务
  • script整体脚本代码
  • setTimeout UI渲染 | #宏任务 | 浏览器 | Node | | —- | :—-: | —-: | | I/O | ✅ | ✅ | | setTimeout | ✅ | ✅ | | setInterval | ✅ | ✅ | | setImmediate | ❌ | ✅ | | requestAnimationFrame | ✅ | ❌ |

微任务:

  • 语言标准(ECMA262)提供的API运行
  • Promise
  • Mutation Observer:接口提供了监视对DOM树所做更改的能力 | #微任务 | 浏览器 | Node | | —- | :—-: | —-: | | process.nextTick | ❌ | ✅ | | MutationObserver | ✅ | ❌ | | Promise.then catch finally | ✅ | ✅ |

问题:为什么要区分宏任务和微任务?

主要是实现优先级的问题,微任务实际上是优先级要高于宏任务,每一次微任务在执行队列里的任务执行完毕后要清空微任务当前任务队列,也就是先对微任务进行处理,然后去渲染,去执行宏任务

问题:为什么Promise.then要优先于下一个宏任务去处理?

因为它跟后续的代码也是保持异步关系,处理Promise,微任务去处理Promisethen回调

流程

事件环的运行流程分析:

  1. JS引擎线程(执行栈):

    • 同步代码
    • 宏任务异步代码
  2. 微任务队列(清空所有):

    1. Promise.then

    2. Mutation Observer:接口提供了监视对DOM树所做更改的能力

  3. GUI渲染

  4. 宏任务队列(取出一个放入执行栈,先进先出):

    • Ajax(响应回来进执行栈)
    • setTimeout(时间到了进执行栈)
    • 用户交互事件(事件被触发进执行栈)
  5. JS引擎线程(接收异步的回调)…

代码分析:

  1. //宏任务: script脚本
  2. document.body.style.backgroundColor = 'orange';
  3. console.log(1);
  4. //宏任务: setTimeout
  5. //微任务: setTimeout里的回调
  6. setTimeout(() => {
  7. document.body.style.backgroundColor = 'green';
  8. console.log(2);
  9. }, 100);
  10. //微任务: then里的回调
  11. Promise.resolve(3).then(num => {
  12. document.body.style.backgroundColor = 'pink';
  13. console.log(num);
  14. });
  15. //宏任务: script脚本
  16. console.log(4);

注意:

关于Promise,它是根据后面是否跟有then来决定是否进入微任务队列的

  • 如果没有跟then,它是同步关系,不会进入微任务队列,不会挂起
  • 如果有跟then,它是异步关系,会进入微任务队列,会挂起
  1. //代码流程:
  2. JS引擎线程执行栈:
  3. 1. script脚本 document.body.style.backgroundColor = 'orange'; 没渲染
  4. 2. script脚本 console.log(1); //1
  5. 3. script脚本 console.log(4); //4
  6. 微任务队列:
  7. 4. 执行Promise.resolve(3).then()里面的回调;
  8. 5. document.body.style.backgroundColor = 'pink';
  9. 6. console.log(num); //3
  10. 7. 清空微任务队列
  11. GUI渲染; //backgroundColor = 'pink'; 渲染成功
  12. 宏任务队列:
  13. 8. setTimeout(); 根据延迟时间到点决定是否放入队列
  14. 9. setTimeout(cb); 将回调放入JS执行栈
  15. JS引擎线程执行栈:
  16. 10. 去执行回调 document.body.style.backgroundColor = 'green'; 没渲染
  17. 11. 去执行回调 console.log(2); //2
  18. 微任务队列:已清空
  19. GUI渲染; //backgroundColor = 'green'; 渲染成功
  20. 宏任务队列:空
  21. JS引擎线程执行栈:空

问题:为什么宏任务和微任务都要滞后(异步)?

都是为了模拟一个多线程,跟同步代码是滞后关系

练习

题目1:

  1. Promise.resolve().then(() => {
  2. console.log('p1');
  3. setTimeout(() => {
  4. console.log('s2');
  5. }, 0);
  6. });
  7. setTimeout(() => {
  8. console.log('s1');
  9. Promise.resolve().then(() => {
  10. console.log('p2');
  11. });
  12. }, 0);
  1. //代码流程:
  2. JS引擎线程执行栈:
  3. 1.
  4. 微任务队列:
  5. 2. 执行Promise.resolve().then()里面的回调;
  6. 3. console.log('p1'); //p1
  7. 4. 清空微任务队列
  8. GUI渲染;
  9. 宏任务队列:
  10. 5. setTimeout();
  11. 6. setTimeout();
  12. JS引擎线程执行栈:
  13. 7. 去执行setTimeout回调 console.log('s1'); //s1
  14. 11. 去执行setTimeout回调 console.log('s2'); //s2
  15. 微任务队列:
  16. 8. 执行Promise.resolve().then()里面的回调;
  17. 9. console.log('p2'); //p2
  18. 10. 清空微任务队列
  19. GUI渲染;
  20. 宏任务队列:空
  21. JS引擎线程执行栈:空
  22. //打印结果:
  23. p1
  24. s1
  25. p2
  26. s2

题目2:

  1. console.log(1);
  2. setTimeout(() => {
  3. console.log(2);
  4. }, 10);
  5. new Promise(function(resolve, reject){
  6. console.log(3);
  7. resolve('');
  8. console.log(4);
  9. }).then(res => {
  10. console.log(5);
  11. });
  12. console.log(6);
  1. //分析:
  2. //考点:
  3. //1.new Promise(resolve函数) 里面的resolve是同步回调程序
  4. //2.new Promise(resolve函数)
  5. //如果resolve执行会有then 没有执行没有then
  6. //如果reject执行会有catch 没有执行没有catch
  7. JS执行栈:
  8. 1.console.log(1); //1 -> x
  9. 2.new Promise()
  10. 3.new Promise 执行resolve回调 //3 //4 -> x
  11. 4.console.log(6); //6 -> x
  12. 9.setTimeout1 cb 等待10 进入JS执行栈 然后执行 //2 -> x
  13. 微任务:
  14. 7.new Promise().then
  15. 微任务队列:
  16. 8.new Promise().then 执行回调 //5 -> x
  17. 宏任务:
  18. 5.setTimeout1
  19. 宏任务队列:
  20. 6.setTimeout1 cb
  21. 打印结果:
  22. 1
  23. 3
  24. 4
  25. 6
  26. 5
  27. 2

题目3:

  1. console.log(1);
  2. setTimeout(() => {
  3. Promise.resolve().then(() => {
  4. console.log(2);
  5. });
  6. }, 10);
  7. new Promise(function(resolve, reject){
  8. console.log(3);
  9. resolve('');
  10. console.log(4);
  11. }).then(res => {
  12. setTimeout(() => {
  13. console.log(5);
  14. }, 0);
  15. });
  16. console.log(6);
  1. //分析:
  2. JS执行栈:
  3. 1.console.log(1); //1 -> x
  4. 4.new Promise()
  5. 5.new Promise 执行resolve回调 //3 //4 -> x
  6. 6.console.log(6); //6 -> x
  7. 12.setTimeout2 cb 等待时间短 先进执行栈 执行 //5 -> x
  8. 13.setTimeout2 cb 等待时间长 后进执行栈 执行
  9. 微任务:
  10. 7.new Promise().then
  11. 14.Promise.resolve().then
  12. 微任务队列:
  13. 8.new Promise().then 执行回调
  14. 11.清空微任务队列
  15. 15.Promise.resolve().then 执行回调 //2 -> x
  16. 16.清空微任务队列
  17. 宏任务:
  18. 2.setTimeout1
  19. 9.setTimeout2
  20. 宏任务队列:
  21. 3.setTimeout1 cb
  22. 10.setTimeout2 cb
  23. 打印结果:
  24. 1
  25. 3
  26. 4
  27. 6
  28. 5
  29. 2

题目4:

  1. let res = function(){
  2. console.log(1);
  3. return new Promise((resolve, reject) =>{
  4. console.log(2);
  5. resolve(4);
  6. });
  7. }
  8. new Promise(async(resolve, reject) => {
  9. console.log(3);
  10. let test = await res();
  11. console.log(test);
  12. });
  13. console.log(5);
  14. new Promise((resolve, reject) => {
  15. console.log(6);
  16. });
  17. console.log(7);
  1. //分析:
  2. //考点:async/await语法糖
  3. //async函数默认返回一个promise实例
  4. //await 必须存在在async函数中
  5. //await test() 跟 yield是一回事
  6. //test()返回一个promise实例对象,那么await test()就会调用test().then()
  7. JS执行栈:
  8. 1.fn赋值给res
  9. 2.new Promise1(); 执行回调 //3
  10. 3.遇到await 进微任务队列
  11. 9.console.log(5); //5
  12. 10.new Promise2(); 执行回调 //6
  13. 11.console.log(7); //7
  14. 微任务:
  15. 4.res().then()
  16. 7.test().then()
  17. 微任务队列:
  18. 5.res().then() 执行res回调 //1 //2
  19. 6.let test = await res(); -> test().then() -> 进入微任务
  20. 8.清空微任务队列
  21. 12.执行test().then() //console.log(test); //4
  22. 13.清空微任务队列
  23. 打印结果:
  24. 3
  25. 1
  26. 2
  27. 5
  28. 6
  29. 7
  30. 4

题目5:

  1. let res = function(){
  2. console.log(1);
  3. return new Promise((resolve, reject) =>{
  4. setTimeout(() => {
  5. new Promise((resolve) => {
  6. console.log(2);
  7. setTimeout(() => {
  8. console.log(3);
  9. });
  10. });
  11. }, 0);
  12. resolve(5);
  13. });
  14. }
  15. new Promise(async(resolve, reject) => {
  16. setTimeout(() => {
  17. Promise.resolve().then(() => console.log(4));
  18. }, 0);
  19. let test = await res();
  20. console.log(test);
  21. });
  22. setTimeout(() => {
  23. console.log(6);
  24. });
  25. new Promise((resolve, reject) => {
  26. setTimeout(() => {
  27. console.log(7);
  28. }, 0);
  29. });
  30. console.log(8);
  1. //分析:
  2. JS执行栈:
  3. 1.fn赋值给res
  4. 2.new Promise1(); 执行回调 是宏任务且进队列
  5. 5.遇到await 进微任务队列
  6. 13.let test = await res(); -> test().then() -> 进入下一轮微任务
  7. 17.new Promise3(); 执行回调 是宏任务且进队列
  8. 20.console.log(8); //8
  9. 微任务:
  10. 6.res().then() -> x
  11. 14.test().then() -> x
  12. 24.Promise.resolve().then() -> x
  13. 微任务队列:
  14. 7.res().then() 执行res() //1
  15. 8.new Promise2(); 执行回调 是宏任务且进队列
  16. 11.resolve(5); 抛出5
  17. 12.清空微任务队列
  18. 21.test().then()执行回调 //5
  19. 22.清空微任务队列
  20. 24.Promise.resolve().then()执行 //4
  21. 25.清空微任务队列
  22. 宏任务:
  23. 3.setTimeout1
  24. 9.setTimeout2
  25. 15.setTimeout3
  26. 18.setTimeout4
  27. 27.setTimeout5
  28. 宏任务队列:
  29. 4.setTimeout1 cb
  30. 10.setTimeout2 cb
  31. 16.setTimeout3 cb
  32. 19.setTimeout4 cb
  33. 23.setTimeout1 cb 执行回调 遇到Promise.resolve().then()微任务
  34. 26.setTimeout2 cb 执行回调 遇到new Promise() 执行 //2 再遇到宏任务
  35. 28.setTimeout5 cb
  36. 29.setTimeout3 cb 执行 //6
  37. 30.setTimeout4 cb 执行 //7
  38. 31.setTimeout5 cb 执行 //3
  39. 打印结果:
  40. 1
  41. 8
  42. 5
  43. 4
  44. 2
  45. 6
  46. 7
  47. 3