说说浏览器渲染

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34009967/article/details/80628748

传说中的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>

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

CSS解析

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

合并CSSOM和DOM

    合并CSSOM和DOM 生成render树,对于使用过React的同学,可以说是灰常亲切了。在浏览器渲染的流程中,render其实就是DOM和CSSOM的合体(怎么想起来了悟天克斯0.0)
render树
    在前端渲染,树的遍历都会经历 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开始执行一遍

这些算首屏优化吗

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

JQ爱妃 再见

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

猜你喜欢

转载自blog.csdn.net/qq_34009967/article/details/80628748