浏览器的事件循环

什么是事件循环

JavaScript 有一个特点就是异步单线程,异步就是同时干不同的事,单线程就是只有一个线程可以做事,而 js 实现这一点就是通过事件循环来实现,本文只讨论浏览器端的事件循环,暂不涉及 Node 的事件循环。

前置知识

在具体了解事件循环之前,我们先了解一些必要的内容,帮助我们更好的理解事件循环。

执行栈(call stack)

函数调用栈,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。每次 js引擎执行的都是执行栈顶的代码。

function f1 () {
  f2();
}
function f2 () {
  f3();
}
function f3() {
  console.log('f3');
}
f1();

对应的函数调用栈的情况应该是:
在这里插入图片描述

任务分类

在 js 中任务,或者说函数分为两种,一种是同步的,一种是异步的。

该如何区分呢?

一个很好的办法就是看调用的什么时候可以得到结果,如果发起调用,立马能获得结果就是同步任务,无法立即得到结果的就是异步,比如 ajax,计时器,或者注册的事件,这些都需要等待才能得到结果。

事件队列(event queue)

一种数据结构,js 中异步操作的回调函数都会放入这个队列中,排队让 js 引擎执行。
在这里插入图片描述

浏览器的常驻线程

js 引擎线程

js 引擎线程负责帮我们执行 js 代码,该线程只执行执行栈最顶层的代码,我们常说的js单线程,就是说 js 引擎线程是单线程。

GUI 渲染线程

GUI 渲染线程负责帮我们渲染页面(当界面需要重绘(repaint)/回流(reflow)时,该线程就会执行),需要注意的是该线程和 js 引擎线程互斥,什么意思呢?就是这连个线程不能同时工作只有当 js 引擎线程空闲的时候 GUI 线程才会工作,很好理解,js 是可以 操作 dom 的,总不能一边改变 dom 一边渲染吧。
可以用一小段代码测试一下

<button>click</button>
<script>
    window.onload = () => {
        setTimeout(() => {
            console.log('start')
            console.time();
            for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) { }
            console.timeEnd();
            console.log('end')
        }, 5000);
    }
</script>

在从 start - end 这 110713ms 内 js 引擎线程一直忙于执行计算,所以 GUI 是无法工作的,这就是为什么在这段时间内点击 button 是没有动画的
在这里插入图片描述

http 网络请求线程

是浏览器用来发送 http 请求的线程,我们使用代码发送 ajax 请求服务器数据就是委托该线程帮忙

定时器触发线程

当我们使用 setInterval 和 setTimeout 函数就是委托该线程帮我们计时

事件处理线程

当我们给一个 dom 元素注册了一个事件,该线程就会帮我们监听该事件

事件循环

首先我们需要了解一个事实,js 虽然是单线程但是不代表在执行代码的过程中只有 js 引擎线程一个参与,其他的线程也会参与辅助代码的执行。
js 执行代码的具体流程,我们直接用例子来说明:

function f1 () {
    console.log('f1');
}
f1();
setTimeout(function f2() {
    console.log('f2')
}, 1000);
console.log('window')

具体的过程就是,首先全局的执行上下文入栈,接着执行 f1f1 执行完毕接着 setTimeout 函数入栈,js 引擎发现这是异步任务,则会通知计时器线程(让它帮忙计时,计时结束后将回调函数放入事件队列,等待 js 引擎空闲时执行)接着执行 console('window'),然后 console 的执行上下文出栈,js 引擎空闲,于是 js 引擎去看看事件队列中是否有任务,没有就等待,有就立即执行,计时器线程在 1s 后将 f2 函数放入事件队列中等待执行,js 引擎空闲之后执行。
对于 计时器这些线程我们称之为 Web API 模块,js 引擎运行的大致过程就是,同步任务立马执行,异步任务注册回调函数然后分发给 web api 模块去处理,处理完放入事件队列等待引擎空闲去运行事件队列中的任务。这整个过程就叫做事件循环。

宏任务和微任务

其实异步任务还有分类,分为宏任务,和微任务,相应的,事件队列也有两个,分别为宏队列和微队列。
微任务的优先级比宏任务的优先级要高,当两个队列都非空的时候,微队列中的任务会被优先执行。
宏任务:计时器的回调,ajax,注册的事件都是宏任务
微任务:Promise,MutationObserver

setTimeout(() => console.log(1), 0);
new Promise(resolve => {
  resolve()
  console.log(2)
}).then(() => {
  console.log(3)
});
console.log(4);

从这一段代码的运行结果可以很清楚的看出微任务的优先级是高于宏任务的。
所以事件循环的大体流程应该是:

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页