浏览器的渲染机制

输入url发生了什么

  • 浏览器查找当前URL是否存在缓存,并比较缓存是否过期

    浏览器会先根据这个URL查看浏览器缓存-系统缓存-路由器缓存,若缓存中有,直接Http连接

  • DNS解析域名对应的IP地址
  • 建立TCP连接(三次握手)
  • 发送HTTP请求
  • 服务器处理请求,并返回HTTP报文
  • 浏览器解析渲染页面
  • 关闭TCP连接(四次挥手)

浏览器渲染

  1. 解析HTML
  2. 解析CSS
  3. 构建render tree
  4. layout 根据渲染树来布局,计算每个节点的位置,定位坐标和大小
  5. paint:调用 GPU 绘制,合成图层,显示在屏幕上

页面加载完成有两种事件

  1. ready,表示文档结构已经加载完成(不包含图片等非文字媒体文件)
  2. onload,指示页面包含图片等文件在内的所有元素都加载完成

具体过程

  1. 首先浏览器从服务器接收到html代码,自上而下解析html,构建DOM树,并且在同时构建渲染树。DOM树的构建过程是一个深度遍历过程:当前结点的所有子节点都构建好后才会去构建当结点的下一个兄弟结点
  2. 将css解析成css去构造cssom树(css对象模型)
  3. 根据DOM树和cssom来构造渲染render树。渲染树并不等同于DOM树,因此一些像Header或者display:none的东西就不会出现在渲染树中。
  4. 根据render树进行布局,计算每个节点在屏幕中的大小和位置
  5. 遍历render树进行绘制,并使用浏览器UI后端层绘制每个节点
  • JS在下载时会阻塞一切行为(资源下载,内容呈现),JS在加载且执行完之前不会往下解析DOM树
  • CSS并行下载,不会阻塞DOM树的解析,CSS加载会阻塞DOM树的渲染
    遇到js文件加载执行,将阻塞DOM树的构建;遇到css文件,将阻塞渲染树的构建
1
2
3
- 浏览器遇到script文件和css文件都会另起线程去下载
- 内嵌的script脚本、外链的script脚本下载都会阻塞构建DOM树
- css文件执行和内嵌的style标签阻塞css对象模型

在构建 CSSOM 树时,会阻塞渲染,直至 CSSOM 树构建完成。并且构建 CSSOM 树是一个十分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS 选择器,执行速度越慢。

当 HTML 解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。并且 CSS 也会影响 JS 的执行,只有当解析完样式表才会执行 JS,所以也可以认为这种情况下,CSS 也会暂停构建 DOM。

JS和CSS的放置位置

  1. JS放在body底部
  2. CSS放在head中
1
2
3
4
5
6
1、把js放在body之后,是为了预防外部js文件过多,浏览器呈现页面出现延迟,延迟期间浏览器的窗口一片空白
3、js脚本放在head和body有什么区别
head中的脚本会在页面加载之前解析,可以保证脚本在任何调用之前被加载
body中的脚本会在页面加载完成之后读取,放在body部分的脚本通常被用来生成页面的内容。因为加载js脚本会阻塞页面的加载,为了用户体验也为了脚本可以正常操作DOM,一般放在body中
浏览器解析html是从上到下的
如果把js放在head里的话,则先被解析,但这时候body还没有解析,所以会返回空值。一般都会绑定一个监听,当全部的html文档解析完之后,在执行代码:windows.onload

Load 和 DOMContentLoaded 区别

Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。

DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。

图层

一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用。

通过以下几个常用属性可以生成新图层

3D 变换:translate3d、translateZ
will-change
video、iframe 标签
通过动画实现的 opacity 动画转换
position: fixed

