浏览器中的事件循环

打印 上一主题 下一主题

主题 917|帖子 917|积分 2751

1. 浏览器进程和线程

浏览器是多进程的,每当你打开一个 Tab ,都会有一个进程被体系创建出来。 这就是为什么你
会在任务管理器中瞥见多个 chrome.exe 的缘故起因。
每一个 tab 都是一个浏览器『渲染进程』。 当然,除了一个( 或多个 )渲染进程,浏览器还有其它
的进程,比方 GPU 进程等。
渲染进程是浏览器最焦点最重要的进程,而渲染进程下又有多个线程,其中与 JS 关系最大的
就是「JS 引擎线程」。
我们常说的「浏览器中的 JS 是单线程执行的」指的就是浏览器 ( 的一个 Tab 中 ) 有且仅有一
个线程执行你所编写的 JS 代码。
  1. 1.  浏览器
  2. 2.  ├── 其它进程
  3. 3.  ├── ...
  4. 4.  ├── ...
  5. 5.  ├── 渲染进程 1 // 一个 tab 一个进程
  6. 6.  │ ├── JS 引擎线程
  7. 7.  │ ├── HTTP 请求线程
  8. 8.  │ ├── 定时触发线程
  9. 9.  │ ├── 事件触发线程
  10. 10.  │ └── GUI 线程
  11. 11.  ├── 渲染进程 2 // 一个 tab 一个进程
  12. 12.  │ ├── JS 引擎线程
  13. 13.  │ ├── HTTP 请求线程
  14. 14.  │ ├── 定时触发线程
  15. 15.  │ ├── 事件触发线程
  16. 16.  │ └── GUI 线程
  17. 17.  ├── ...
  18. 18.  └── 渲染进程 N // 一个 tab 一个进程
  19. 19.  ├── JS 引擎线程
  20. 20.  ├── HTTP 请求线程
  21. 21.  ├── 定时触发线程
  22. 22.  ├── 事件触发线程
  23. 23.  └── GUI 线程
复制代码
JS 引擎线程也被称作『主线程』。
2. 事件循环和执行栈

JS 引擎线程在剖析一段 JS 代码时,会将代码放在某个地方,这个地方就被称作『执行栈』,
然后依次执行内里的函数 ( 代码 ) 。
JS 引擎线程 ( 即,主线程 ) 会执行 JS 代码,但并非全部的 JS 代码都是由 JS 引擎线程 ( 即,主线程 ) 执行!
当在依次执行这些代码过程中,如果遇到了异步代码 ( 比方,发起 ajax 请求、设置定时任务等
) 时,JS 引擎线程就会将这些代码交给其它线程执行 ( 而不是本身亲身、立即执行它们 ) 。当 JS引擎线程把异步任务代码交给别人执行之后,执行栈中的执行流程就继续向下执行 ( 而不是在
异步任务代码处干等、白耗时间 ) 。
当执行栈中的同步代码执行完之后 ( 异步代码都交出去了 ) ,JS 引擎线程会从「一个队列」中
去取出已完成的异步任务的回调,将这个 ( 这些 ) 回调函数放到执行栈中继续执行。如果回调
中又有异步代码,这些异步代码还是会被 JS 引擎线程「交出去」。
比方前面例子中:
「发起 ajax 请求」代码会被交给「HTTP 请求线程」执行;
「设置定时任务」代码会被交给「定时触发线程」执行。
==这些异步任务代码,都不是由 JS 引擎线程执行的!==
被交给「其它线程」执行的异步任务在被「其它线程」执行完成后,它们 ( 异步任务 ) 的回调
函数会被放入『任务队列 ( 即,上面所说的「一个队列」 ) 』,而 JS 引擎线程在执行完同步代码之后,会从「任务队列」中取出这些已完成的异步任务的回调继续执行。
上述工作,JS 引擎线程是『周期性、循环』执行的!这个周期性的循环也被称作事件循环,
每一次循环其实就是一个时间周期,也被称之为一次 tick 。
3. 宏任务和微任务

