JavaScript学习笔记:浏览器底层渲染机制

624 阅读5分钟

了解了浏览器渲染机制,就可以更好的写代码,更好的做优化。就需要理解向服务器取回的文件如何渲染。

进程:一个程序(一个进程中可能包含多个线程)

线程:程序中要做的事情,一个线程只能做一个事情。

浏览器向web服务器发送请求,获取到index.html页面的源码,浏览器分配一个线程“自上而下”,“自左到右”依次解析和渲染代码。

1. 样式的处理

【link导入外部样式资源】

异步

在页面加载过程中,如果遇到link,浏览器会新开辟一个线程去服务器获取对应的资源文件。(不会阻碍主线程的渲染)

【style内嵌样式】

同步

如果是style内嵌样式,正常从上到下解析。解析完继续解析DOM结构。

优化:

在真实项目中,如果css样式代码不是很多(或者是移动端项目),我们应该使用内嵌式,以此来减少http资源请求。提高页面渲染速度。

【@import 导入样式】

同步

如果遇到@import导入样式,此时不会开辟新的线程去资源文件,而是让主线程去加载获取,这样阻碍了DOM结构的继续渲染,只有等把外部样式导入进来,并且解析后,才会继续渲染DOM结构。

网络资源请求或HTTP请求的最大并发数(优化)

大部分浏览器都维持在六个左右。(为了避免并发的上线,导致某些资源要延迟加载,页面渲染速度变慢,我们应该尽可能减少HTTP请求数量)

为什么要把link写在结构上面,script写在结构下面?

link放到顶部是为了更快的加载回来的css。

script放在底部是为了获取DOM元素或者不阻碍DOM的渲染。

2. JS处理

页面加载过程中遇到JS(js中有操作DOM的代码)

<script src="xxx"></script>

主线程会从服务器获取到js资源,并且把js资源进行解析加载,加载完成后继续渲染DOM结构

现代浏览器都有完善的扫描机制,如果遇到script需要同步加载和渲染代码,浏览器就会在渲染js的时候同时向下继续扫描代码,如果发现有异步资源代码,会开始加载请求

【设置defer或async】

当我们想要把js文件放在header里,并且这个js文件还会操作DOM,就可以添加这两个属性。

都是变为异步获取资源,但不会阻碍DOM的渲染

区别:

  • defer可以遵循原有的加载顺序,获取后按照顺序去依次渲染js。例如有4个js文件,假如4号先回来,那也得等着,等前面123都回来了再去依次渲染
  • async:是无需的谁先获取到先执行谁。如果js之间没有依赖关系时可以使用。

js等待机制: 在js中可能还会操作元素的样式,所以哪怕都是异步请求资源的情况下,js先加载回来,也要等到他之前发送的css加载并渲染完成后才会执行js代码.

【DOMContentLoaded 和 load】

  • DOMContentLoaded 事件:当DOM结构加载完成就会出发

DOM 树有了,并且js也执行加载了,此时触发这个事件

因此还可以监听这个事件来对DOM进行操作

window.addEventListener('DOMContentLoaded', _=>{
    console.log(document.getElementById('box'))
})
  • load事件:当所有资源都加载完成才会触发

包含了需要等待图片等资源也都加载完才触发

JQ类库中:

$(function(){}) 或者 $(document).ready(function(){})

都是当DOM结构加载完才会执行函数中的代码

原理:应用DOMContentLoaded事件完成的

DOMContentLoaded是DOM二级事件,在低版本浏览器中不兼容,不兼容使用onreadystatechange事件代替,在这个事件中监听document.readyState值,值为complate代表DOM结构加载完成

3. DOM的回流(reflow)和重绘(repaint)

回流:根据渲染树计算出它应有的结构,大小,位置等,这个计算的阶段叫回流。

重绘:根据渲染树和回流得到的信息,开始准备绘制

性能优化之:避免DOM的回流

  • 放弃传统操作DOM的时代,使用基于vue/react开始数据影响视图的模式

vue/react 拿到最新数据后,重新生成虚拟DOM,拿以前的虚拟DOM和现在对比,渲染的时候统一一次性渲染。比我们自己操作DOM性能更好,自己操作容易引发回流,而数据渲染引发次数少的多。

  • 读写分离

如果不可避免需要自己操作DOM,那就需要读写分离

渲染队列机制导致引发一次回流

(浏览器发现DOM要被操作,就存储在渲染队列里,直到发现不需要操作DOM的代码后,一起渲染)

box.style.width = '100px'
box.style.height = '100px'

这种情况就会触发两次

box.style.width = '100px'
console.log(box.clientWidth)
box.style.height = '100px'

以下方法都会引起渲染队列刷新

  • 样式集中改变

    • 设置classname,把要改的样式写成样式类,统一一次改了。
    div.className = 'box'
    
    • 通过style.cssText,最终统一加载为行内样式
      div.style.cssText="width:100px;height:100px"
    
  • 元素批量修改

    • 创建文档碎片:例如向div添加10个span标签
    let box = document.getElementById('box')
    let frag = document.createDocumentFragment()
    for(let i = 0; i<10 ; i++){
        let span = document.createElement('span')
        frag.appendChild(span)
    }
    box.appendChild(frag)
    
  • CSS3硬件加速(GPU加速)

    transform/ opacity/ filters 这些属性会触发硬件加速,不会引发回流和重绘

    问题:用的多了会占用大量内存,性能消耗严重,可能会导致字体模糊