事件循环到底是什么回事啊?
事件循环机制使js能够非阻塞的执行代码,处理异步操作
本文会从浏览器的进程和线程开始,逐渐讲到事件轮询机制。
浏览器的进程和线程
众所周知,浏览器是个多进程应用,本文要讲的事件循环机制主要涉及的就是浏览器渲染进程,其包括多线程:
- GUI渲染线程:主要负责页面的渲染,其与js引擎呈互斥关系
- js引擎线程:主要负责解析js文件,是本文的主角之一
- 事件触发线程:当异步事件执行完后,由该线程负责将其触发的回调函数根据异步源(宏任务还是微任务),放入对应的任务队列中
- 定时触发线程:顾名思义该线程主要负责执行定时器任务
- 异步http请求线程:该线程主要负责http请求相关任务
js是单线程的
js是单线程的说的就是其只有一个执行线程,也就是js引擎线程。因为在浏览器环境中,js可以对dom进行操作,如果其设计为多线程,当多个线程同时对dom进行操作,很容易造成冲突。
js单线程存在的问题
单线程的意思是说每次执能执行一个任务,就像高速收费口一样,每次执能处理一辆车的缴费工作,如果这辆车缴费过程很慢,那么后面车就只能等着。如果没有任务队列和事件循环机制,那么js的单线程也会存在同样的问题,当遇到例如定时器这样需要长时间等待执行的函数,那么就会阻塞后面函数的执行。
但好在这种情况并不会发生,浏览器通过任务队列配合事件循环机制,非常巧妙的实现了js非阻塞执行。
任务队列
队列是一种尾进头出,先进先出的数据结构。任务队列中存放的是异步函数执行完后触发的回调函数。当执行栈为空后,主线程会来任务队列里查看是否有待执行的回调函数,如果有则将其放入主执行栈中进行执行。但这里有个关键细节,主线程会先检查微任务队列中的任务,直到微任务队列清空,才会去执行宏任务队列,每当执行完一个宏任务,会再次检查微任务队列,这个过程会一直重复形成循环。也因此有多个宏任务队列,而微任务队列只有一个。
是的没错,任务队列分为宏任务队列和微任务队列。对于不同的异步任务其是存在优先级的,微任务队列中存放优先级高的微任务,宏任务队列中存放优先级低的宏任务。这样做的目的其实就是为了‘插队’,当来了一个优先级比较高的,但任务队列中却排满了异步任务,再将其放到最后排队显然是不合理的。
那么有哪些异步函数是微任务,哪些又是宏任务呢?我自己通俗理解,微任务一般是js自身带的api,宏任务则是运行环境如浏览器带的api。
宏任务
- setTimeout,setInterval
- script文件的执行
- i/o操作:网络请求等
- ui渲染
微任务
- Promise
- async/await
- MutationObserver
整体流程
-
js从宏任务队列出取出一个宏任务(最开始就是运行script文件)
-
遇到宏任务将其交给对应的线程进行处理,处理完后会触发回调函数,将回调函数存入宏任务队列。
遇到微任务直接将微任务存入微任务队列
-
待执行栈被清空后,检查微任务队列中是否有微任务,如果有则将其按顺序调入执行栈进行执行,直至全部执行完
-
再取出一个宏任务
-
....
-
按照上述顺序全部执行完
涉及概念
执行栈 :我们知道 js 执行的就是一个个的函数,每个函数都有其对应的执行上下文,这其中包括 作用域 ,this等,每当浏览器执行一个函数,就会将其对应的执行上下文压入执行栈中,运行完后再将其弹出。
回调函数 :当遇到异步任务时,我们不能知道其会在什么时候执行结束,比如一个 http请求 我无法知道其什么时候会返回结果。回调函数的出现就解决了这个问题,将回调函数作为参数传给异步任务,等其执行结束后会触发这个回调函数。
转载自:https://juejin.cn/post/7393314542094204938