欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

说说浏览器渲染

程序员文章站 2022-05-12 16:35:21
...

传说中的DOM操作

    从去年正式接触到前端开发开始,就听很多人说直接操作dom成本高 性能差,尤其是在学习了React这类的MVx框架之后,前端也开启了data驱动view的模式,大学学习的jQuery还没怎么用就被毙掉了,那到底为什么DOM操作会严重降低前端性能呢?


原来DOM是这货

DOM 文档对象模型,是Document Object Model的缩写,CSSOM css对象模型,是CSS Object Model的缩写

    在刚接触前端的时候,一直以为DOM就是div、span、Hx这些标签。DOM毕竟是model,html作为一种标记语言 在DOM模型中担任对象的角色,而DOM则为html标签提供‘编程API’,DOM不会去操作标签属性 内容等内部的东西。但实际开发中,光设计 不动态修改界面是不合理的,所以就出现了JS等脚本语言进行html级别的操作
    其实DOM操作不是JS的特权,貌似Python这些脚本语言也是可以的。而且在前端页面加载的时候,除了DOM 还有一个叫CSSOM的东西,负责解析CSS并形成树,和DOM是两套模型结构

说说浏览器渲染

    DOM操作成本,无非就是传输成本+渲染成本两部分。首先服务器和客户端(这里只说浏览器,其他的不太了解 不敢乱说 怕被打),挥个手啊 抱一抱啊巴拉巴拉的PY交易一波,就把文件从服务器搬到了浏览器,我们下面介绍的浏览器渲染 也只从这个时间点开始 只讲DOM渲染,JSP这种服务端渲染就不讲了。浏览器渲染主要步骤如下:

  • HTML解析,生成DOM模型树(如果这里有外链或者内联 会发出一些请求或者加载)
  • CSS解析,构建CSS样式树(同上)
  • 合并CSSOM和DOM
  • render布局
  • render绘制
  • 小尾巴

HTML解析

<html>
  <head>
    <meta name="viewport" content="initial-scale=1">
    <link href="xxx.css" rel="XXX">
    <title>HaHaHa</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students</p>
    <div><img src="XXX.png"></div>
  </body>
</html>

    我们以上面的代码为例,树的构建过程如下。类似数据结构里面 树的先序遍历,只有当前节点的所有子节点都遍历 处理好了,才会去遍历兄弟节点
说说浏览器渲染

CSS解析

    css解析比html解析稍微复杂点儿,很多浏览器都有一套自己的样式库(又爱又恨的东西,有时候需要花很多精力去样式覆盖),所谓的css解析就是对样式库进行一次从通用样式(比如父级的 作用于某一块的‘全局样式’)到某节点具体样式的样式替换。最终输出的就是修改版的浏览器样式库

合并CSSOM和DOM

    合并CSSOM和DOM 生成render树,对于使用过React的同学,可以说是灰常亲切了。在浏览器渲染的流程中,render其实就是DOM和CSSOM的合体(怎么想起来了悟天克斯0.0)
说说浏览器渲染
    在前端渲染,树的遍历都会经历 Bytes、characters 、tokens 、nodes、object model五步转变。首先从Bytes解析字符串,然后进行类似对象转变的过程 生成tokens,然后在一个一个的生成node节点,最终将node节点按从属关系遍历成树,完成遍历
    不过这里有个东西要提一下,就是React中的三目元算符(或者&&)display、visibility、opacity这四种情况,前两种在不显示的时候 是不会丢到模型树里面的(DOM树/CSSOM树/render树),后两种会放到树里面 所以会保留空间,之后在需要的时候进行绘制
说说浏览器渲染

render布局

    这一步又叫Layout布局,就是绘制图层的PX信息,比如基于当前浏览器的可视窗口大小,通过属性计算元素的相对尺寸、相对位置等

render绘制

    客户端将图层数据传给GPU,进行图层绘制,然后显示在浏览器中

小尾巴

  • 在上面的过程中,图片加载、外链、修改样式以及脚本操作DOM都会导致重构模型、生成render树、布局、绘制(图片加载貌似只会应该布局+绘制)
  • 因为布局是在绘制之前的,所有绘制的动作也会引发页面重绘,比如reflow 也就是回流
  • 上面的重绘 即repaint,不会触发布局,但是也会损耗GPU,影响性能。

