浏览器的组成
浏览器主要由7个部分组成:用户界面(User Interface)、浏览器引擎(Browser engine)、渲染引擎(Rendering engine)、网络(Networking)、Javascript解释器(JavaScript Interpreter)、用户界面后端(UI Backend)、数据持久化(Data Persistence)。
图中箭头表示调用相关模块的接口关系,箭头指向表示调用该模块。
用户界面:定义了一些常用的浏览器组件,如地址栏、返回、书签等,也就是你所看到的除了页面显示窗口之外的其他部分;
浏览器引擎:平台应用的相关接口,在用户界面和呈现引擎之间传送指令,可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据等,是浏览器中各个部分之间相互通信的核心;
渲染引擎:处理HTML、CSS的解析和渲染,也有人称之为排版引擎,我们常说的浏览器内核主要指的就是渲染引擎
JavaScript解释器:解析和执行JavaScript代码,如V8引擎、JavaScriptCore;
数据持久化/数据存储:指浏览器的cookie、localStorage等组件,可通过浏览器引擎提供的API进行调用;
UI后端:值浏览器的图形库等,用来绘制基本的浏览器窗口内控件,如输入框、按钮、单选按钮等,根据浏览器不同绘制的视觉效果也不同,但功能都是一样的;
网络:用于网络调用或资源下载的模块,比如HTTP请求。
浏览器内核
浏览器内核是指支持浏览器运行的最核心的程序,分为两个部分,一是渲染引擎、另一个是JS引擎。渲染引擎在不同浏览器上也不是都相同的。以下是现在主流浏览器及其渲染引擎。
- Trident(IE浏览器内核)
- Webkit(Safari浏览器内核)
- Blink(Chrome、Opera浏览器内核)
- Gecko(Friefox浏览器内核)
注意:最开始渲染引擎和js引擎并没有区分的很明确,后来js引擎越来越独立,浏览器内核就趋向于指渲染引擎了。
浏览器工作流程
1)浏览器会解析三个东西
- HTML/SVG/XML,解析这三种文件会产生一个DOM Tree;
- CSS,解析CSS会产生CSSOM Tree;
- Javascript,只要是通过DOM API和CSSOM API来操作DOM Tree和CSS Tree.
2)解析完成后,浏览器引擎会通过DOM Tree和CSSOM Tree来构建Render Tree.
- Render Tree(渲染树)并不等同于DOM Tree,因为像标签或display: none的元素不会出现在Render Tree中。
- CSSOM Tree主要是为了完成匹配并把CSS Rule附加上Render Tree上的每个DOM结点上。
- 然后计算每个元素(DOM结点)的位置,这又叫layout和reflow过程。
3)最后通过调用操作系统Native GUI的API绘制(paint/repaint)。
构建DOM Tree
浏览器会遵守一套步骤讲HTML文件转换成DOM树。宏观上,可以分成几个步骤:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
构建CSSOM Tree
与处理HTML时一样,我们需要将收到的CSS规则转换成浏览器能够理解和处理的东西。
在构建CSSOM Tree过程中,浏览器会确定下每个节点(Nodes)d的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归CSSOM树,然后确定具体的元素到底是什么样式。
构建渲染树
当我们生成DOM树和CSSOM树之后,就需要将这两棵树结合为渲染树。
在这一过程中,不是简单的将两者合并就可以了。渲染树只会包括需要显示的节点和这些节点的样式信息(去掉了head和display为none的Node)。
注意:css匹配html元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class。千万不要过度层叠下去。
布局(layout/reflow)和绘制(paint/repaint)
当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情就是弄清楚各个节点在页面上的确切位置和大小。通常这一行为也称为“自动重排”。layout=自动重排;reflow=重排/回流。
布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都转换成屏幕上的绝对像素。
布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。
问题
问题一:页面首次渲染、DOMContentLoaded、Load概念
- 浏览器首先下载该地址对应的html页面;
- 浏览器解析html页面的DOM结构;
- 开启下载线程对文档中所有的资源按优先级排序下载;
- 主线程继续解析文档,到达head节点,head里的外部资源是外链样式表和外链js。
发现有外链css或者外链js,如果是外链js,则停止解析后续内容,等待资源下载,下载完后立即执行js。如果外链css,继续解析后续内容 - 解析到body
body里的情况比较多,body里可能只有DOM元素,可能既有DOM、也有css、js等资源,js资源又有可能异步加载图片、css、js等。DOM结构不同,浏览器的解析机制也不同,所以需要分开来讨论。
1)只有DOM元素
这种情况比较简单,DOM树构建完,页面首次渲染;
2)有DOM元素、外链js
当解析到外链js的时候,该js尚未下载到本地,则js之前的DOM会被渲染到页面上,同时js会阻止后面DOM的构建,即后面的DOM节点并不会添加到文档的DOM树中,所以,js执行完之前,我们在页面上看不到位于该js后面的DOM元素。
3)有DOM元素、外链css
外链css不会影响css后面的DOM构建,但是会阻碍渲染。简单来说,外链css加载完之前,页面还是白屏。
4)有DOM元素、外链js、外链css
外链js和外链css的顺序会影响页面渲染,这点尤其重要。当body中js之前的外链css未加载完之前,页面是不会被渲染的。
当body中js之前的外链css加载完之后,js之前的DOM树和CSSOM树合并成渲染树,页面渲染出该js之前的DOM结构。 - 文档解析完毕,页面重新渲染。当页面引用的所有js同步代码执行完毕,触发DOMContentLoaded事件。
- html文档中的css、js、图片资源及js代码中有异步加载的css、js、图片资源都加载完毕之后,load事件被触发。
对于javascript阻塞,有两种解决办法。defer(延迟执行)和async(异步执行)。这两种方法只适用于引入时的js脚本,不适合inline-script;
async和defer的作用与区别?
上图中蓝色线代表JavaScript加载;红色线代表JavaScript执行;绿色线代表HTML解析。
async属性表示异步执行引入的JavaScript,与defer的区别在于,如果已经加载好,就会开始执行——无论此刻是HTML解析阶段还是DOMContentLoaded触发之后。需要注意的是,这种方式加载的JavaScript依然会阻塞load事件。换句话说。async-script可能在DOMContentLoaded触发之前或之后执行,但一定在load触发之前执行。
defer属性表示延迟执行引入的JavaScript,即这段JavaScript加载时HTML并未停止解析,这两个过程是并行的。整个document解析完毕且defer-script也加载完成之后,会执行JavaScript代码,然后触发DOMContentLoaded事件。
在加载多个js脚本时,async是无顺序的加载,而defer是有顺序的加载。
defer和async推荐的应用场景
- 如果你的脚本代码依赖于页面上的DOM元素,或者被其他脚本文件依赖(有顺序的加载),用defer;
- 如果你的脚本并不关心页面中的DOM元素,并且也不会产生其他脚本需要的数据,用async.
问题二:你真的了解回流和重绘吗?
回流:当渲染树(render tree)节点发生改变,影响了节点的几何属性(如宽高、内边距、外边距、或是float、position,display:none等),导致节点位置发生改变。此时触发浏览器重排,需要重新生成渲染树。
重绘:当渲染树(render tree)节点发生改变,但不影响该节点在页面当中的空间位置及大小(如color、background-color等)。
回流必定会发生重绘,但重绘不一定引起回流。回流所需的成本比重绘高的多,改变父节点的子节点很可能导致父节点的一系列回流。
1)何时会引起重排?
- 添加或删除可见的DOM元素;
- 元素位置改变——display、float、position、visible等等;
- 元素尺寸改变——宽度、高度、边距、填充、边框
- 内容改变——比如文本改变或者图片大小改变而引起的计算值(宽度和高度)的改变;
- 浏览器窗口尺寸改变——resize事件触发时。
2)常见引起重绘属性和方法
3)如何减少回流、重绘
使用transform替代top、left来显示动画;
多个DOM统一操作;
先将要操作的元素进行“离线处理”,处理完后一起更新;- 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
- 使用cloneNode和replaceChild,引发一次回流和重绘
- 使用display: none; 先隐藏元素,处理完之后再显示;只引发两次回流和重绘;
问题三:为什么脚本操作DOM慢
- 因为DOM术语渲染引擎的东西,而JS又是JS引擎的东西。当我们通过js操作DOM时,其实这个操作涉及到了两个线程之间的通信;
- 操作DOM可能会带来重绘回流
问题四: 渲染页面时常见哪些不良现象?
1. 白屏问题
如果将CSS放在HTML尾部或js文件放在头部,因为css或js阻塞问题而出现白屏。
2. FOUC
由于Firefox浏览器渲染机制,在CSS加载之前,先呈现HTML,就会才导致展示出无样式内容,然后样式突然呈现的现象。
参考链接: https://my.oschina.net/u/3375885/blog/2996380