要弄明白浏览器任务调度,先要搞清楚进程和线程这两个概念。
一、进程、线程的关系与区别
进程:CPU进行资源分配的最小单元
线程:CPU任务调度的最小单元
对于操作系统来说,一个独立的任务就是一个进程。为什么要强调“独立”?因为操作系统会给每一个进程分配完全独立的内存空间,进程与进程之前不能共享资源。而一个进程由一个或多个线程组成,线程与线程之前可以共享分配给所属进程的内存中的资源!
二、浏览器的进程、线程分配
浏览器由多进程组成,每新建一个Tab就会新建一个进程。因此,不同Tab之前内存互不影响,如果某个Tab由于内存被长期占用而崩溃,并不影响其他Tab页面。那么一个Tab进程的调度又是如何的呢?
2.1 主进程
一个Tab只有一个主进程,主要负责页面和创建和销毁,网络资源的请求、下载,前进、后退等。
2.2 第三方插件进程
当某个第三方插件被调用时会为该插件创建一个进程,没用到则不会创建。
2.3 GPU进程
这个进程只有在需要进行3D绘制的时候才创建。
2.4 渲染进程
此进程亦称为浏览器内核,是进行DOM解析与渲染、CSSOM解析、js加载与执行的主要进程。
- js引擎线程
此线程为单线程!主要负责js脚本的加载与执行,一个Tab只有一个js引擎线程!并且此线程与GUI线程(后续介绍)互斥,这两者其中一个在执行的时候,另一个必然处于挂起状态。
- 事件线程
此线程用于管理js中解析到的事件。注意,它并不负责执行,它只负责维护一个事件队列,等到js引擎线程空闲下来后再把事件队列挨个放进js引擎线程中去执行。
- 定时器线程
此线程主要用于管理js引擎线程中产生的定时器,当js引擎线程执行遇到setTimeout
、setInterval
这类定时器时,则会交给定时器线程进行倒计时,当倒计时结束后,回调函数会被放进事件线程等待js引擎线程空闲下来。假如倒计时结束时事件线程中仍有未调用的事件,那么定时器的回调函数还不会被马上执行,这也是为什么有时候会产生定时器真正执行的时候与倒计时结束的时间有偏差。
- xhr线程
http本身就是异步请求,所以此线程不受其他线程约束,也不依赖于其他线程,可独立执行。
- GUI线程
此线程负责DOM Tree
和CSSOM Tree
的解析,然后将二者结合生成Render Tree
,最后渲染浏览器界面。前面提到此线程与js引擎线程互斥,也就是说在DOM解析过程中遇到了script
标签,此线程会被临时挂起,接下来将执行权让给js引擎线程,等js加载并执行完成后,重新拿回执行权,才能继续解析剩余DOM。
三、思考
3.1 js引擎线程为什么要设计成单线程?
这个问题可以反过来思考,假如js引擎线程支持多线程会怎样?假如在a.js
中我要对id
为test-dom
的div
标签进行修改,而在b.js
中我要对它进行删除。多线程异步操作,a.js
修改节点时发现节点已经被删除了,那么这个修改操作将无任何意义!由于js可以操作dom的增、删、改、查以及样式的动态变化,为了避免多线程带来的冲突及频繁地回流和重绘,js引擎线程被设计成单线程就成了必然要求。
3.2 js引擎线程和GUI线程为什么要设计成互斥的?
这一点其实和js引擎线程被设计成单线程的道理有些类似。由于js中存在对DOM和样式的操作,如果js引擎线程和GUI线程可以异步执行,将会导致不确定的结果,因为js执行的时候无法保证DOM是否一定被解析了。
3.3 js会阻塞DOM解析,css会阻塞DOM解析吗?
事实上,css文件的加载和解析并不会阻塞DOM的解析。但它会阻塞DOM
的渲染!因为Render Tree
是由DOM Tree
和CSSOM Tree
相互结合生成的,如果DOM解析已经完成,而css尚未解析完成,GUI线程会等css解析完成后才进行渲染。
3.4 js中DOMContentLoaded
和load
方法分别在什么时候执行?
DOMContentLoaded
在DOM解析完成后即执行,此时可能会有哪些情况呢?比如css解析尚未完成、浏览器中还不存在已经渲染完成的节点、诸如图片之类的静态资源加载还未完成。
load
是在页面渲染已经完成后才执行,包括图片等静态资源也已经加载完成。
3.5 script
标签的async
和defer
各自有什么作用?
async
修饰的js加载和执行都不会阻塞DOM的解析,是完全异步的一种引用形式。
defer
则表示在加载阶段和DOM解析是异步的,但是js的执行必须等到dom解析完成后,DOMContentloaded
方法执行之前。
async
修饰的js在执行的时候阻塞了DOM解析。此图结合自己的理解画的,欢迎讨论。