浏览器知识零散学习(面试篇)
浏览器渲染过程
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树。
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)。
- Layout(回流):根据生成的渲染树,进行回流,得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上
生成渲染树
- 从DOM树的根节点开始遍历每个可见节点
- 对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。
- 根据每个可见节点以及其对应样式,组合生成渲染树。
注意:渲染树只包含可见的节点。
不可见节点:不会渲染输出的节点,比如script
meta
link
等。还有通过css隐藏的节点比如display:none
回流(reflow)
通过构造渲染树,将可见DOM节点以及它对应的样式结合起来,然后计算节点在设备视口的确切位置和大小。这个计算阶段就是回流。
重绘(repaint)
通过构造渲染树和回流阶段,已知可见节点的样式和具体的几何信息(位置,大小),将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点。
何时发生回流重绘
回流阶段主要计算节点位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流。比如以下情况:
- 添加/删除可见DOM元素
- 元素位置发生变化
- 元素尺寸发生了变化(边距、边框、宽高等)
- 内容发生变化,比如文本变化,图片变化
- 浏览器尺寸发生变化。(回流是根据视口的大小来计算元素的位置和大小的)
注意:回流一定会触发重绘,重绘不一定触发回流。
如何减少回流和重绘?
- 最小化回流和重绘
合并多次对DOM的修改
比如:
let el = document.getElementById("root")
el.width = "100px";
el.height = "100px";
el.padding = "10px";
以上代码对于元素进行了多次修改,理论上每一次修改都会引起一次回流,以上修改了三次因此三次回流,但是大部分现代浏览器都做了优化,只会引起一次回流,但是在旧版本浏览器中还是会引起三次回流。
因此可以考虑合并所有修改统一处理
let el = document.getElementById("root");
el.style.cssText += `el.width = 100px;el.height = 100px;el.padding = 10px;`
或者
let el = document.getElementById("root");
el.className += 'active'
- 批量修改DOM
当对DOM进行一系列修改的时候,可以通过以下步骤减少回流重绘次数。
(1). 使元素脱离文档流
(2). 对元素进行操作
(3). 让元素回归文档流
按照这个步骤操作。1,3两步会引起回流,但是频繁操作的第二步却是不会引起回流的,因为目标元素已经不在渲染树了。
有三种方式可以让DOM脱离文档流:
- 隐藏元素,修改,重新显示
- 使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档。
- 将原始元素拷贝到一个脱离文档的节点中,修改节点再替换原始元素。
var ul = document.getElementById("root");
for (var i = 0; i<3; i++){
var li = document.createElement('li');
li.innerText = "text"
ul.appendChild(li)
}
以上代码回流三次
以下是优化方案
var ul = document.getElementById("root");
ul.style.display = "none"
for (var i = 0; i<3; i++){
var li = document.createElement('li');
li.innerText = "text"
ul.appendChild(li)
}
ul.style.display = "block"
回流2次
var ul = document.getElementById("root");
var frag = document.createDocumentFragment();
for (var i = 0; i<3; i++){
var li = document.createElement('li');
li.innerText = "text"
frag.appendChild(li)
}
ul.appendChild(frag)
var ul = document.getElementById("root");
var clone = ul.cloneNode(true);
for (var i = 0; i<3; i++){
var li = document.createElement('li');
li.innerText = "text"
clone.appendChild(li)
}
ul.parentNode.replaceChild(clone,ul)
理论上是可以优化的,但是现代浏览器实际上会使用队列来存储多次修改,进行优化,所以在现代浏览器上优化效果不明显。
-
对于动画可以使用绝对定位使其脱离文档流,避免影响父元素后续频繁回流。
-
CSS3硬件加速(GPU加速)
css3硬件加速可以让transform、opacity、filters、这些动画不会引起回流重绘。
常见触发硬件加速css属性
- transform
- opacity
- filters
- will-change
缺点:硬件加速过多会导致内存过大,会有性能问题。
参考:
你真的了解回流和重绘吗
浏览器数据本地存储
localStorage
生命周期永久,除非用户清除浏览器中的localStorage信息,否则永远存在;
localStorage中一般浏览器支持的是5M大小
优点:
- 拓展了cookie的4k限制
- 遵循同源策略,不同的网站直接不能使用相同的localStorage
缺点: - 需要手动删除,否则长期存在
- 各浏览器支持不太统一
- 只支持String类型的存储,JSON对象须使用JSON.stringify()转换
- 本质是对字符串的读取,如果存储内容多会消耗内存,导致页面变卡
sessionStorage
会话存储,在浏览器被关闭之前使用,关闭浏览器之后数据消失。(关闭当前页面不会消失,再次打开该页面,sessionStorage还存在)
Cookie
由于http协议是无状态的而服务端的业务必须是有状态的,Cookie诞生的最初目的是为了存储web中的状态信息,以方便服务器端使用。比如判断用户是否是第一次访问网站。它是一个有浏览器和服务器共同协作实现的规范。
Cookie主要由服务端生成,前端也可以设置,保存在客户端本地,通过response响应头的set-Cookie字段进行设置,且Cookie的内容自动在请求的时候被传递给服务器。
如下:
Cache-Control: max-age=0, private, must-revalidate
Connection: keep-alive
Date: Mon, 25 May 2020 09:09:03 GMT
ETag: W/"b9e6480ac475b399cd9533cab4fa89ce"
Server: Tengine
Set-Cookie: locale=zh-CN; path=/
Set-Cookie: _m7e_session_core=1c249e10b2aaad51dcbe0aa865cf3f34; domain=.jianshu.com; path=/; expires=Mon, 25 May 2020 15:09:03 -0000; secure; HttpOnly
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Request-Id: 9b702ad0-88f3-4b48-8e43-48d971c57888
X-Runtime: 0.065999
X-XSS-Protection: 1; mode=block
Cookie格式如下
Set-Cookie: "name=value;domain=.domain.com;path=/;expires=Sat, 11 Jun 2016 11:29:42 GMT;HttpOnly;secure"
其中name=value是必选项,其他都是可选项。
主要构成如下:
name:一个唯一确定的cookie名称。
value:存储在cookie中的字符串值,一般情况下都会进行加密编码
domin:指明cookie对哪个域是有效的。所有向该域发送的请求中都会包含这个cookie信息。这个值可以包含子域(如:domain=.jianshu.com则表示对于jianshu.com的所有子域都有效)
path:表示这个cookie影响到的路径,浏览器会根据这项配置向指定域中匹配的路径发送cookie
expires:失效时间,表示cookie何时应该删除的时间戳(也就是,何时停止向服务器发送该cookie)。如果不设置,浏览器会在页面关闭时删除所有cookie;不过也可以自己设置删除时间。这个值是GMT格式,如果客户端和服务器端时间不一致,使用expires就会存在偏差。
max-age:与expires的作用相同,用来告诉浏览器此cookie多久过期(单位:秒)而不是一个固定的时间点。正常情况下,max-age的优先级高于expires。
HttpOnly告知浏览器不允许通过脚本document.cookie去更改这个值,同样这个值在doucment.cookie中也不可见,但在http请求中仍然会携带这个cookie,虽然在脚本中不可获取,但仍然在浏览器安装目录中以文件形式存在。这项通常在服务器端设置。
secure:安全标志,制定后只有在使用SSL链接时候才能发送的浏览器,如果是http链接则不会传递该信息。就算设置了secure属性也并不代表他人不能看到机器本地保存的cookie信息,所以不要把重要信息放cookie就对了。
Cookie作用:
可以记录用户ID、密码、浏览过的网页、停留的时间等信息。一个网站只能读取它自己放置的信息,不能读取其他网站的Cookie文件。因此,Cookie文件还保存了host属性,即网站的域名或ip。这些属性以键值对的方式保存,为了安全,内容大多加密处理。
Cookie优点:
- 提升用户体验,如记住密码等功能
- 弥补了HTTP无连接特性
- 站点统计访问人数的一个依据
Cookie缺点:
- 无法解决多人共用一台电脑的问题,有不安全因素。
- Cookie文件容易被误删
- 容量有限制
- 在请求头上携带存在数据安全性问题
- Cookies欺骗,修改host文件,可以非法访问目标站点的Cookie
indexDB
浏览器发展越来越成熟,很多应用考虑做离线功能,将大量数据存储在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。
由于存储空间大小和其他原因,浏览器的存储方案都不适合做大量数据存储。
IndexDB就是浏览器提供的本地数据库,可以被网页脚本创建,操作。允许存储大量数据,提供查找接口,建立索引等。浏览器数据库 IndexedDB 入门教程
浏览器缓存(强缓存与协商缓存)
第一次听说强缓存与协商缓存也是在面试中。之前一直以为缓存就是本地存储,这次就整理一下关于缓存的知识。
缓存从微观上可以分为以下几类:
- 浏览器缓存
- 代理服务器缓存
- CDN缓存
- 数据库缓存
- 应用层缓存
浏览器缓存又称http缓存:
根据是否需要服务器参与是否使用缓存,分为强缓存与协商缓存。
强缓存
强缓存不会向服务器发送请求,直接从缓存中读取资源,请求返回200的状态码,在chrome控制台的network选项中可以看到size显示from disk cache 或from memory cache.
- 浏览器第一次向服务器请求一个资源,服务器在返回资源的同时,在响应头(Response Header)上添加上Cache-Control
- 浏览器在接收到这个资源后,会把这个资源连同所有的Response Header一起缓存下来
- 浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,如果当前时间没有超过设置的Catch-Control就能命中缓存,否则就不行。
- 如果缓存没有命中,浏览器直接从服务器加载资源时,Response Header在重新加载的时候会被更新
Expires
是服务器返回的一个绝对时间,对比的时间是客户端的时间,而客户端的时间可以随意修改,因此,会影响缓存效果,在http1.1的时候,提出一个新的header,Cache-Control,这是一个相对时间,在进行缓存的时候,都是利用客户端进行判断,而且是以时间长度为依据判断的,因此会更有效。在配置缓存的时候以秒为单位,用数值表示:Cache-Control : max-age=1000000000,他的缓存过程是:
- 浏览器第一次向服务器请求一个资源,服务器在返回资源的同时,在响应头(Response Header)上添加上Cache-Control
- 浏览器在接收到这个资源后,会把这个资源连同所有的Response Header一起缓存下来
- 浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,拿出Cache-Control毫秒数跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行。
- 如果缓存没有命中,浏览器直接从服务器加载资源时,Response Header的Catch-Control在重新加载的时候会被更新
这两个header可以只用一个,也可以同时使用,同时存在,Cache-Control优先级高于Expires。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,协商缓存生效,返回304和Not Modified,协商缓存利用的是【Last-Modified、If-Modified-Since】、【Etag、If-None-Match】这两对header来管理的。
【Last-Modified、If-Modified-Since】
- 浏览器第一次向服务器发送加载资源请求时,服务器会在响应头(Response Header)上加上Last-Modified,表示该资源在服务器最后一次修改时间,
- 浏览器再一次请求该资源的时候,会在请求头(Request Header)加上If-Modified-Since,值为Last-Modified的值。
- 服务器再次收到资源请求时,根据浏览器传过来的If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有发生变化则返回304 Not Modified,但是不会返回资源内容,如果有变化就返回资源内容,当服务器返回304 Not Modified的响应时,response header中不会再添加Last-Modified的header,因为资源没有变化,Last-Modified的值也不变。
- 浏览器收到304的响应后,就会从缓存中加载资源
- 如果协商缓存没有命中,浏览器直接从服务器加载资源,同时更新Last-Modofied为当前资源在服务器最终修改时间,下次请求时,If-Modified-Since会采用上一次返回的Last-Modified的值
缺点:文件修改时间改了,但文件内容没有变。
【Etag、If-None-Match】
- 浏览器第一次向服务器请求一个资源,服务器在返回这个资源的同时,在response的header加上Etag,这个Etag是服务器根据当前请求的资源生成的一个唯一标识,是一个字符串,只要资源内容发生改变,这个字符串也会改变,跟时间没有关系。
- 浏览器再次请求这个资源的时候,在request的header上加上If-None-Match。这个If-None-Match的值是上一次请求返回的Etag的值
- 服务器再次收到资源请求时,根据客户端传过来的If-None-Match和重新生成的资源的新的Etag做比较,相同则返回304 Not Modified,不会返回资源内容,但这里这里即使资源没有发生变化,也会返回Etag,因为这个Etag重新生成过,即使Etag没有发生变化
- 浏览器收到304响应后,就从缓存中加载资源。
一般服务器上的【Last-Modified、If-Modified-Since】和【Etag、If-None-Match】会同时启用,但在精度上Etag优先级高,比如Last-Modified的时间单位是秒,如果某个文件1秒内修改很多次,那么他们的Last-Modified并没有体现出来修改。但是Etag每次都会生成一个hash值,以保证精度。
在性能上Last-Modified优先级高,Last-Modified只需要记录时间,而Etag需要服务器通过算法计算出一个hash值。
协商缓存需要配合强缓存使用。
缓存机制
强缓存优先于协商缓存,若强缓存【Expires、Cache-Control】生效则使用强缓存,若不生效,则使用协商缓存【Last-Modified、If-Modified-Since】【Etag、If-None-Match】协商缓存由服务器决定是否使用缓存,若协商失败,那么代表该请求的缓存失败,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304继续使用缓存。
流程图如下:
绿色
代表命中缓存,橙黄色
代表重新从服务器获取数据。
面试官问:
- 页面什么时候加载资源从内存中,什么时候加载资源从磁盘中
答:当浏览器访问资源的时候,访问到的是缓存资源,当第一次访问后没有关闭该浏览器tab,在第二次访问时资源来自memory,当tab被关闭再访问时,资源来自disk.
写在最后:文中内容大多为自己平时从各种途径学习总结,文中参考文章大多收录在我的个人博客里,欢迎阅览http://www.tianleilei.cn