浏览器事件循环机制

没有异步机制会怎样

js是单线程的,如果没有异步机制,所有代码会同步执行。一些比较耗时的操作比如网络请求,会阻塞线程直到请求返回结果才会继续向下执行。如果这样js的执行效率会比较低,甚至导致页面卡死。所以必须加入异步机制以提升js线程执行效率。

怎么加入异步机制

单线程和异步是互斥的,所以js线程本身是没有异步能力的。但是浏览器赋予了js线程异步能力,浏览器不仅支持js主线程,还可以开辟出http请求线程、定时器触发线程、事件触发线程等多种幕后线程。js线程遇到http请求、定时器等任务,会把它们交给相应的幕后线程执行,js线程继续向下执行,http请求、定时器等任务完成后再把回调放入js队列中等待js线程调用。这样js线程就具备了异步的能力了。同时JS引入了Promise使单线程也具备了异步能力。

怎么调度队列中的任务

浏览器把任务分为两类:宏任务和微任务。js引擎线程发起的任务称为微任务,浏览器其它线程发起的任务称为宏任务。整体代码script会放到宏任务里;幕后线程执行的任务完成后会把对应的回调放到宏任务里;js主线程会把某些任务放到微任务里,比如:Promise.resolve().then(callback) js主线程会把callback放到微任务里。

js线程的一般调度策略是:js线程从宏任务中选取一个任务执行,执行完成后,逐个执行微任务中的任务直至微任务为空,然后再次从宏任务中选取一个任务执行,如此反复。js线程从宏任务中选取一个任务开始到执行完所有微任务为止称为一个事件循环。比如宏任务中有两个任务macro1, macro2,微任务中有三个任务micro1, micro2, micro3,那么调度顺序为:macro1 > micro1 > micro2 > micro3 > macro2。

分析一个例子:

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

Promise.resolve().then(function then_1() {
  console.log("then_1");
});

Promise.resolve().then(function then_2() {
  console.log("then_2");
});

js线程执行前,宏任务里只有整体代码script,微任务里没有任务。按照一般调度策略:

  1. js线程首先从宏任务里选取整体代码script执行;
  2. 把setTimeout任务交给计时器线程,计时器线程会立刻往宏任务里加入一个tiemtimeou_1任务 ;
  3. js线程把then_1加入微任务;
  4. js线程把then_2加入微任务 ;
  5. 执行完宏任务里的整体代码script,接着要依此执行微任务里的任务,现在微任务里有then_1和then_2,js线程从微任务里选取then_1执行,控制台打印then_1;
  6. js线程从微任务里选取then_2执行,控制台打印then_2;
  7. 微任务里没有其它任务,所以第一次事件循环结束;
  8. js线程开启第二次事件循环,从宏任务中选取任务执行。此时宏任务里只有timeou_1任务,js线程选取timeou_1执行,控制台打印timeou_1;
  9. 执行完timeou_1,接着要依此执行微任务里的任务,现在微任务里没有任务,所以第二次事件循环结束;
  10. 此时宏任务、微任务都为空,js线程循环等待新任务。

事件回调任务优先级较高

宏任务中,任务一般会按照先进先出的原则进行调度,但是如果宏任务中同时存在事件触发线程触发的任务,则会优先调度该任务。

分析一个例子:

<!DOCTYPE html>
<html lang="en">
  <body>
    <button id="btn">button</button>
    <script>
      document.getElementById("btn").onclick = function handleClick() {
        console.log("click btn");
      };
      setTimeout(function timeoutFn() {
        console.log("timeoutFn");
      }, 0);
      document.getElementById("btn").click();
    </script>
  </body>
</html>
  1. js线程执行整体代码script;
  2. 为btn添加监听事件;
  3. 时间触发线程将timeoutFn加入宏任务;
  4. 触发点击btn事件,将handleClick加入宏任务;
  5. 执行完整体代码script,因为微任务为空,本次事件循环结束;
  6. 第二次事件循环,宏任务中同时存在时间触发线程回调timeoutFn和事件触发线程回调handleClick,虽然timeoutFn先进入宏队列但事件回调任务优先级高,优先调度handleClick,输出click btn。微任务中没有任务本次事件循环结束;
  7. 第三次事件循环,宏任务中只有timeoutFn,js线程选取 timeoutFn 执行,输出 timeoutFn。微任务中没有任务本次事件循环结束;
  8. 此时宏任务、微任务都为空,js线程循环等待新任务。

参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言 想要了解一门语言,最好的办法就是了解它的运行机制。掌握了运行机制,能够让我们在开发中少走许多弯路,写出高质量...
    moofyu阅读 363评论 0 1
  • 新建bat文件:*.bat 脚本文件,新建“文本文档”,修改文本文档文件后缀为 ".bat" 使用bat文件:无参...
    宇宙小神特别萌阅读 1,590评论 0 2
  • 目录:1、Hexo 启用next主题模板2、更改Next主题为中文3、增加标签页和分类页4、使用标签页和分类页5、...
    宇宙小神特别萌阅读 1,888评论 1 3
  • 宏定义对比函数的优点 要写好C语言,漂亮的宏定义是非常重要的。宏定义可以帮助我们防止出错,提高代码的可移植性和可读...
    fangyuu阅读 98评论 0 0
  • 2020-04-10 中原焦点团队 肖巧风 焦点解决初级网络班第21期 坚持第78天原创分享 总约练81次 挑战三...
    凤舞九天阅读 272评论 0 0