HTML渲染过程详解
无意中看到寒冬关于前端的九个问题,细细想来我也只是对第一、二、九问有所了解,正好也趁着这个机会梳理一下自己的知识体系。由于本人对http协议以及dns对url的解析问题并不了解,所以这里之探讨url请求加载到浏览器端时,浏览器对html的解析到呈现过程,后来经过几位道友分享,整理了一下url解析的过程,如下:
- 用户输入url地址,浏览器根据域名寻找IP地址
- 浏览器向服务器发送http请求,如果服务器段返回以301之类的重定向,浏览器根据相应头中的location再次发送请求
- 服务器端接受请求,处理请求生成html代码,返回给浏览器,这时的html页面代码可能是经过压缩的
- 浏览器接收服务器响应结果,如果有压缩则首先进行解压处理,紧接着就是页面解析渲染
解析渲染该过程主要分为以下步骤:
- 解析HTML
- 构建DOM树
- DOM树与CSS样式进行附着构造呈现树
- 布局
- 绘制
解析与构建DOM树
前两步我们放在一起讨论,浏览器的实际工作也是将他们放在一起进行的。对于HTML浏览器有专门的html解析器来解析HTML,并在解析的过程中构建DOM树。在这里我们讨论两种DOM元素的解析,即样式(link、style)与脚本文件(script)。由于浏览器采用自上而下的方式解析,在遇到这两种元素时都会阻塞浏览器的解析,直到外部资源加载并解析或执行完毕后才会继续向下解析html。对于样式与脚本的先后顺序同样也会影响到浏览器的解析过程,究其原因主要在于:script脚本执行过程中可能会修改html界面(如document.write函数);DOM节点的CSS样式会影响js的执行结果。在我的测试中得到以下四条结论:
1)外部样式会阻塞后续脚本执行,直到外部样式加载并解析完毕。
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>JS Bin</title>
- <script>var start = +new Date;</script>
- <link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">
- </head>
- <body>
- <span id="result"></span>
- <script>
- var end = +new Date;
- document.getElementById('result').innerHTML = (end-start);
- </script>
- </body>
- </html>
2)外部样式不会阻塞后续外部脚本的加载,但会阻塞外部脚本的执行。
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>JS Bin</title>
- <script>var start = +new Date;</script>
- <link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">
- </head>
- <body>
- test
- <script src="http://udacity-crp.herokuapp.com/time.js?rtt=1&a"></script>
- <div id="result"></div>
- <script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>
- </body>
- </html>
主页代码
- var loadTime = document.createElement('div');
- loadTime.innerText = document.currentScript.src + ' executed @ ' + window.performance.now();
- loadTime.style.color = 'blue';
- document.body.appendChild(loadTime);
外部脚本
从瀑布图中我们可以看到,外部脚本与外部样式是并行加载,但直到外部样式加载完毕,外部脚本才开始执行
3)如果后续外部脚本含有async属性(IE下为defer),则外部样式不会阻塞该脚本的加载与执行
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>JS Bin</title>
- <script>var start = +new Date;</script>
- <link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">
- </head>
- <body>
- test
- <script src="http://udacity-crp.herokuapp.com/time.js?rtt=1&a" async></script>
- <div id="result"></div>
- <script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>
- </body>
- </html>
从瀑布图中可以看到外部脚本的加载与执行并不受link的阻塞
4)对于动态创建的link标签不会阻塞其后动态创建的script的加载与执行,不管script标签是否具有async属性,但对于其他非动态创建的script,以上三条结论仍适用
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>JS Bin</title>
- <script>var start = +new Date;</script>
- </head>
- <body>
- test
- <script>
- var link = document.createElement('link');
- link.href = "http://udacity-crp.herokuapp.com/style.css?rtt=2";
- link.rel = "stylesheet";
- document.head.appendChild(link);
- var script = document.createElement('script');
- script.src = "http://udacity-crp.herokuapp.com/time.js?rtt=1&a";
- document.head.appendChild(script);
- </script>
- <div id="result"></div>
- <script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>
- </body>
- </html>
这是最终页面结构
通过瀑布图与页面结果可以看到动态创建的外部脚本并未受link的阻塞。
link或style标签都会被解析成DOM节点。浏览器对于样式表还会生成CSSStyleSheet对象(C++代码),他集成子CSSStyle,指标是样式表对象而不管该对象来自于Style还是link。该对象主要包含以下几个重要属性和方法
- CSSRules 即css样式代码
- type 表示样式表类型的字符串。对CSS样式表而言,这个字符串是“type/css”。
- href 通过link生成的为样式链接,否则为undefined
- insertRule(rule,index):向cssRules集合中指定的位置插入rule字符串。IE不支持这个方法,但支持一个类似的addRule()方法。
- deleteRule(index):删除cssRules集合中指定的位置的规则。IE不支持这个方法,但支持一个类似的removeRule()方法。
文档中对于所有的样式表集合可以通过document.styleSheets来访问。同时对于style或link DOM元素可以通过element.sheet来访问CSSStyleSheet对象,IE中则通过element.styleSheet来访问。
html解析完毕,DOM树创建完成后DOMContentLoaded事件即触发,这时候可以用过script来操作DOM节点。
构建呈现树
HTML解析完毕后,开始构建呈现树RenderTree,这一步的主要工作在于将css样式应用到DOM节点上,WebKit内核将这一过程称为附着,其他浏览器有不同的概念。对前端工程师而言这个过程会涉及到CSS层叠问题。
首先将根据样式重要性排序,由低到高依次为:
- 浏览器声明
- 用户普通声明
- 作者普通声明
- 作者重要声明
- 用户重要声明
对于同一重要级别,则是根据CSS选择符的特指度来判定优先级;一条样式声明的特指度由以下四个部分决定:S-I-C-E
- 声明来自内联的style属性则 S+1;
- 声明中含有id属性则 I+1;
- 声明中含有类、伪类、属性选择器则 C+1;
- 生命中含有元素、伪元素选择器则 E+1;
特指度的比较类似于两个字符串之间比较大小。
呈现树的每一个节点即为与其相对应的DOM节点的CSS框,框的类型与DOM节点的display属性有关,block元素生成block框,inline元素生成inline框。每一个呈现树节点都有与之相对应的DOM节点,但DOM节点不一定有与之相对应的呈现树节点,比如display属性为none的DOM节点,而且呈现树节点在呈现树中的位置与他们在DOM树中的位置不一定相同,比如float与绝对定位元素。
下图为呈现树与其相对应的DOM树节点
布局
呈现树构造完成后浏览器便进行布局处理,及计算每个呈现树节点的大小和位置信息。有道友可能要问,前面已将样式附着到DOM节点上,不是已经有了样式信息为何还要计算大小。这里可以这样理解,以上包含大小的样式信息只是存在内存里,并没有实际使用,浏览器要根据窗口的实际大小来处理呈现树节点的实际显示大小和位置,比如对于margin为auto的处理。
布局是一个递归过程,从跟呈现节点开始,递归遍历子节点,计算集合几何信息。具体过程还是比较复杂偶也不甚了解,道友们还是查阅其他资料吧。
绘制
布局完成后,便是将呈现树绘制出来显示在屏幕上。对于每一个呈现树节点来说,主要绘制顺序如下:
- 背景颜色
- 背景图片
- 边框
- 子呈现树节点
- 轮廓
参考资料:
- http://velocity.oreilly.com.cn/2010/ppts/limufromTaobao.pdf
- http://lifesinger.wordpress.com/
- http://hikejun.com/blog/2012/02/02/js%E5%92%8Ccss%E7%9A%84%E9%A1%BA%E5%BA%8F%E5%85%B3%E7%B3%BB/
- http://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
- http://www.2cto.com/kf/201406/305852.html
- http://www.w3cmm.com/dom/document-stylesheets-getstylesheet.html
- http://www.cnblogs.com/wenanry/archive/2010/02/25/1673368.html
文章有错误之处欢迎各位道友不吝指正
HTML渲染过程详解的更多相关文章
- vue.js选择if(条件渲染)详解
vue.js选择if(条件渲染)详解 一.总结 一句话总结: v-if <!DOCTYPE html> <html lang="en"> <head& ...
- Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)
View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇 ...
- TortoiseGIT的安装过程详解
TortoiseGIT简介 TortoiseGIT 是Git版本控制系统的一个免费开源客户端,它是git版本控制的 Windows 扩展.可以使你避免使用枯燥而且不方便的命令行.它完全嵌入 Windo ...
- Hadoop MapReduce执行过程详解(带hadoop例子)
https://my.oschina.net/itblog/blog/275294 摘要: 本文通过一个例子,详细介绍Hadoop 的 MapReduce过程. 分析MapReduce执行过程 Map ...
- Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)
启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...
- Linux启动过程详解
Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...
- ping命令执行过程详解
[TOC] ping命令执行过程详解 机器A ping 机器B 同一网段 ping通知系统建立一个固定格式的ICMP请求数据包 ICMP协议打包这个数据包和机器B的IP地址转交给IP协议层(一组后台运 ...
- Cordova 打包 Android release app 过程详解
Cordova 打包 Android release app 过程详解 时间 -- :: SegmentFault 原文 https://segmentfault.com/a/119000000517 ...
- 理论经典:TCP协议的3次握手与4次挥手过程详解
1.前言 尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务.TCP提供一种面向连接的.可靠的字节流服务. 面向连接意味着两个使用TCP的应用(通常是一个客户和一 ...
随机推荐
- 理解CSS边框border
前面的话 边框是CSS盒模型属性中默默无闻的一个普通属性,CSS3的到来,但得边框属性重新焕发了光彩.本文将详细介绍CSS边框 基础样式 边框是一条以空格分隔的集合样式,包括边框粗细(边框宽度 ...
- 深入浅出Redis-redis底层数据结构(上)
1.概述 相信使用过Redis 的各位同学都很清楚,Redis 是一个基于键值对(key-value)的分布式存储系统,与Memcached类似,却优于Memcached的一个高性能的key-valu ...
- C#高性能TCP服务的多种实现方式
哎~~ 想想大部分园友应该对 "高性能" 字样更感兴趣,为了吸引眼球所以标题中一定要突出,其实我更喜欢的标题是<猴赛雷,C#编写TCP服务的花样姿势!>. 本篇文章的主 ...
- .net 分布式架构之配置中心
开源QQ群: .net 开源基础服务 238543768 开源地址: http://git.oschina.net/chejiangyi/Dyd.BaseService.ConfigManager ...
- [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程
怎样使用 async & await 一步步将同步代码转换为异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6079707.html ...
- ubuntu系统下如何修改host
Ubuntu系统的Hosts只需修改/etc/hosts文件,在目录中还有一个hosts.conf文件,刚开始还以为只需要修改这个就可以了,结果发现是需要修改hosts.修改完之后要重启网络.具体过程 ...
- [原创]java使用JDBC向MySQL数据库批次插入10W条数据测试效率
使用JDBC连接MySQL数据库进行数据插入的时候,特别是大批量数据连续插入(100000),如何提高效率呢?在JDBC编程接口中Statement 有两个方法特别值得注意:通过使用addBatch( ...
- JDBC MySQL 多表关联查询查询
public static void main(String[] args) throws Exception{ Class.forName("com.mysql.jdbc.Driver&q ...
- 安卓GreenDao框架一些进阶用法整理
大致分为以下几个方面: 一些查询指令整理 使用SQL语句进行特殊查询 检测表字段是否存在 数据库升级 数据库表字段赋初始值 一.查询指令整理 1.链式执行的指令 return mDaoSession. ...
- ILJMALL project过程中遇到Fragment嵌套问题:IllegalArgumentException: Binary XML file line #23: Duplicate id
出现场景:当点击"分类"再返回"首页"时,发生error退出 BUG描述:Caused by: java.lang.IllegalArgumentExcep ...