开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第二天,点击查看活动详情
前言
面试经常问的事件循环,你是怎么回答的?js是单线程的那么什么是线程是什么?
进程与线程
进程我我们把计算机比喻成一块土地,我们在这块地上盖了一座工厂,那么这就是一个进程,这块地上我们可以盖多个工厂(多个进程),盖多了是不是就挤了(计算机卡了)。那么线程其实就是工厂里的工人,多个工人就是多个线程,当我们打开浏览器创建一个进程(工厂),我们又添加了一个tab页又一个进程,其实就是这么一个原理。
渲染进程
我们打开一个tab页面其实下面有多个进程,来处理不同的工作,这里我们主要看渲染进程,渲染进程就是处理我们的html、css、js的,然后下面又分了多个线程,我们经常说的js单线程,其实就是说的渲染进程中的一个。
JS引擎线程
(例如V8引擎)JS引擎线程负责解析Javascript脚本,运行代码。JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序。(这个就是处理js的工人我们找到他了)
注意:GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
事件触发线程
归属于浏览器而不是JS引擎,用来控制事件循环。当JS引擎执行代码块如setTimeOut时(也可来自其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到对应的线程中。
定时器触发线程
传说中的setInterval与setTimeout所在线程
浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
异步http请求线程
在XMLHttpRequest连接后是通过浏览器新开一个线程请求
将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行
合成线程
在GUI渲染线程后执行,将GUI渲染线程生成的带绘制列表转换为位图。
IO线程
用来和其他进程进行通信
现在我们看到不同的任务有有对应的处理线程,上面我们提到了事件队列,那么事件队列又是啥呢?
事件队列(任务队列)
可以理解为一个静态的队列存储结构,非线程,只做存储,里面存的是一堆异步成功后的回调函数,肯定是先成功的异步的回调函数在队列的前面,后成功的在后面。
注意:是异步成功后,才把其回调函数扔进队列中,而不是一开始就把所有异步的回调函数扔进队列。比如setTimeout 3秒后执行一个函数,那么这个函数是在3秒后才进队列的。
从实际代码中去了解它们之间的关系
下面是一段js代码
//先把它们给区分一下
1.var a = 2;//同步代码
2.setTimeout(fun A) //异步代码
3.ajax(fun B) //异步
4.console.log() //同步
5.dom.onclick(func C) //同步 等待用户点击dom后触发
图中 3 、9行代码是同步代码直接在js引擎线程(主线程中)执行,第五行setTimeout异步代码,上面我们说了有专门处理它的线程交给定时器触发线程,再接着碰到ajax异步请求,交给异步http请求线程,再接着dom.onclick交给事件触发进程,等待用户点击触发。
现在我们知道了,现在分类分完之后也把任务给了相应的线程(工人),那么后续操作是怎么样呢,总不能把任务分出去就完事了吧。这时候任务队列上场了。
setTimeout交给异步线程后,在相应的秒数执行完后,这时候funA会进入任务队列同样funB请求成功后的回调函数也会进入任务队列,对于dom.onclick,浏览器会监听dom,直到dom被点击了才会将回调函数放入任务队列
EventLoop事件循环
我直接用一张图来说明
这就是整个js执行流程,整个过程是一直重复循环的下面说几个注意点:
- 只有主线程的同步代码都执行完了,才会去队列里看看还有啥要执行的没。
- 如果主线程里执行的代码复杂需要很长时间,这时队列里的函数们就排着,等着主线程啥时执行完,再来队列里取
所以从这里能看出来,对于setTimeout,setInterval的定时,不一定完全按照设想的时间的,因为主线程里的代码可能复杂到执行很久,所以会发生你定时3秒后执行,实际上是3.5秒后执行(主线程花费了0.5秒)
面试回答
当我们打开一个tab页面的时候其实这时候创建了一个主进程,我们页面其实通过渲染进程来处理我们的html、css、js代码的,渲染进程里面又分一些 js引擎线程(处理js代码)、定时器触发线程、异步http请求线程 等,当我们一段代码有同步任务,定时器、请求的时候,js线程遇到同步任务直接执行,遇到计时器通过事件循环中心把代码交给异步线程(定时器,请求等) 然后把回调函数在给事件中心,事件中心在把回调函数给任务队列,主线程同步代码执行完了,就去任务队列顶部去取出一个函数执行,这个过程是循环的,这就是事件循环。
end
下一遍复习微任务与宏任务,来看看它们是怎么在事件中心执行的