浏览器相关知识整理

整理了浏览器相关的一些知识,包括浏览器的组成、渲染引擎渲染机制等

浏览器结构组成

  1. 用户界面(除了页面显示窗口之外的其他部分)
  2. 浏览器引擎(负责通信,用户界面、渲染引擎之间传送指令,缓存中读写数据)
  3. 渲染引擎(解析 HTML 和 CSS 并渲染)
  4. 网络(网络调用或资源下载)
  5. UI 后端(绘制基本的浏览器窗口内控件)
  6. JS解释器(解释、编译、执行JS脚本)
  7. 数据存储(保存 cookie、localStorage 等各种数据)

渲染引擎的工作流程

  1. 构建 DOM 树
  2. 解析 CSS 文件,生成一个具有样式规则描述的 DOM 渲染树
  3. 将渲染树进行布局、绘制
  4. 如渲染完首屏后,对 DOM 进行操作会引起浏览器引擎对 DOM 渲染树的重新布局和重新绘制
    PS:CSS 规则是按照「从右向左」的方式在 DOM 树上进行逆向匹配,是为了节省效率,但是也要尽量避免在选择器末尾添加通配符。

渲染引擎相关的性能优化

  1. 减少 JS 加载对 Dom 渲染的影响
  2. 避免重排,减少重绘(减少使用 width、 margin、 padding 等影响 CSS 布局对规则,可以使用 CSS3 的 transform 代替)
  3. 减少使用关系型样式表的写法(使用唯一的类名、避免在选择器末尾添加通配符)
  4. 减少 DOM 的层级

浏览器的渲染进程

  1. GUI渲染线程(渲染浏览器界面,与JS引擎线程是互斥)
  2. JS引擎线程(处理Javascript脚本程序,与GUI引擎线程是互斥)
  3. 事件触发线程(控制事件循环,待处理队列中的事件都得排队等待JS引擎处理)
  4. 定时触发器线程(setInterval与setTimeout所在线程,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
  5. 异步http请求线程(如有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JS引擎执行)
  6. 浏览器重绘和重排

几种主流内核

  1. Trident(1997,IE,浏览器被弃用)
  2. Gecko(2000,FireFox)、
  3. Webkit(2001,Safari)
  4. Presto(2003,opera前内核、已废弃)
  5. Chromium(2008,Chrome前内核,基于webkit)
  6. blink(2013,Chrome,Google 和 Opera Software 共同研发)
  7. EdgeHTML(2015,Edge已重选Chromium作为内核)

浏览器重绘和重排

文档初次加载时:浏览器引擎将HTML文档解析成对应的DOM树根据DOM元素的几何属性来构建一个用于渲染的渲染树,渲染树的每个节点都包含其大小和内外边距等属性(对于隐藏的不需要显示的元素,不会构建到渲染树当中)。渲染树构建完成后,浏览器就可以将元素放置到正确位置了,再根据渲染树节点的样式属性绘制到页面中。

  1. 重排(reflow) - 节点尺寸位置发生改变,对性能影响更大,会影响到其父元素、子元素和兄弟元素的重排
  2. 重绘(repaint) - 背景颜色图片、外观属性等发生改变

哪些改变能引起重排:

  1. 重新调整浏览器窗口大小
  2. 添加、删除可见的DOM元素
  3. 添加、删除样式表
  4. 激活CSS伪类,如a:hover
  5. 修改字体
  6. 修改class的属性
  7. 设置style的属性
  8. 页面渲染器初始化
  9. 计算offsetWidth和offsetHeight
  10. display: none 隐藏一个 DOM 节点-触发重排和重绘
  11. visibility: hidden 隐藏一个 DOM 节点-只触发重绘,因为没有几何变化

如何避免重排或者减少重排带来的性能问题:

  1. 减少使用 width、 margin、 padding 等影响 CSS 布局对规则,可以使用 CSS3 的 transform 代替
  2. 避免在内联样式中设置多重属性
  3. 将动画应用在 absolute 定位或者 fixed 的元素上
  4. 减少 table 布局
  5. 批量修改 DOM
  6. DOM离线化,即给元素设置display:none后进行操作不会频繁触发重排和重绘,只会在元素添加到元素中的时候触发一次
  7. 缓存布局信息(将某个值缓存下来,避免重复读取)

浏览器的内存分配和垃圾回收

HTTP 头信息控制缓存(强缓存、协商缓存)

客户端请求资源先检查是否命中强缓存

  1. 强缓存通过设置两种 HTTP Header 实现: ExpiresCache-Control(服务器返回的)
  2. Expires(HTTP 1.0)
  • 设置的是具体的过期日期
  • 告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求
  • 弊端:受限于本地时间,如果修改了本地时间,可能会造成缓存失效
  1. Cache-Control:max-age=xxx(HTTP 1.1)
  • 使用相对时间,指定缓存有效时长秒数,在max-age这段时间里浏览器就不会再向服务器发送请求了
  • Cache-Control的其他参数:no-store禁止缓存任何资源,每次都需要向服务器请求完整资源;no-cache客户端缓存内容,但每次使用都需要和服务器协商;private只有客户端可以缓存;public客户端和代理服务器都可缓存
  1. 优先级上:两者同时使用时 Cache-Control会覆盖 Expires
  2. 命中强缓存时返回 200 from cache,直接从本地获取缓存资源,不会发请求到服务器

强缓存没有命中时,客户端携带缓存标识发请求到服务器验证是否命中协商缓存

  1. 协商缓存通过设置两种 HTTP Header 实现: Last-ModifiedETag(服务器返回的)
  2. Last-Modified(HTTP 1.0)
  • 值是资源在服务器上的最后修改时间
  • 请求资源时,浏览器将上一次返回Last-Modified的值复制一份放到请求头中的If-Modified-Since中,服务器收到后,把这个值和最后修改时间做对比,如没有变化则命中缓存返回 304
  • 弊端:本地打开缓存文件也会导致Last-Modified被修改;Last-Modified以秒计时,如果在不可感知的时间内修改文件,服务器会认为缓存时命中的
  1. ETag(HTTP 1.1)
  • 服务器生成的资源文件的一个唯一标识,只要资源有变化,Etag 就会重新生成
  • 请求资源时,浏览器将上一次返回ETag的值复制一份放到请求头中的If-None-Match中,服务器收到后,把这个值和最后修改时间做对比,如没有变化则命中缓存返回 304
  1. 优先级上:两者同时使用时服务器会优先考虑 ETag;性能上,Etag要逊于Last-Modified;精确度上,Etag要优于Last-Modifie
  2. 命中协商缓存时服务器返回 304 not modified,但不返回资源,告诉客户端直接从缓存中获取;协商缓存失效服务器会返回 200 和请求结果;如果资源被删除,服务器返回 404

PS:当ctrl+f5强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;但是当f5刷新网页时,会跳过强缓存,但是会检查协商缓存

谷歌浏览器跨域

浏览器图标右键–属性–在目标后面加上--disable-web-security --user-data-dir=C:\MyChromeDevUserData
注意:最开始有一个空格

谷歌浏览器中快速截图

  1. 打开开发者工具
  2. ctrl + shift + p
  3. 输入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()