序言
后端仔每天都跟浏览器打交道,这么浏览器究竟是如何渲染的呢?
我们都晓得,JS是单线程的,也就是只有前一个任务执行完成,才能执行下一个任务。倘若前一个任务历时很长,这么下一个任务就只能干等着。其实,这样是十分浪费资源的。这么就要解决这个问题啦,先来了解一下「EventLoop」事件循环。
EventLoop
我们先来看一下HTML标准的解释:为了协调风波event,用户交互userinteraction,脚本script,渲染rendering,网路networking等,用户代理useragent必须使用风波循环「EventLoop」。
「event-loop」是解决JS单线程运行阻塞的一种机制,在JS的异步运行机制中,我们须要晓得:
这就是JS的运行机制,亦称为「EventLoop」事件循环。
异步任务分类
「宏任务」macrotasks:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UIrendering「微任务」microtasks:process.nextTick(NodeJS)、Promise、Object.observe、MutationObserver
理解风波循环
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('Promise 1')
}).then(() => {
console.log('Promise 2')
})
console.log('script end')
//运行结果
//script start
//script end
//Promise 1
//Promise 2
//setTimeout
过程剖析:
1、task队列中只有script,则将script中所有函数放进函数执行栈按次序执行2、接出来遇见setTimeout,这儿的setTimeout只是将反弹函数在0微秒后装入task队列(虽然是4ms,html5标准中规定中要求setTimeout中高于4ms的时间间隔算为4ms),也就是说反弹函数在下一个风波循环执行,setTimeout在这儿将反弹函数放至task列表后就结束了。3、遇到Promise,属于「microtasks」,所以将第一个then的反弹放在microtasks队列4、执行完所有script代码后,检测microtasks队列,发觉队列不为空,所以执行第一个反弹函数输出'Promise1',因为then的返回一直是Promise,因而将第二个then的反弹放至microtasks队列并执行输出'Promise1'5、此时microtasks队列为空,开始执行下一个风波循环。检测task队列发觉setTimeout的反弹函数,因而执行输出'setTimeout'
浏览器渲染
前面我们早已大致的了解到风波循环,为何讲浏览器渲染要扯到风波循环呢?这是由于风波循环跟浏览器渲染有大大的关系,我们来一探究竟。
关键渲染路径
关键渲染路径指的是浏览器接收最初的HTML,CSS,JS等资源后,解析,建立树,渲纺线局,勾画,最后呈现给用户能看见的界面的这个过程。主要过程如下:
里面的5个步骤并不是一次性执行完成,比如DOM或则CSSOM被更改时,都会有某个过程须要被重复执行,重新估算并重新渲染。实际上,因为JS跟css的操作会多次更改DOM跟CSSOM。
建立DOM树
当浏览器收到HTML文档后,会遍历文档节点,生成DOM树。HTMLParser将HTML标记解析成DOM树。
建立CSSOM规则树
CSSParser将每位CSS文件都解析成一个「StyleSheet」对象,每位对象都包含「StyleRules」,也称作CSSOM。
建立渲染树(RenderTree)
有了DOM树跟CSS规则树,浏览器就可以结合她们来建立渲染树了。浏览器会先从DOM树的根节点开始遍历每位可见节点,之后为每位可见节点找到适配的CSS款式规则并应用到DOM树上。
然而DOM树跟渲染树在结构上又不是完全对应的,区别在于:
渲染树布局
生成渲染树以后,还是没有办法直接渲染到屏幕上。由于这时侯还不晓得每一个节点的位置信息,这就须要布局(Layout)的处理了,这个过程虽然就是按照渲染树中渲染对象的信息,估算出每一个渲染对象的位置跟规格,将渲染对象置于浏览器相应的位置上。
回流与重画
回流(reflow):当浏览器发觉某个部份发生改变影响了布局,须要重新渲染。回流会从html的rootframe开始递归往下,依次估算所有节点的规格跟位置。回流几乎是难以避开的,只要行为导致了页面上元素的占位形式,定位方法,行距等属性的变化,这就会导致内部,周围,甚至整个页面的重新渲染。
重画(repaint):当改变某个元素的背景颜色,文字颜色,边框颜色等不影响它内部以及周围布局的。屏幕的某一部份要重写,而且元素的规格位置都没有改变,这就是重画。
浏览器渲染进程
浏览器的渲染是多进程的,包含了Browser进程,第三方插件进程,GPU进程,浏览器渲染进程(浏览器内核),这儿我们重点剖析浏览器渲染进程,由于页面的渲染,JS的执行,风波的触发都是在这个进程中进行的。划重点:「浏览器的渲染是多进程的,浏览器的渲染进程是多线程的。」
浏览器渲染进程有多个线程,下边来介绍浏览器的线程与其作用:
GUI渲染进程
GUI渲染进程做的事情似乎就是上述的「关键路径渲染」,这儿将不再表述。而在了解GUI渲染进程的执行过程后,我们可以依据原理进行渲染优化:
JS引擎线程
JS引擎线程,亦称为JS内核,负责处理JavaScript脚本程序。JS引擎等待着任务队列任务的到来,之后处理这种任务。无论哪些时侯,都只有一个JS引擎线程,由于JS是单线程的。
关于为何JS是单线程的,这儿我想用一个反例来解释一下:如果JS是多线程的,假定现今有2条线程,一条在dom节点上添加节点,另一条删掉这个节点。这么问题来了,这时侯该以那条线程为准。所以说,JS的主要用途就是与用户互动,操作dom节点,这就决定了JS只能是单线程的。
风波触发线程
风波触发线程拿来控制风波循环,当对应的风波符合条件被触发时,该线程会将风波添加到待处理的风波队列中,等待JS引擎的处理。
上述早已提到,所有的同步任务都在主线程运行,而异步任务步入任务队列。而异步任务均由风波触发线程控制,只要异步任务有了运行结果,还会在任务队列中放置反弹函数,所以说异步任务一定要指定反弹函数。
主线程空了,还会去读取任务队列。这个过程不断的重复,其本质基于JS的风波协程机制。
定时器触发线程
JS是单线程的,当处于阻塞线程的状态会影响计时的确切性,因而须要单独开一个线程来计时。
当使用setTimeout或则setInterval时,须要定时器线程计时。计时完成后会将特定的风波推动风波触发线程的任务队列中,等待步入主线程执行。