html页面的渲染过程_网站渲染-程序员宅基地

技术标签: web  

最近在学习前端的性能优化,有必要了解一下页面的渲染流程,以便对症下药,找出性能的瓶颈所在。以下是我看到的一些东西,分享给大家。

参考:Understanding the renderer

页面的渲染有以下特点:

  • 单线程事件轮询
  • 定义明确、连续、操作有序(HTML5)
  • 分词和构建DOM树
  • 请求资源并预加载
  • 构建渲染树并绘制页面

具体来说:

当我们从网络上得到HTML的相应字节时,DOM树就开始构建了。由浏览器更新UI的线程负责。当遇到以下情况时,DOM树的构建会被阻塞:

  • HTML的响应流被阻塞在了网络中
  • 有未加载完的脚本
  • 遇到了script节点,但是此时还有未加载完的样式文件

渲染树构建自DOM树,并且会被样式文件阻塞。

由于是基于单线程的事件轮询,即使没有脚本和样式的阻塞,当这些脚本或样式被解析、执行并且应用的时候,也会阻塞页面的渲染。

一些不会阻塞页面渲染的情况:

  • 定义的defer属性和async属性的
  • 没有匹配的媒体类型的样式文件
  • 没有通过解析器插入script节点或样式节点

下面,通过一个例子来说明一下(完整的代码):

复制代码
 1 <html>
 2 <body>
 3   <link rel="stylesheet" href="example.css">
 4   <div>Hi there!</div>
 5   <script>
 6     document.write('<script src="other.js"></scr' + 'ipt>');
 7   </script>
 8   <div>Hi again!</div>
 9   <script src="last.js"></script>
10 </body>
11 </html>
复制代码

代码很容易看明白,如果放在浏览器中打开会立即显示出想要的页面。下面,让我们用慢镜头回放的方式来看看它究竟是怎么渲染的。

1 <html>
2 <body>
3   <link rel="stylesheet" href="example.css">
4    <div>Hi there!</div>
5    <script>...

首先,解析器遇到了example.css,并将它从网络中下载下来。下载样式表的过程是耗时的,但是解析器并没有被阻塞,继续往下解析。接下来,解析器遇到script标签,但是由于样式文件没有加载下来,阻塞了该脚本的执行。解析器被阻塞住,不能继续往下解析。

渲染树也会被样式文件阻塞,所以这时候没有浏览器不会去渲染页面,换句话说,如果example.css文件下载不下来,Hi there! 是显示不出来的。

接下来,继续。。。

复制代码
<html>
<body>
  <link rel="stylesheet" href="example.css">
   <div>Hi there!</div>
   <script>
     document.write('<script src="other.js"></scr' + 'ipt>');
   </script>    
复制代码

一旦example.css文件加载完成,渲染树也就被构建好了。

内联的脚本执行完之后,解析器就会立即被other.js阻塞住。一旦解析器被阻塞,浏览器就会收到绘制请求,"Hi there!"也就显示在了页面上。

当other.js加载完成之后,解析器继续向下解析。。。

复制代码
1 <html>
2 <body>
3     <link rel="stylesheet" href="example.css">
4   <div>Hi there!</div>
5   <script>
6     document.write('<script src="other.js"></scr' + 'ipt>');
7   </script>
8   <div>Hi again!</div>
9   <script src="last.js"></script>
复制代码

解析器遇到last.js之后会被阻塞,然后浏览器收到了另一个绘制请求,"Hi again!"就显示在了页面上。最后last.js会被加载,并且会被执行。

但是,为了减缓渲染被阻塞的情况,现代的浏览器都使用了猜测预加载(speculative loading)。

在上面这种情况下,脚本和样式文件会严重阻塞页面的渲染。猜测预加载的目的就是减少这种阻塞时间。当渲染被阻塞的时候,它会做以下一些事:

  • 轻量级的HTML(或CSS)扫描器(scanner)继续在文档中扫描
  • 查找那些将来可能能够用到的资源文件的url
  • 在渲染器使用它们之前将其下载下来

但是,猜测预加载不能发现通过javascript脚本来加载的资源文件(如,document.write())。

注:所有的“现代”浏览器都支持这种方式。这句话有待商榷,具体请看我下一篇随笔(正在整理中。。。)。

回过来再看上面的例子,通过猜测预加载这种方式是怎么工作的。

1 <html>
2 <body>
3   <link rel="stylesheet" href="example.css">
4   <div>Hi there!</div>
5   <script>...

解析器返现了example.css,并从网络获取,解析器没有被阻塞,继续解析,当遇到了内联的script节点时,被阻塞住,由于样式文件没有加载完成,阻塞了脚本的执行。渲染树同样也被样式文件阻塞住,所以浏览器没有收到渲染请求,看不到任何东西。到目前为止,和刚才提到的那种方式是一样的。但是接下来就有变化了。

预加载器(Speculative loader)继续“阅读”文档,发现了last.js并试图加载它,(注:此时,如果example.css没有加载下来,last.js是不会加载的,一直处于pending状态)。接下来:

复制代码
1 <html>
2 <body>
3   <link rel="stylesheet" href="example.css">
4   <div>Hi there!</div>
5   <script>
6     document.write('<script src="other.js"></scr' + 'ipt>');
7   </script>
复制代码

一旦example.css文件加载完成,渲染树也就完成了构建,内联的脚本也可以执行,之后解析器又被other.js阻塞住。解析器被阻塞住之后,浏览器会收到第一个渲染请求,“Hi there!” 会被现实在页面上。这个步骤和刚才那种情况是一致的。然后:

复制代码
1 <html>
2 <body>
3   <link rel="stylesheet" href="example.css">
4   <div>Hi there!</div>
5   <script>
6     document.write('<script src="other.js"></scr' + 'ipt>');
7   </script>
8   <div>Hi again!</div>
9   <script src="last.js"></script>
复制代码