白屏和FOUC(闪屏)

  1. 浏览器的解析方式的不同:
  • Chrome和Safari

    当发现 后立即停止渲染,在所有css加载完成之前页面上不会有任何内容

  • Firefox

    放在head与Chrome和Safari中完全一致,这些link标签全部加载完之前,页面上不显示任何内容,而中的内容则不阻塞任何内容显示,也就是说,放内,先渲染没有样式的,再渲染有样式的。

  • IE/Edge

    未加载完成的只阻塞其后面HTML内容显示,而对其前面的内容不影响

  1. 白屏:
  • chrome采用的是等CSS全部加载解析完后再渲染展示页面,CSS样式被置于底部(最后加载)
  • 使用 @import 标签,它引用的文件则会等页面全部下载完毕再被加载
  • 当JavaScript被置于顶部时,阻止其后的文件的加载及组件的下载
  1. 闪屏(FOUC 文档无样式闪烁)
  • 火狐将css放在body底部,html会先呈现出来,待到html加载完毕,再一次性加载CSS样式使得样式突然呈现

FOUC 无样式内容闪烁

若使用@import对css样式表进行导入,会导致页面在window下的IE出现奇怪现象,无样式显示页面内容的瞬间闪烁

可能导致FOUC的原因

  1. 使用@import引入样式表
  2. 将样式表放在页面底部
  3. 有几个样式表放在放在html结构不同位置

    样式表晚于结构性html加载,当加载到样式表时,页面停止之前的渲染,等样式表被下载并解析之后,将重新渲染页面。

script标签的async和defer

  • 什么都不加:读到就加载,并执行指定脚本

async 和 defer 的作用与区别

  • 作用:可以让后续文档元素和js加载并行加载(异步)
  • 区别:差别在于JS脚本下载完成后何时执行
  1. async;加载JS脚本与解析html并行(异步),加载完脚本立即执行。标记为async的脚本并不保证按照它们的先后顺序执行。
  2. defer:脚本会被延迟到整个页面都解析完毕后再运行,相当于浏览器立即下载但是延迟执行。按照顺序执行

重排(回流 reflow)

某些元素的内容、结构、位置、尺寸发生变化影响了布局,需要重新计算样式和渲染树。

引起重排

  • 网页初始化的时候
  • 一些js在操作DOM树时 (增加、删除、修改DOM结点),获取某些属性时
1
offsetTop scrollTop client getComputedStyle()
  • 元素位置的改变,或者使用动画
  • DOM元素的几何属性变化,如外边距、内边距、边框厚度、宽高、等几何属性)
  • resize窗口的时候(移动端没有这个问题),或是滚动的时候
  • 修改CSS的属性时
  • 填充内容的改变,比如文本字体的改变或图片大小改变而引起的计算值宽度和高度的改变
  • 窗口属性的获取和尺寸改变
  • 激活CSS伪类(例如::hover)

重绘(repaint)

元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就OK了

display:none 会发生重排,visibility会发生重绘

重排一定会发生重绘、重绘不一定发生重排

减少重排重绘的方法

  1. display: none,把 DOM 离线后修改

    由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。

  2. 为动画的DOM使用fixed或absolute的position:不会重排,脱离文档流
  3. 元素位置移动变换时尽量使用transform代替left、top的操作。
  4. 用opacity代替visibility,因为当时单独的图层时,透明度的改变时,GPU在绘画时只是简单的降低之前已经画好的纹理的alpha值来达到效果,不会发生重绘。
  5. 不要把DOM结点的属性值放在一个循环里当成循环里的变量
  6. 不要一条一条地修改DOM的样式。不如预先定义好css的class,修改DOM的className
    (样式集中改变)
  7. 分离读写操作:获取DOM属性(引起重排的属性)的时候先拿一个变量接收(缓存)
  8. 如果需要创建多个DOM节点,可以使用DocumentFragment(文档片段)创建完后一次性的加入document;
  9. 不要使用table布局,可能很小的一个小改动会造成整个 table 的重新布局

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var fragment = document.createDocumentFragment();

    var li = document.createElement('li');
    li.innerHTML = 'apple';
    fragment.appendChild(li);

    var li = document.createElement('li');
    li.innerHTML = 'watermelon';
    fragment.appendChild(li);

    document.getElementById('fruit').appendChild(fragment);
  10. 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)

  11. 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
  12. CSS 选择符从右往左匹配查找,避免 DOM 深度过深
  13. 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video 标签,浏览器会自动将该节点变为图层。