上面所说的「任务队列」不止一个。根据任务种类的差异可以分为「微任务队列」和「宏任务
队列」。
在事件循环的过程中,执行栈在同步代码执行完成之后,优先会去查抄「微任务栈」中是否有
任务 ( 代码 ) 必要执行,如果没有,再去「宏任务队列」中查看有没有必要执行的任务。如此
往复。口诀:同微宏。
微任务会比宏任务先执行,而且微任务只有一个队列。宏任务队列大概会有多个。
前面举例的 ajax 请求任务和定时器任务都是宏任务,别的,鼠标点击、键盘按下等事件也属于宏任务。
常见的微任务有 promise.then()、promise.catch()、new MutationObserver()、process.nextTick()等。
补充:从某种意义上讲,微任务不能算作严酷意义上的异步任务。由于在底层实现上,V8 引
擎并不会将属于微任务的异步任务交给其它线程执行,仍然是由 JS 引擎线程执行:将它的回
调直接在执行完同步代码之后立即直接执行。逻辑感官上,微任务好似改变了执行次序的同步
代码。
宏任务特性:有明确的异步任务必要执行和回调,必要其它线程支持;
微任务特性:没有明确地任务必要执行,只有回调,不必要其它线程支持。
  1. 1.  console.log('同步代码1');
  2. 2.  setTimeout(() => {
  3. 3.      console.log('setTimeout');
  4. 4.  }, 0); // 这里的 0 ,实际上是默认 4 毫秒,它不是真正的 0
  5. 5.  new Promise((resolve) => {
  6. 6.      console.log('同步代码2');
  7. 7.      resolve();
  8. 8.  }).then(() => {
  9. 9.      console.log('promise.then');
  10. 10.  });
  11. 11.  console.log('同步代码3');
复制代码


  • setTimeout 和 promise.then 都是异步执行的,将在所以的同步代码块之后执行;
  • promise.then 是微任务,而 setTimeout 是宏任务,所以,promise.then 先执行;
  • new promise 是同步执行的
4. 案例

案例一:在主线程上添加宏任务

  1. 1.  // 执行结果:1 3 2
  2. 2.
  3. 3.  console.log(1);
  4. 4.
  5. 5.      setTimeout(function () {
  6. 6.      console.log(2);
  7. 7.  }, 0);
  8. 8.
  9. 9.  console.log(3);
复制代码
上述代码 3 条语句:


  • 一个打印
  • 一个定时器
  • 一个打印
先执行第一个打印,输出 1 ,然后由于 setTimeout 是异步操作,因此它将被「其它线程」执
行,而它的回调  console.log(2) 会被放入宏队列。接着执行第二个打印,输出 3 ,最后主
线程再取出宏队列中的代码,执行  console.log(2) ,输出 2 。

所以终极的执行结果是 1 3 2 。
案例二:在主线程上添加微任务

  1. 1.  // 最终执行结果:1 2 4 3
  2. 2.
  3. 3.  console.log(1);
  4. 4.  new Promise(function(resolve,reject) {
  5. 5.      console.log('2');
  6. 6.      resolve();
  7. 7.  }).then(function() {
  8. 8.      console.log(3);
  9. 9.  })
  10. 10.  console.log(4);
复制代码
上述代码 4 条语句:


  • 一个打印
  • 一个 new promise
  • 一个 promise.then
  • 一个打印
先执行第一个打印,输出 1 ,然后执行 new Promise ,由于 new Promise 是同步代码,它的参数函数代码会立即执行,所以会执行  console.log(2) 输出 2 。而 promise.then 作为Promise 的回调,会被放入微队列。紧接着执行  console.log(4) 输出 4 ,最后主线程再取出微队列中的  console.log(3) 执行,输出 3 。