解析器发现了last.js,但是由于预加载器刚才已经把它给加载下来了,放在了浏览器的缓存里,所以last.js会被立即执行。之后,浏览器会收到渲染请求“Hi again”也被显示在了页面上。

通过前后两种情况的对比,希望大家可以对页面的渲染有一定的了解,然后再有针对性的做一些优化。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/nchu2020/article/details/78570913

智能推荐

深度学习的可解释性!-程序员宅基地

文章浏览阅读1.1w次,点赞23次,收藏139次。一、深度学习的可解释性研究概述随着深度学习模型在人们日常生活中的许多场景下扮演着越来越重要的角色,模型的「可解释性」成为了决定用户是否能够「信任」这些模型的关键因素(尤其是当我们需要机器为..._可解释性

第四章 CSS样式表_css: -webkit-box-reflect给图片设置倒影实例-程序员宅基地

文章浏览阅读1.1k次。样式的分类: 1、行类样式 2、内嵌式样式 3、外部样式选择器的分类 1、标签选择器 2、类选择器 3、ID选择器 4、伪类选择器内嵌式样式 _css: -webkit-box-reflect给图片设置倒影实例

Mysql主从备份数据库服务器搭建_mysql主备搭建-程序员宅基地

文章浏览阅读7.5k次,点赞9次,收藏52次。一,引入mysql主从备份1,为什么要做主从备份防止数据丢失,数据的热备份,架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘 I/O访问的频率,提高单个机器的I/O性能2,什么是mysql主从备份MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。 MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可 以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库_mysql主备搭建

Linux下用命令行编译运行Java总结_用linux编译java文件感悟-程序员宅基地

文章浏览阅读3w次,点赞26次,收藏106次。最近使用腾讯云的Cloud Studio写Java,只能使用命令行进行编译运行,趁此机会,学习一下Linux的一些常用命令。平时windows下IDE用习惯了,现在用命令行进行编译运行,发现其实问题还是挺多的,所以写下这篇文章。1.javac命令行javac用于编译java源文件,生成.class文件。形式如下javac [option] source常用的option..._用linux编译java文件感悟

获得程序的句柄_如何取得程序句柄-程序员宅基地

文章浏览阅读703次。DWORD aProcesses[1024], dwSize, dwSize2; char szProcessName[MAX_PATH] = "unknown"; char MyProcessName[MAX_PATH] = "Test.exe";//用实际文件名代替 unsigned int i; if _如何取得程序句柄

Vue循环指令v-for_<li v-for="book in books">-程序员宅基地

文章浏览阅读566次。基本用法当需要将一个数组遍历,或者将对象循环显示时,就会用到列表渲染指令v-for。它的表达式需结合in来使用,类似item in items的形式,看下面的示例:&amp;lt;div&amp;gt; &amp;lt;ul&amp;gt; &amp;lt;li v-for=&quot;book in books&quot;&amp;gt;{{book.name}}&amp;lt;/li&amp;_

随便推点

Geany文本编辑器_geany编辑器显示方框-程序员宅基地

文章浏览阅读5.3k次,点赞3次,收藏8次。第一步,下载Geany,在百度里搜Geany点开Geany:Home Page第二步,在Download里点Releases.第三步,点windows下的下载程序下载,下载好后设置安装文件夹,然后一路安装。第四步,安装好之后打开Geany,点“生成”设置生成命令。第五步,在设置生成命令里需要Python的安装位置,这时候找到Python,点文件所在位置,找到后右键点“属性”里面有Python的目标..._geany编辑器显示方框

Github Actions 使用Docker部署SpringBoot_docker/setup-qemu-action@v3-程序员宅基地

文章浏览阅读372次。Github Actions 使用Docker部署SpringBoot_docker/setup-qemu-action@v3

YOLOV5-seg中json转txt及划分数据集_seg_json-程序员宅基地

文章浏览阅读1.5k次,点赞4次,收藏26次。yolov5-seg自制数据集的划分_seg_json

C#/.NET 多线程任务Task的详解——应用实例_.net task-程序员宅基地

文章浏览阅读9.9k次,点赞2次,收藏32次。Task类介绍:Task 类的表示单个操作不返回一个值,通常以异步方式执行。 Task 对象是一个的中心思想 基于任务的异步模式 首次引入.NET Framework 4 中。 因为由执行工作 Task 对象通常以异步方式执行在线程池线程上而不是以同步方式在主应用程序线程,可以使用 Status 属性,以及 IsCanceled, ,IsCompleted, ,和 IsFaulted 属性,以确..._.net task

css响应式网页设计:自适应屏幕宽度、移动页面开发技巧_响应式 自适应大小 css-程序员宅基地

文章浏览阅读1.4w次,点赞7次,收藏56次。html响应式网页设计:自动适应屏幕宽度背景移动设备正超过桌面设备,成为访问互联网的最常见终端。于是,网页设计师不得不面对一个难题:如何才能在不同大小的设备上呈现同样的网页?手机的屏幕比较小,宽度通常在600像素以下;PC的屏幕宽度,一般都在1000像素以上(目前主流宽度是1366×768),有的还达到了2000像素。同样的内容,要在大小迥异的屏幕上,都呈现出满意的效果,并不是一件容易的事。..._响应式 自适应大小 css

Pandas基础操作2——DataFrame的基础操作_data=nr-程序员宅基地

文章浏览阅读257次。紧接着上一篇博客,创建了Series跟DataFrame今天学习DataFrame的行列添加和删除操作,以及append和切片操作下面看代码:import numpy as npimport pandas as pdfrom pandas import Series, DataFrame# Seriess = Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])print(s)# >>># a 1# b _data=nr