前言
Event Loop 也是 JavaScript 的重点知识之一,为了讲清楚,我会从浏览器进程,其中的渲染进程包含的线程等方面,来仔细说明。各个知识点都是相关联的,简单地说出宏任务和微任务并不能帮我们很好地理解整个事件循环机制。
浏览器是多进程的
浏览器进程可以简单分为以下几点:
- Browser 进程——浏览器的主进程,负责协调和主控,该进程只有一个。它可以控制页面的显示,和与用户的交互。负责各个页面的管理,创建和销毁进程。可以将渲染进程得到的内存中的 Bitmap(位图)绘制到用户界面上。同时它还负责网络资源的下载管理。
- 第三方插件进程。该进程与第三方插件是一对一的关系,在使用的时候才会创建。
- GPU进程。负责3D绘制。
- 渲染进程——即我们所说的浏览器内核。每个 Tab 页面都有一个渲染进程,互不影响。它主要用于页面渲染、脚本执行、事件处理。
渲染进程
渲染进程主要包含以下几个线程:
-
GUI渲染线程。
负责浏览器渲染界面,解析 HTML、CSS,生成 RenderTree。负责回流和重绘。
与JS引擎线程互斥,在JS引擎线程执行时,GUI 渲染线程会挂起(冻结),保存在一个队列中,等到JS引擎线程空闲的时候,立即被执行。 -
JS引擎线程。
JS内核。负责处理JS脚本。等待执行任务队列中的任务。一个Tab页面只有一个JS线程。与GUI渲染线程互斥,会阻塞GUI引擎渲染。当浏览器渲染时遇到script标签,会停止GUI渲染,等到JS引擎执行完毕停止工作,再继续进行GUI渲染。 -
事件触发线程
属于浏览器而不是属于JS引擎。用来控制事件循环,管理一个事件队列。
我们知道JS是单线程的,为了避免阻塞,当JS执行到事件绑定和一些异步操作的时候(比如SetTimeout,或可能来自浏览器其他线程的鼠标点击,AJAX等),会通过事件触发线程将事件添加到对应的线程中,如定时器事件=>定时器线程。等到异步事件有了结果,再把他们的回调添加到事件队列中,等待JS引擎线程空闲时处理。 -
定时触发线程
SetTimeout,SetInterval 所在线程。浏览器定时计数器通过单独线程来计时并触发定时,计时完毕后添加到事件触发线程队列中,等到JS引擎空闲执行。 -
异步HTTP请求线程
在 XMLHTTPRequest 连接后通过浏览器新开一个线程,检测到状态发生变化时,若有回调函数,异步线程就产生状态变更事件,将回调放入事件队列由JS引擎执行。
所有线程的工作都单一且独立。如定时器线程只关心定时不关注结果,时间到了就把回调给事件触发线程,事件触发线程只关心异步回调入事件队列,而JS引擎线程只会执行执行栈中的事件,执行完毕之后就会读取事件队列中的事件添加到执行栈中执行。
事件循环
上面我们说了分工不同的各个线程,现在我们就来捋捋这个事件循环。
同步任务都在主线程(JS引擎线程)上执行,形成一个执行栈。
主线程之外,事件触发线程管理着一个任务队列,异步任务有运行结果时,就将事件回调放入任务队列。
执行栈中的同步任务执行完毕,JS引擎线程就会读取任务队列,添加到执行栈中,开始执行。
宏任务
每次执行栈执行的代码,可看作一次宏任务,每一个宏任务会从头到尾执行完毕,不会执行其他。宏任务包括:
- 主代码块、setTimeout、setInterval、setImmediate、requestAnimationFrame (浏览器)
微任务
可理解为在当前宏任务执行后立即执行的任务。一个宏任务会在渲染前将执行期间产生的所有微任务执行完。微任务包括:
- process.nextTick()、promise.then()、catch、finally、object.observe、Mutation Observer
执行顺序可以总结为:
宏任务 => 微任务 => GUI渲染 => 宏任务 …(循环)
- 执行一个宏任务(栈中没有就从任务队列中获取)。
- 执行时遇到微任务添加到微任务队列中。
- 宏任务执行完毕后,立即执行微任务队列中的所有微任务。
- 开始检查渲染,GUI线程接管渲染。
- 渲染完毕后,JS引擎线程继续接管,开始下一个宏任务。
其他
JS单线程的原因
① 创建 JS 的时候,多线程并不流行,也缺乏足够的硬件支持。
② 多线程更复杂,需加锁,操作DOM可能会导致预期之外的结果。
本文深入探讨了JavaScript中的Event Loop事件循环机制,从浏览器进程的多线程模型开始,包括渲染进程、GUI渲染线程、JS引擎线程、事件触发线程和定时触发线程。讲解了宏任务与微任务的概念及其执行顺序,阐述了JS单线程设计的原因。
925

被折叠的 条评论
为什么被折叠?



