zoukankan      html  css  js  c++  java
  • 小谢第67问:用户输入一个URL到页面渲染完成具体发生了什么?

    一、前言

    当用户输入一个URL到页面渲染完成具体发生了什么?

    先回顾一下之前写的一篇文章输入url到页面渲染全链路分析,主要分析了浏览器从加载ULR到服务器返回资源的过程,如果不太了解可以看看这篇(又增加了更详细的内容)

    当浏览器发出请求,服务器返回对应的资源后,浏览器又做了哪些工作将字节流转化成漂亮的页面?

    针对这个问题,需要对浏览器的工作原理进行深入的研究,才能清楚的知道浏览器在这个过程中,做了哪些操作,就能帮助我们在前端开发或前端性能优化的时候,针对某个环节采取不同的方法进行优化,而不是只会背诵减少http请求,合并背景图等。

    上篇文章我们知道了,浏览器会通过网络进程请求网络资源,然后网络进程和渲染进程建立"管道",放入消息队列等待渲染线程进行处理。

    二、渲染进程下的各个线程

    现代浏览器是多进程架构,其中进程中又包含了多个线程,而处理html解析和页面渲染主要就发生在渲染进程中,就是我们常说的浏览器内核,渲染进程主要包括了GUI渲染线程、js引擎线程、事件触发线程、定时触发线程、合成线程、IO线程等

    image.png

    1、渲染线程

    渲染线程主要负责页面的渲染工作,解析html、css、构建布局树、绘制图层等操作

    2、JS引擎线程

    JS引擎线程是浏览器用来执行js的解释器,常见的一种实现方式就是V8引擎。

    JS引擎线程和渲染线程互斥,即同时只能有一个线程在执行。

    这是因为JS可以对DOM节点进行增删改,所以如果在渲染的过程中,JS同时修改了DOM,就会不断的重复渲染,

    所以JS引擎在设计之初就设计了两者互斥,当JS引擎工作的时候,渲染线程就挂起等待,

    这就导致了我们经常遇到的一个问题,当js执行时间过长会造成页面卡顿。

    3、事件触发线程

    我们知道JS是靠事件驱动的语言,它是单线程,异步执行的。主要处理浏览器的各种事件,比如点击事件,移动事件,然后将事件放入任务队列末尾等待JS引擎执行。

    4、定时触发线程

    主要负责处理JS中的定时器,因为JS引擎是单线程的,可能存在阻塞的情况,所以再开一个线程负责可以保证定时器的准确性。

    当然我们通常通过回调函数执行定时的事件,而回调事件触发后会被加入任务队列末尾等待执行,所以如果JS引擎阻塞后,具体的执行时间还是会有误差。

    5、异步请求线程

    当有XMLHttpRequest请求时,会新开线程发出请求,等返回状态变更时,会触发回调事件,将事件放入任务队列等待JS引擎执行。

    当渲染线程拿到返回的资源,会发生如下整个过程,下面我们来一一说明,具体的渲染流程图 image.png

    • DOM解析
      • 字节流词牌解析
      • 转化Token
      • DOM树构建
    • CSSOM解析
      • CSS令牌
      • CSS格式化
      • CSSOM构建
    • 布局树
      • DOM和CSSOM计算布局树
      • 计算DOM节点的坐标,构建布局树
    • 图层树
      • 根据布局树构建图层树
    • 绘制列表、合成
      • 渲染引擎根据图层树,生成绘制列表,交给合成线程
      • 合成线程将绘制列表生成图块
    • 光栅化
      • 进行光栅化操作,将图块合成位图,放入光栅化线程池
      • 生成位图,优先生成视口附近的位图
    • 显示
      • 然后交给浏览器进程,通过浏览器组件绘制成图片到内存,显示出来

    三、构建DOM树

    image.png

    这里通过浏览器开发者工具中的Performance面板录制了页面加载过程中,浏览器执行的任务,可以清晰的看到有个解析HTML的任务,事件的加载和JS先解析再编译等。

    其实后面有很多任务,但截图太多没法显示信息,就截图了一部分,可以自行测试。

    image.png

    这一步渲染线程获取资源后会获取返回头信息,如果头部信息存在标识content_type: html,网络进程会和渲染线程建立通道,就像流水线一样,渲染引擎的HTML模块解析器,首先通过词解析,生成对应的Token,就是标记<StartTag><EndTag>,然后压入它维护的栈中,同时生成对应的Node节点,通过不断的对比开始节点和结束节点,最终构建出DOM树。

    在词解析的过程中,如果发现了style和script的引用文件,浏览器另开启线程进行预下载。当DOM树构建完成,开始解析预下载的CSS。

    在解析DOM的过程中,如果有JS脚本,渲染线程会停止工作,等待JS引擎的执行,所以说在文档头部引用JS会导致页面渲染卡顿。

    image.png

    上面这个是打印出来的DOM结构,这个结构给我们通过JS操作DOM提供了可能。

    四、构建styleSheet样式表

    这一步主要发生了:CSS解析、CSS标准化、styleSheets构建(CSSOM)

    CSS主要有四个来源:

    • 1、内联样式
    • 2、style标签嵌入
    • 3、link引入
    • 4、js引入

    在解析DOM的过程中,不管遇到哪种样式,渲染引擎都会在将CSS标准化完成后,加入到如下的StyleSheetList表中,这样就为后面我们操作CSS提供了便利,在控制台Console中可以打印出来document.styleSheets

    image.png

    其中CSS标准化就是将一些浏览器不能识别的语法标准化成可以识别的。比如

    .demo {
        font-size: 2em;
        color: #000;
    }
    复制代码

    转化成

    .demo {
        font-size: 28px;
        color: rgb(0, 0, 0);
    }
    复制代码

    五、布局树,计算样式和Node节点坐标

    有了DOM树和StyleSheetss,就可以开始构建布局树了。具体就是遍历DOM节点,为每个节点计算样式,过程中隐藏的和不可见的元素是不会加入布局树的,这个过程包括:计算布局树和渲染布局树。

    image.png

    我们可以在Elements这里看到每个节点计算的样式。

    这个过程中,我们发现构建布局树需要DOM和CSS样式表(这里可以理解成CSSOM)。

    如果CSS下载时间过长,导致CSSOM没有下载或解析完成,渲染线程就会停下来等待CSS处理。

    所以如果CSS文件比较大或者网络差,就会导致页面最终的渲染时间增加。

    如果在加载DOM的过程中执行了JS代码,JS中又包含CSS,那么渲染线程也会等待CSS下载,这样也会影响渲染的时间。

    六、图层树

    构建完成了布局树,此时还不会进行绘制,渲染引擎会通过布局树构建图层树。

    就像PS一样,每张图片都是由若干个图层覆盖,最后显示出来一副图片,图层树就是这样,将页面处理成为一个一个层级,每个节点都有所在的层级,如果没有就归属于父节点。

    如图所示,在浏览器layers栏可以看到浏览器分层的效果

    image.png

    点击下面的绘制列表,拖动绘制步骤就可以重现绘制过程

    常见的可以引起分层的样式有z-index,DIV内容大于宽度出现的裁剪或出现滚动条,Fix,3D渲染

    七、合成绘制列表、光栅化操作

    构建完成图层树,渲染引擎会将图层转化成绘制列表,如上图所示,这个列表只有绘制指令。

    接着渲染引擎会将绘制列表交给合成线程,合成线程会将绘制列表绘制成图块,然后执行光栅化操作,就是大页面分割成256*256512*512的大小,然后生成位图,优先渲染页面的可视区域,这也是浏览器在渲染上做的优化。

    渲染引擎会会维护一个光栅化线程池,一旦光栅化完成,就会将绘制指令交给浏览器进程。如果这一步操作使用GUI加速,那么后续操作也会在GUI进程中操作,这样就不会影响渲染进程,提高了绘制的速度。

    八、绘制

    合成线程将绘制位图的指令提交给浏览器进程后,浏览器进程会调用它的viz 的组件根据绘制指令将他们绘制到内存中,然后在页面显示出来。

    这样整个过程就完成了。

    九、重排和重绘

    重排和重绘是面试中经常问到的一个知识点,如果开发中做动画比较多,那了解这方面的内容可以优化动画效果。

    重排,就是当页面元素的几何属性改变时,会导致页面重新计算DOM树,然后引发后续的一系列操作。

    重绘,就是当页面的颜色等属性变化,只会重新计算样式,然后执行合成操作,省略了DOM树计算的过程,就减少了渲染引擎的压力。

    当然,如果你的修改即没有触发重排也没有触发重绘,那不就更快了吗?

    对,所以CSS动画实现可以使用transform,只会在合成线程阶段处理,如果使用了GUI加速,也不会影响主进程,渲染就更快了。

    下面是一些减少重排重绘的方法:

    • 使用class批量处理样式,而不是频繁操作style
    • 避免使用table布局
    • 使用框架,框架使用虚拟DOM,通过算法减少操作DOM的频率
    • 使用transform处理动画等
    既然许愿了,就努力去实现
  • 相关阅读:
    [php learn] php 从头開始学习1
    Qt实现Windows远程控制
    [core java学习笔记][第十一章异常断言日志调试]
    网络安全-安全散列函数,信息摘要SHA-1,MD5原理
    Cardboard虚拟现实开发初步(二)
    C#中Stack&lt;T&gt;类的使用及部分成员函数的源代码分析
    编程算法
    读取spring配置文件的方法(spring读取资源文件)
    Spring中的事务管理详解
    配置spring事务管理的几种方式(声明式事务)
  • 原文地址:https://www.cnblogs.com/xieoxie3000question/p/15020240.html
Copyright © 2011-2022 走看看