浏览器事件循环

前言:本人表述能力不太好,若是有不清晰的地方请必定回复,我会改。javascript

先肯定几个概念:html

1.单线程:做为浏览器脚本语言,用于解释用户操做,为了不复杂性,js设计之初就是单线程的,这个是毋庸置疑的,也不会改变。
2.[同步,异步,阻塞,非阻塞](https://www.zhihu.com/question/19732473): 
  同步和异步关注的是消息通讯机制,区别在因而否等待结果返回。
  阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
3.执行栈,任务队列:当调用js代码的时候,会生成一个执行上下文。当开始调用一系列的方法,这些方法就存储在执行栈中,等待依次执行。执行栈是执行同步任务。任务队列存放执行当前上下文的异步任务
复制代码

什么是事件循环

单线程意味着事件是一个一个执行的,前一个执行的时候,后一个须要等待着。但前一个如果网络请求,可能长时间没有回复,就容易致使CPU浪费。执行完同步以后再执行异步,解决了一部分等待的问题。同步的任务在执行栈上执行,遇到异步就放进任务队列里,执行栈中的执行完后执行异步队列。 所谓循环,就是上述事情在重复执行。它常常按照相似以下的方式来被实现:java

while (queue.waitForMessage()) {
    queue.processNextMessage();
}
复制代码

事件循环是一种运行机制,js引擎须要执行的时候,将须要执行的消息放进执行栈中,开辟执行上下文。事件循环就是一直重复循环检查执行栈中是否有待执行的消息,当消息完整的执行完后,检查是否有未执行的消息,有就继续执行,没有保持监听。web

怎么执行事件

简单事件执行

如下这种简单代码怎么执行呢?chrome

function test () {
    console.log(1);
    console.log(2);
}
复制代码

简单调用执行栈
这个很好理解,代码一行一行执行,入栈,执行完了以后出栈。console.log是当即执行的,因此在执行console.log后当即出栈了。最后只剩下test{}执行后进入下一次循环。

函数调用

那若是有函数调用呢?执行栈如何处理?api

function foo(b) {
  var a = 10;
  return a + b + 11;
}

function bar(x) {
  var y = 3;
  return foo(x * y);
}

console.log(bar(7)); // 返回 42
复制代码

函数调用执行栈

宏任务

函数调用的时候,执行栈中增长了被调用的函数的上下文,执行完被调用的函数后再继续执行当前函数。 项目中有不少异步代码。好比定时器,这些代码会安排在任务队列里,等待当前主函数执行后依次执行。promise

console.log('这是开始');

  setTimeout(function cb() {
    console.log('这是来自第一个回调的消息');
  });

  console.log('这是一条消息');

  setTimeout(function cb1() {
    console.log('这是来自第二个回调的消息');
  });

  console.log('这是结束');
复制代码

异步调用执行栈
同步代码顺序执行,异步代码另起任务队列,只有在执行栈为空的状况下,任务队列才会开始工做。因此代码中执行到setTimeout时,往任务队列中添加事件,但并无执行。而当console.log('这是结束');执行出结果后执行任务队列里的任务。队列是先进先出,因此首先执行cb,最后执行cb1;

微任务

ECMAScript 2015 引入了 Promises(也在 ES6 / ES2015 中引入) ,使用了做业队列(Job Queue)概念,这是一种尽快执行异步函数的方法。异步队列分为两种,消息队列(也有叫宏任务)和做业队列(也有叫微任务)。执行顺序为: 当前执行上下文 -> 微任务 -> 宏任务根据事件循环理论分析代码浏览器

var t = new Promise((resolve, reject) => {
    console.log('宏事件');
    resolve()
}).then(() => {
    console.log('微事件');
})
setTimeout(() => {
    console.log(t.then(opt => {
        console.log('内层微事件')
    }));
    console.log('内层宏事件')
})
console.log('外层事件');
复制代码

宏任务微任务执行栈
promise的方法当即执行,而其回调则是在微任务队列中。setTimeout在宏任务队列中。主任务执行结束后,执行微任务,再执行宏任务。

事件循环和浏览器的关系

事件循环是js代码执行顺序的解释,可是这里面没有说到何时会对用户界面产生影响。 我在W3C的文章上看到的是这样的:bash

-运行JS代码
-运行微任务队列
-执行布局和IO工做
-运行宏任务队列。
复制代码

有结论,很美好,验证下。网络

<!DOCTYPE html>
<html lang="en">
    <body>
        <div class="outer" style="width:200px;height:200px;background-color: #ccc"></div>
    </body>
    <script> var outer = document.querySelector('.outer'); function onClick() { alert('start') outer.innerHTML = 'main func'; alert('main func do') setTimeout(function () { alert('setTimeout start') outer.innerHTML = 'setTimeout'; alert('setTimeout end') }, 10); Promise.resolve().then(function () { alert('Promise start') outer.innerHTML = 'Promise'; alert('Promise end') }); } outer.addEventListener('click', onClick); </script>
</html>
复制代码

咱们在各种任务里增长了DOM渲染,在DOM渲染的先后增长了弹窗(debug模式下不同的机制,不讨论),在Chrome(其余浏览器不同)上执行,发现Promisehtml end打印出来后,inner里才有了Promise字样,任务结束后,字样改为了setTimeout。上面的结论在chrome是ok的。

参考文章:

并发模型与事件循环

8.1.4 Event loops Definitions

Tasks, microtasks, queues and schedules

相关文章
相关标签/搜索