浏览器是多进程多线程的应用。
主要有浏览器进程、网络进程、渲染进程。
渲染进程
渲染进程启动后,会开启一个渲染主线程。默认情况下,浏览器会为每个标签页开启一个渲染主线程。
渲染主线程:主要处理解析 HTML,CSS,计算样式,布局,处理图层(z-index),按帧数每秒重绘页面(一般 60 帧,即重绘 60 次),执行全局 JS,执行各种回调等。
计算样式:
(1)确定声明值 (2)层叠 (3)继承 (4)使用默认值
确定声明值:如果某个属性在作者样式表或者浏览器样式表有明确的值,且没有冲突的情况,则直接取该值。
层叠 :上述有冲突的样式,则通过层叠计算,按权重取值。 作者样式表 > 浏览器默认样式表
继承:对于没有指定值的属性,判断其可以继承,则继承其父级元素的值。 (文本样式基本都是可以继承的)
默认值:上述三步走完仍然未确定的,则取该样式的默认值。
事件循环
如何理解 JS 的异步?JS 为什么需要异步?
JS 是单线程语言,原因在于它是运行在浏览器的渲染主线程,浏览器渲染主线程只有一个。 页面渲染、js 执行等诸多工作都是需要渲染主线程去承担的。如果 JS 没有异步的方式,使用同步的方式执行,那么极有可能导致渲染主线程阻塞,导致消息队列中很多后续任务无法被执行,这样一方面会导致主线程持续等待消耗时间,另一方面会导致页面渲染阻塞,页面无法及时更新,给用户造成卡死的假象。
所以浏览器采用异步的方式去避免这个问题。具体做法是当某些任务发生时,比如计时器、网络异步请求、事件监听等,主线程会把这种任务交给其他线程去处理,自己则立即结束该任务的执行,开始执行后续的任务。 当其他线程完成任务处理后,会将事先传递的回调函数包装成任务,加到主线程消息队里末尾,等待主线程调度执行。
任务是否有优先级?
任务没有优先级,在消息队列中先进先出。
但是消息队列有优先级。
W3C 最新解释:
- 每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属不同的队列。在一次事件循环中,浏览器可以按实际情况去决定从不同队列取任务执行
- 浏览器必须准备一个微队列,微队列中的任务优先所有其他任务的执行。
目前 chrome 至少包含了:
- 延时队列:计时器回调,优先级 中
- 交互队列:存放用户操作后产生的时间处理任务,优先级 高
- 微队列:存放需要最快执行的任务,优先级 最高 ---------- 添加任务到微队列的主要方式是Promise、MutationObserver
常见面试题:
1、阐述一下 JS 的事件循环?
事件循环又叫消息循环,是浏览器渲染主线程的工作方式
在Chrome源码中,它开启一个不会结束的for循环,每次循环都会从消息队列中取出第一个任务执行,而其他线程只需要在合适的时机将任务加入到消息队列的末尾即可。
过去是把消息队列简单的分成了宏队列和微队列,这种说法已经无法满足复杂的浏览器环境了,取而代之的是一种新的处理方式。
按照新的解释,每个任务都有一个任务类型,同一类型的任务必须在同一队列,不同类型的任务也可以分属不同的队列。任务队列是有不同的优先级的,在一次事件循环中,浏览器自行决定取哪个队列的任务,但是必须有一个微队列,且微队列的优先级是最高的。,必须优先调度执行。
2、JS 中的计时器能做到准确精确计时么?为什么?
不能,因为:
1、计算机硬件没有原子钟,无法做到精确计时
2、操作系统的计时函数本来就有少量偏差 ,而 JS 的计时器最终调用的是操作系统的函数,也就会有偏差
3、按照W3C的标准,JS 计时器嵌套超过5层,默认就会有4ms的偏差
4、受事件循环的影响,计时器的回调函数只能再主线程空闲时运行,因此也会带来偏差。