所以终极的执行结果是 1 2 4 3。
案例三:宏任务中创建微任务

  1. 1.  // 最终结果:1 5 11 6 2 3 4 7 8 10 9
  2. 2.  console.log('1');
  3. 3.
  4. 4.  setTimeout(function () {
  5. 5.      console.log('2');
  6. 6.      new Promise(function (resolve) {
  7. 7.      console.log('3');
  8. 8.      resolve();
  9. 9.  }).then(function () {
  10. 10.      console.log('4')
  11. 11.  })
  12. 12.  }, 0);
  13. 13.
  14. 14.  new Promise(function (resolve) {
  15. 15.      console.log('5');
  16. 16.      resolve();
  17. 17.  }).then(function () {
  18. 18.      console.log('6')
  19. 19.  });
  20. 20.
  21. 21.  setTimeout(function () {
  22. 22.      console.log('7');
  23. 23.      new Promise(function (resolve) {
  24. 24.      console.log('8');
  25. 25.      resolve();
  26. 26.  }).then(function () {
  27. 27.      console.log('9')
  28. 28.  })
  29. 29.  console.log('10')
  30. 30.  }, 0);
  31. 31.
  32. 32.  console.log('11');
复制代码


  • 起首会执行第一个打印,输出 1 ;
  • 然后第 1 个 setTimeout 定时任务会被「其它线程」执行,其回调(  new Promise() {}
)会被放入宏队列,而主线程继续向下;


  • 接着执行第 1 个 new Promise 其代码会立即执行,因此会输出 5 。而它的 .then() 方法
的回调(  console.log(6) )将会被放入微队列;


  • 主线程继续向下,遇到第 2 个 setTimeout 定时任务,它又会被「其它线程」执行,其回
调(  new Promise() {} )会被放入宏队列,而主线程继续向下;


  • 主线程执行  console.log(11) 输出 11 。截止目前为止,输出的是 1 5 11 。然后,主线
程的代码全部执行完,主线程开始从微队列和宏队列中取代码执行。


  • 现在微队列中有且仅有的代码是输出 5 的 new Promise 的回调: console.log(6) 。它
被主线程取出,执行。


  • 微队列当前被清空了,主线程从宏队列中取代码执行。起首取出的是第 1 个定时任务的回
调,先输出 2 ,然后执行 new Promise 输出 3 ,然后 promise.then 的回调
console.log(4) 被放入微队列。


  • 当前主线程代码又执行竣事,然后微队列中有了一条语句,被取出执行,输出 4 。
  • 主线程代码队列和微队列全空,主线从又从宏队列中取代码,执行第 2 个的回调,打印 7
和 8 ,将 promise.then 的回调  console.log(9) 扔进微队列后,继续打印 10 。


  • 最后,主线程从微队列中取出  console.log(9) 执行,输出 9 。
案例四:微任务中创建宏任务

  1. 1.  // 1 5 2 3 4
  2. 2.  new Promise((resolve) => {
  3. 3.      console.log("1")
  4. 4.      resolve()
  5. 5.  }).then(() => {
  6. 6.      console.log("2")
  7. 7.      setTimeout(() => {
  8. 8.          console.log("3")
  9. 9.      }, 0)
  10. 10.  })
  11. 11.
  12. 12.  setTimeout(() => {
  13. 13.      console.log("4")
  14. 14.  },1000);
  15. 15.
  16. 16.  console.log("5")
复制代码


  • 起首  new Promise 执行,执行  console.log(1) 输出 1 ,而后 promise.then 被仍进微
队列。


  • 紧接着下面的定时任务代码执行,其回调,会在 4 秒后被「其它线程」扔进宏队列。主线
程继续向下。


  • 主线程执行  console.log(5) 输出 5 ,至此,主线程代码队列中全部执行完,主线程开始
从为队列中拿代码执行。


  • 主线程取出 promise.then 的回调输出 2 ,然后遇到定时器,由于上面定时器隔断时间更
短,因此上面定时器的回调  console.log(3) 会比下面的定时器的回调  console.log(5)
先辈入宏队列,因此,主线程先取到、执行的是输出 3 ,后执行的是输出 4 。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表