一、为什么有消息队列和事件循环?
我们都知道,浏览器每打开一个标签页(目前是每打开不同的站点网页),就会新开一个渲染进程。 而这个渲染进程里,分为以下这些线程:
- 主线程(也就是 JS 引擎线程)
- GUI 渲染线程
- 事件触发线程
- 定时器触发线程
- 异步 http 请求线程
其中,主线程既要处理 DOM,又要计算样式,处理布局,同时还需要处理 js 任务及各种事件。 在同一个线程中处理这么多的任务,如何让不同的任务按照不同的优先程度有条不紊地执行呢? 这就需要有一个调度系统来对这些任务执行进行系统地规划,而这个系统,就是消息队列和事件循环系统。
二、从头实现消息队列和事件循环
2.1 第一版-同一个线程处理安排好的任务
我们先从最简单的情况开始想。 举个栗子:现在你要做一道算术题,需要你一行一行按顺序计算,得出最终的结果:
// 开始线程
1. a = 5 + 10
2. b = 4 * 2
3. 输出 a + b
// 线程退出
假如每一行都是一个任务,而步骤 1、2、3 就是主线程需要执行的任务列表。这些任务都是事先安排好的,直接一行行按顺序执行就好,最终得出的结果是23。
这便是最简单的版本:调度系统按预先安排好的顺序挨个执行任务。
2.2 第二版-同一个线程处理动态加入的任务
第一版的情况太过于简单。真实情况里,我们不可能把所有要执行的任务都提前安排好。 最简单的例子是浏览器的各种事件,比如 click 事件任务何时发起取决于用户何时点击鼠标。 那么,第二版我们来讨论,当在执行过程中会一直出现新产生的任务,要如何处理?
最简单的方式就是:维护一个任务列表,当目前已有的任务执行完,就去循环任务列表,检查并执行新的任务。
截止到现在,已经有一点事件循环的样子了。
2.3 第三版-处理不同线程产生的任务
现在我们再接着考虑更现实一点的情况。之前,我们讨论的一直是在同一个线程内的情况。 然而在正式的浏览器运行中,一个渲染进程有多个线程,主线程需要处理的任务可能来自于多个线程。比如定时器计时完成产生的任务、比如用户点击鼠标产生的任务。
这种情况下,该如何执行任务呢? 想象一种情景:你的任务是将水果分类放入不同的箱子,然而,一整天里,一直都会有不同的人来给你送水果,你如何能够把不断送来的水果,都分类清楚呢?还有一个需求:来的早的人,你需要先把人家的水果分类好。
怎么实现呢?
最好的方式是,有一条流水线,来送水果的人都把水果依次放在流水线上。 而当你在分类水果的时候,总是不断地一次次去看流水线上还有没有水果,就可以了。
这个流水线,就是消息队列。队列作为一种数据结构,是先进先出,就满足了“先产生的任务要先被执行”这种需求。
其他线程产生任务便推入消息队列。而主线程一旦把当前任务执行完毕便去消息队列取出最靠前的任务依次执行。 这便是 消息队列。
截止到现在,就是我们经常讲的 消息队列和事件循环
<关于我们>
我们是来自帝都的一枚前端程序猿 + 一枚前端程序媛。
这里发布的文章是我们对学习内容的总结,预计会每周至少会更新一篇。
目前我们学习计划是: 小程序实战 => vue 进阶用法 => vue 原理 => css 基础 => es6 => js 深入
另外,工作中用到的一些技术和完成的功能,我们也会及时总结更新在这里
如文章有错误或表述不清晰,欢迎各位留言反馈~~