重绘和回流其实和 Event loop 有关

  1. 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
  2. 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
  3. 判断是否触发了 media query
  4. 更新动画并且发送事件
  5. 判断是否有全屏操作事件
  6. 执行 requestAnimationFrame 回调
  7. 执行 IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
  8. 更新界面
  9. 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。

网页优化

1. 减少http请求的次数

在浏览器与服务器进行通信时,主要是通过 HTTP 进行通信。浏览器与服务器需要经过三次握手,每次握手需要花费大量时间。而且不同浏览器对资源文件并发请求数量有限(不同浏览器允许并发数),一旦 HTTP 请求数量达到一定数量,资源请求就存在等待状态,这是很致命的,因此减少 HTTP 的请求数量可以很大程度上对网站性能进行优化。

  • CSS Sprites(CSS精灵/雪碧图):将多张图片合并成一张图片达到减少HTTP请求的一种解决方案,可以通过CSS的background属性来访问图片内容。这种方案同时还可以减少图片总字节数
  • 合并CSS和JS文件:现在前端有很多工程化打包工具,如:grunt、gulp、webpack等。为了减少 HTTP 请求数量,可以通过这些工具再发布前将多个CSS或者多个JS合并成一个文件。
  • 采用lazyload:懒加载,可以控制网页上的内容在一开始无需加载,不需要发请求,等到用户操作真正需要的时候立即加载出内容。这样就控制了网页资源一次性请求数量。

2. 控制资源文件加载优先级

浏览器在加载HTML内容时,是将HTML内容从上至下依次解析,解析到link或者script标签就会加载href或者src对应链接内容,为了第一时间展示页面给用户,就需要将CSS提前加载,不要受 JS 加载影响

一般情况下都是CSS在头部,JS在底部。

3. 利用浏览器缓存

浏览器缓存是将网络资源存储在本地,等待下次请求该资源时,如果资源已经存在就不需要到服务器重新请求该资源,直接在本地读取该资源。

4. 减少重排、重绘

基本原理:重排是DOM的变化影响到了元素的几何属性(宽和高),浏览器会重新计算元素的几何属性,会使渲染树中受到影响的部分失效,浏览器会验证 DOM 树上的所有其它结点的visibility属性,这也是Reflow低效的原因。如果Reflow的过于频繁,CPU使用率就会急剧上升

减少Reflow,如果需要在DOM操作时添加样式,尽量使用 增加class属性,而不是通过style操作样式。

启用GPU(图像处理器)硬件加速

GPU(Graphics Processing Unit) 是图像处理器。GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。
GPU 加速可以不仅应用于3D,而且也可以应用于2D。这里, GPU 加速通常包括以下几个部分:Canvas2D,布局合成(Layout Compositing), CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。

脚本执行阶段

  • 尽量减少节点查找
  • 事件委托来减少事件处理器的数量
  • 用querySelectorAll代替getElementBy
  • 节流、防抖
  • DNS解析优化
  • 避免重定向
  • cookie优化
  • 文件压缩
  • 图标使用IconFont替换
  • 启用GZIP压缩,浏览速度变快,搜索引擎的蜘蛛抓取信息量也会增大
  • 使用CDN网络缓存,加快用户访问速度,减轻服务器压力
  • 不使用CSS表达式,会影响效率
  • 伪静态设置
    如果是动态网页,可以开启伪静态功能,让蜘蛛“误以为”这是静态网页,因为静态网页比较合蜘蛛的胃口,如果url中带有关键词效果更好。
  • SEO搜索引擎优化