说说回流和重绘

    上文说到的回流和重绘,前者发生在布局阶段 后者发生才绘制阶段
    回流一般模型树发生了改变,比如html元素的内容、属性变化,就需要重新生成树、布局、绘制。而且一个元素的回流会导致子节点和兄弟节点的回流,进而又是一大波重绘,所以性能消耗要比重绘大
    重绘一般是元素结构没有变化,只是颜色、背景等样式发生了变化,只需要使用新样式重新绘制界面即可。类似于截肢等身体改造要比换个外套要麻烦的多
    对于reflow和repaint,目前已知的优化是,部分浏览器对改变做了量化,只有改变到了一定的数量,才会执行相应的操作

  • reflow:比如发生页面初始化、窗口尺寸改变、padding等会导致render树改变的以及html元素的CRUD等。这里的R是特殊情况,R即read 浏览器在读取某些页面属性的时候会提前触发回流,来防止因为某些元素未回流 最终得到假数据,比如offsetXxx系列、scrollXxx系列,或者脚本使用了getComponentStyle等实时获取元素属性的方法
  • repaint:回流必然会引发重绘,但是如修改背景、字体颜色等操作,只会单独引发重绘

为什么CSS放开头 JS放结尾

    这里的原因,挖到最底层 其实就是css和dom渲染的问题。在说这个之前,我们先嘴把嘴的脑补一个知识点,就是DOM事件,这里回顾两个:DOMContentLoaded + load事件

  • DOMContentLoaded:在得到DOM树之后触发,而不理会外链是否加载完毕,不过该事件支持在页面加载的早期添加事件处理程序,初衷目测就是希望可以和页面更早的交互吧。所以如果js脚本的加载是同步的,则会该钩子应该放在所有脚本最前面,否则如果发生阻塞,会延迟钩子的触发
  • load: onload这个方法只能触发一次,不过JQ的load可以触发多次。他会在页面的所有模型树、图片、外链加载完了才会触发。

    我们要想缩短渲染时间,除去布局和绘制这两步主要依赖计算机性能的环节,我们能做的应该是减少模型树的生成时间(当然 也吃机器性能)。所以这里又可以展开css阻塞渲染和js资源阻塞渲染

  • css阻塞渲染:目的就是尽快的生成CSSOM树,所以最好放在头部,以缩短渲染时间(sass的计算会不会发生阻塞呢?毕竟js单线程)
  • js阻塞渲染:因为js脚本是同步的,即加载完了就会执行,然后才会去继续下面的渲染。所以没有特殊需求,还是把js放在末尾比较好,也可以设置async和defer,后者相当于把js放在了最后面,前者会异步执行 但是不会保证顺序,虽然是在load之前的,但是如果脚本发生阻塞,还是会影响加载速度
    • 上面的js加载完才会执行渲染:因为引擎线程和渲染线程是不同的东西,而且js还是单线程的,所以没法并发执行。
    • 如果有css资源,在CSSOM未构建完之前,也会阻塞js进程
    • 如果页面有脚本,会直接触发js解析动作的执行,暂停并阻塞DOM渲染,等到CSSOM解析之后 才会执行js 然后渲染DIM
    • 执行顺序是请求页面—解析模型 同时加载外链请求—进行blocked操作 按照构建CSSOM、执行js、构建DOM、render的顺序执行,但是只要某一环节断掉了就会从头blocked一次,比如在构建dom的时候 js外链加载好了 触发js解析,bocked操作就会暂停,重新从构建CSSOM开始执行一遍

这些算首屏优化吗

  • 减少reflow 和 repaint的次数
    • 将样式修改集中,减少零散的修改(某些引发回流的读取也一样)
    • 使用DocumentFragment做缓存,然后将修改一次性的append
    • 降低float这类可以让元素脱离文档流的属性的使用
    • 可以设置display或者三目运算符等 在最终完善好元素之后 再显示元素
  • 尽量不要阻塞CSSOM和DOM的构建,比如异步/开头加载js脚本 导致DOM阻塞
  • css文件也要减少外链或者内联,因为开头的required 和 import会阻塞整个页面的渲染(从CSSOM这里开始阻塞)

JQ爱妃 再见

    作为一个搬运工,这些知识当然都来自网络和书本,小弟只是看到、实践并整合了一波。有什么不对的 求喷。
    顺便痛苦的说一句:JQ爱妃 来生再见吧…

相关标签: 性能优化