整理了浏览器相关的一些知识,包括浏览器的组成、渲染引擎渲染机制等
浏览器结构组成
- 用户界面(除了页面显示窗口之外的其他部分)
- 浏览器引擎(负责通信,用户界面、渲染引擎之间传送指令,缓存中读写数据)
- 渲染引擎(解析 HTML 和 CSS 并渲染)
- 网络(网络调用或资源下载)
- UI 后端(绘制基本的浏览器窗口内控件)
- JS解释器(解释、编译、执行JS脚本)
- 数据存储(保存 cookie、localStorage 等各种数据)
渲染引擎的工作流程
- 构建 DOM 树
- 解析 CSS 文件,生成一个具有样式规则描述的 DOM 渲染树
- 将渲染树进行布局、绘制
- 如渲染完首屏后,对 DOM 进行操作会引起浏览器引擎对 DOM 渲染树的重新布局和重新绘制
PS:CSS 规则是按照「从右向左」的方式在 DOM 树上进行逆向匹配,是为了节省效率,但是也要尽量避免在选择器末尾添加通配符。
渲染引擎相关的性能优化
- 减少 JS 加载对 Dom 渲染的影响
- 避免重排,减少重绘(减少使用 width、 margin、 padding 等影响 CSS 布局对规则,可以使用 CSS3 的 transform 代替)
- 减少使用关系型样式表的写法(使用唯一的类名、避免在选择器末尾添加通配符)
- 减少 DOM 的层级
浏览器的渲染进程
- GUI渲染线程(渲染浏览器界面,与JS引擎线程是互斥)
- JS引擎线程(处理Javascript脚本程序,与GUI引擎线程是互斥)
- 事件触发线程(控制事件循环,待处理队列中的事件都得排队等待JS引擎处理)
- 定时触发器线程(setInterval与setTimeout所在线程,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
- 异步http请求线程(如有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JS引擎执行)
- 浏览器重绘和重排
几种主流内核
- Trident(1997,IE,浏览器被弃用)
- Gecko(2000,FireFox)、
- Webkit(2001,Safari)
- Presto(2003,opera前内核、已废弃)
- Chromium(2008,Chrome前内核,基于webkit)
- blink(2013,Chrome,Google 和 Opera Software 共同研发)
- EdgeHTML(2015,Edge已重选Chromium作为内核)
浏览器重绘和重排
文档初次加载时:浏览器引擎将HTML文档解析成对应的DOM树根据DOM元素的几何属性来构建一个用于渲染的渲染树,渲染树的每个节点都包含其大小和内外边距等属性(对于隐藏的不需要显示的元素,不会构建到渲染树当中)。渲染树构建完成后,浏览器就可以将元素放置到正确位置了,再根据渲染树节点的样式属性绘制到页面中。
- 重排(reflow) - 节点尺寸位置发生改变,对性能影响更大,会影响到其父元素、子元素和兄弟元素的重排
- 重绘(repaint) - 背景颜色图片、外观属性等发生改变
哪些改变能引起重排:
- 重新调整浏览器窗口大小
- 添加、删除可见的DOM元素
- 添加、删除样式表
- 激活CSS伪类,如a:hover
- 修改字体
- 修改class的属性
- 设置style的属性
- 页面渲染器初始化
- 计算offsetWidth和offsetHeight
- display: none 隐藏一个 DOM 节点-触发重排和重绘
- visibility: hidden 隐藏一个 DOM 节点-只触发重绘,因为没有几何变化
如何避免重排或者减少重排带来的性能问题:
- 减少使用 width、 margin、 padding 等影响 CSS 布局对规则,可以使用 CSS3 的 transform 代替
- 避免在内联样式中设置多重属性
- 将动画应用在 absolute 定位或者 fixed 的元素上
- 减少 table 布局
- 批量修改 DOM
- DOM离线化,即给元素设置display:none后进行操作不会频繁触发重排和重绘,只会在元素添加到元素中的时候触发一次
- 缓存布局信息(将某个值缓存下来,避免重复读取)
浏览器的内存分配和垃圾回收
HTTP 头信息控制缓存(强缓存、协商缓存)
客户端请求资源先检查是否命中强缓存
- 强缓存通过设置两种
HTTP Header
实现: Expires 和 Cache-Control(服务器返回的) - Expires(HTTP 1.0)
- 设置的是具体的过期日期
- 告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求
- 弊端:受限于本地时间,如果修改了本地时间,可能会造成缓存失效
- Cache-Control:max-age=xxx(HTTP 1.1)
- 使用相对时间,指定缓存有效时长秒数,在
max-age
这段时间里浏览器就不会再向服务器发送请求了 Cache-Control
的其他参数:no-store
禁止缓存任何资源,每次都需要向服务器请求完整资源;no-cache
客户端缓存内容,但每次使用都需要和服务器协商;private
只有客户端可以缓存;public
客户端和代理服务器都可缓存
- 优先级上:两者同时使用时
Cache-Control
会覆盖Expires
- 命中强缓存时返回
200 from cache
,直接从本地获取缓存资源,不会发请求到服务器
强缓存没有命中时,客户端携带缓存标识发请求到服务器验证是否命中协商缓存
- 协商缓存通过设置两种
HTTP Header
实现: Last-Modified 和 ETag(服务器返回的) - Last-Modified(HTTP 1.0)
- 值是资源在服务器上的最后修改时间
- 请求资源时,浏览器将上一次返回
Last-Modified
的值复制一份放到请求头中的If-Modified-Since
中,服务器收到后,把这个值和最后修改时间做对比,如没有变化则命中缓存返回 304 - 弊端:本地打开缓存文件也会导致
Last-Modified
被修改;Last-Modified
以秒计时,如果在不可感知的时间内修改文件,服务器会认为缓存时命中的
- ETag(HTTP 1.1)
- 服务器生成的资源文件的一个唯一标识,只要资源有变化,Etag 就会重新生成
- 请求资源时,浏览器将上一次返回
ETag
的值复制一份放到请求头中的If-None-Match
中,服务器收到后,把这个值和最后修改时间做对比,如没有变化则命中缓存返回 304
- 优先级上:两者同时使用时服务器会优先考虑
ETag
;性能上,Etag
要逊于Last-Modified
;精确度上,Etag
要优于Last-Modifie
- 命中协商缓存时服务器返回
304 not modified
,但不返回资源,告诉客户端直接从缓存中获取;协商缓存失效服务器会返回 200 和请求结果;如果资源被删除,服务器返回 404
PS:当ctrl+f5
强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;但是当f5
刷新网页时,会跳过强缓存,但是会检查协商缓存
谷歌浏览器跨域
浏览器图标右键–属性–在目标后面加上--disable-web-security --user-data-dir=C:\MyChromeDevUserData
注意:最开始有一个空格
谷歌浏览器中快速截图
- 打开开发者工具
ctrl + shift + p
- 输入
full
获取性能数据
参考文章:
初探 performance – 监控网页与程序性能
浅谈小程序运行机制1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 计算加载时间 1000毫秒 == 1秒
function getPerformanceTiming() {
var performance = window.performance
if (!performance) {
console.log('你的浏览器不支持 performance 接口')
return
}
var t = performance.timing
var times = {}
times.loadPage = t.loadEventEnd - t.navigationStart // 页面加载完成的时间
times.domReady = t.domComplete - t.responseEnd // 解析 DOM 树结构的时间
times.redirect = t.redirectEnd - t.redirectStart // 重定向的时间
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart // DNS 查询时间
times.ttfb = t.responseStart - t.navigationStart // 读取页面第一个字节的时间
times.request = t.responseEnd - t.requestStart // 内容加载完成的时间
times.loadEvent = t.loadEventEnd - t.loadEventStart // 执行 onload 回调函数的时间
times.appcache = t.domainLookupStart - t.fetchStart // DNS 缓存时间
times.unloadEvent = t.unloadEventEnd - t.unloadEventStart // 卸载页面的时间
times.connect = t.connectEnd - t.connectStart // TCP 建立连接完成握手的时间
return times
}
getPerformanceTiming()