1. 浏览器缓存机制
缓存行为可分为 强缓存 、协商缓存 、启发式缓存。
- 强缓存: 不向服务器进行请求,直接读取缓存状态码为
200
- 协商缓存: 向服务器发送请求,服务器根据请求头中的相关字段来判断是否命中缓存,如果命中缓存,则返回
304
状态码,否则返 回200
状态码 - 启发式缓存: 浏览器根据一些规则来判断是否命中缓存
启发式缓存
启发式缓存是依赖浏览器自身的规则来判断何时请求资源以及何时缓存资源,也就是说它不需要与服务器进行通信。
WARNING
因为不需要与服务器进行通信,所以它无法知道服务器的最新状态,所以有的时候可能会造成请求了最新资源,但是浏览器还是读取了缓存。
资源相应头中没有出现 Expire
和 cache-control:max-age
字段且没有出现 no-store
的情况下并且有设置 Last-Modified
,浏览器才会启用 启发式缓存算法。但是这个算法的缓存时间因浏览器而异,比如会将响应头中的 Date
字段减去 Last-Modified
字段的 10%
来计算缓存时间。
协商缓存
协商缓存发生在通信双方三次握手之后,依靠 Last-Modified/If-Modified-Since
,Etag/If-None-Match
等标识来确定是否命中协商缓存,如果命中则返回 304
状态码,否则返回 200
状态码。
其中 Etag
是对文件内容的一次 hash
运算,Last-Modified
则是文件的最后修改时间。
所以 Last-Modified
比 Etag
更加高效但是 Etag
更加准确。
WARNING
如果请求头中 Last-Modified
和 Etag
都有值,那么服务器会优先选择 Etag
来进行缓存判断。
启发式缓存和协商缓存区别
todo
强缓存
强缓存在 http1.0
中是靠 Expire
字段实现,但是由于服务器时间和客户端时间不一致的情况下所以在 http1.1
中引入了 Cache-Control
字段。
Cache-Control
可以组合成多种指令,指令间由 ,
分隔
// eg:
Cache-Control: max-age:3600, s-maxage=3600, public
Cache-Control: no-cache
缓存位置
Service Worker
: 运行在浏览器背后的线程、无法直接访问Dom
但是可以作为离线缓存
、消息推送
、网络代理
等用途。协议必须是https
Memory Cache
: 缓存在内存中Disk Cache
: 缓存在硬盘中Push Cache
:http2.0
的主动推动
WARNING
http2.0 中的主动推送功能在 Chrome106 版本后被禁用
浏览器会根据资源的大小、访问频率以及其他因素来决定将资源存储在内存缓存还是磁盘缓存中。通常,对于较小、频繁访问的资源,浏览器更倾向于将其存储在内存缓存中,而对于较大、不经常访问的资源,浏览器可能选择存储在磁盘缓存中。
缓存完整过程
浏览器第一次加载资源,服务器返回 200
,浏览器将资源文件从服务器上请求下载下来,并把 response header
及该请求的返回时间一并缓存;
下一次加载资源时,先比较当前时间和上一次返回 200
时的时间差,如果没有超过 Cache-control
设置的 max-age
,则没有过期,命中强缓存,不发请求直接从本地缓存读取该文件(如果浏览器不支持 HTTP1.1,则用 expires 判断是否过期);如果时间过期,则向服务器发送 header
带有 If-None-Match
和 If-Modified-Since
的请求
服务器收到请求后,优先根据 Etag
的值判断被请求的文件有没有做修改,Etag
值一致则没有修改,命中协商缓存,返回 304
;如果不一致则有改动,直接返回新的资源文件带上新的 Etag
值并返回 200
;
如果服务器收到的请求没有 Etag
值,则将 If-Modified-Since
和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304
;不一致则返回新的 Last-Modified
和文件并返回 200
;
2. 浏览器渲染机制
- 浏览器会根据
HTML
内容从上到下解析创建DOM Tree
- 构建
CSS
树,当解析遇到外部样式时会异步下载,并下载完后解析成CSS Tree
需要注意的是,解析过程中遇到图片、视频等资源都会异步下载,但是遇到script
且没有defer/async
时则会停止解析DOM
等script
下载完并执行完后再继续解析 - 合并
DOM Tree
和CSS Tree
构建Render Tree
- 布局、将
Render Tree
上的计算每一个DOM
节点在屏幕上的位置 - 绘制,这一步包括很多
- 构建图层树,将
Render Tree
上的节点根据CSS
属性(zIndex,合成图层等)进行分层,并按照图层的顺序进行绘制 - 分块,将图层分割成一个个指令块,并将按照顺序组成绘制列表提交到合成线程中。
- 光栅化,合成现成中将图层分成图块,进去光栅化线程池中转换成位图
- 注意因为用户大部分关注视口部分,所以靠近视口部分的图层会优先被光栅化
- 显示,光栅化完毕后则提交给浏览器进程显示到屏幕上
- 构建图层树,将
3. 浏览器资源解析机制
完整流程
- 从解析
html
开始document.readystate = "loading"
- 解析完
dom
后,开始触发DomContentLoad
事件 - 此时可能还有 样式、图片等资源未加载完,所以
document.readystate = "interactive"
,等它们都加载完并执行完document.readystate = 'complete'
才会触发load
事件
为什么 CSS 需要放在头部
- 外链的
CSS
文件放在哪个位置都不会影响html
的解析,因为浏览器会异步加载CSS
文件 - 因为浏览器可以并行解析
CSS
和DOM
,如果放在尾部DOM
被解析完再去下载样式文件会重新计算样式引起回流和重绘
引起页面闪烁
为什么 Script 需要放在尾部
script
标签放在头部会阻塞 DOM
的解析,因为浏览器遇到 script
标签会停止解析 DOM
直到脚本加载并执行完毕
async 和 defer 的区别
async
表示异步加载,加载完立即执行,并不按照顺序defer
表示延迟加载,加载完不会立即执行,而是等待DOM
解析完再执行在DOMContentLoad
事件触发前执行。而且是按照顺序async
的优先级比defer
高
预加载
预加载主要依靠 link
标签上面的 rel
的 preload
和 prefetch
属性,告知浏览器提前加载好这些资源
preload
表示当前页面必定需要的资源,浏览器会优先加载prefetch
表示当前页面可能需要的资源,浏览器会在空闲时加载preload、prefetch
仅仅是加载资源,并不会执行
预连接
预链接也是依靠 link
标签上面的 rel
的 preconnect
和 dns-prefetch
属性,告知浏览器提前建立与服务器的连接
<link rel="preconnect" href="https://example.com"></link>
<link rel="dns-prefetch" href="https://example.com"></link>
两者的区别
preconnect
建立连接(dns/tsl 握手,三次握手/重定向等),dns-prefetch
只解析跨域域名(只到 dns 这步)
4. 浏览器安全
跨站脚本攻击(XSS)
XSS 可分为 存储型、反射型、DOM 型
存储型
: 将危险监本存储在数据库中,在其他用户读取内容时被执行反射型
: 是因为恶意脚本是通过作为网络请求的参数出现在 url 中,经过服务器解析响应,拼接在 HTML 中传回给客户端,然后浏览器解析执行恶意脚本。文档型
: 文档形的 XSS 攻击其实也是恶意脚本被作为请求 URL 的参数;浏览器解析后作为脚本执行,和反射形的区别在于:由前端 JS 取出 URL 中的恶意代码并执行
措施
- 转义字符,如
<
转义为<
- 设置
Cookie
的http-only
属性 - 响应头中加入
CSP(Content-Security-Policy)
策略, 只允许加载指定域的脚本及样式
跨站伪造(CSRF)
CSRF 攻击者可以伪造用户的请求,以用户的身份向网站发起恶意请求
措施
- 服务端验证请求头中的
refer
和Origin
字段 - 服务端验证请求参数中的
csrf token
字段 - Cookie 设置
SameSite
不为none
;新版浏览器默认为Lax
5. 跨域
只要协议、域名、端口、ip 中有一个不同都算跨域。
跨域解决方案
- jsonp
- 跨域资源共享(CORS)在服务器中的响应头设置
Access-Control-Allow-origin:'*';Access-Control-Allow-method:'*'
- postMessage
- nginx 反向代理
- nodejs 中间件代理
简单请求和非简单请求
- 简单请求的请求方法必须是
GET/POST/HEAD
- 简单请求的请求头只能包含以下字段
- Accept
- Accept-Language
- Content-Language
- Content-Type
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
除此之外都是非简单请求,像我们常见的往请求头里面加 token 或者 Content-Type: application/json 都是非简单请求
6. 浏览器存储
- Cookie 大小 5kb
- sessionStorage 会话级
- localStorage 大小 5mb
- indexDB
Cookie
Cookie
的属性有 http-only/domain/SameSite/max-age/secure )
等 其中要注意
domain
:指定了哪些主机可以访问该Cookie
的域名。如果设置为.google.com
,则所有以google.com
结尾的域名都可以访问该Cookie
。注意第一个字符必须为.SameSite
: 限制那些情况不带Cookie
,可以预防CSRF
攻击None
: 任何情况下都不带Cookie
Lax
: 大多数情况下不带Cookie
,只有当点击链接或提交表单时才会带Cookie
(默认值)strict
: 站点相同时才携带Cookie
跨域请求如何携带 Cookie
例如我们想要在跨域请求中带上 Cookie,需要满足以下条件:
Request
请求设置 withCredentials
为 true
服务器设置首部字段 Access-Control-Allow-Credentials
为 true
服务器的 Access-Control-Allow-Origin
设置为\*
或者对应的域名;
7. 重绘和重排
- 重绘:当节点需要更新属性而不会影响布局的,比如
color
,就称为重绘 - 重排:当节点需要更新布局,就称为重排。常见的情况有:
- 添加或者删除可见的 DOM 元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、宽高、padding 等)
- 内容发生变化,比如文本变化或者图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候,或者
resize
窗口的时候 - 获取
clientWidth/clientHight/computedStyle/innerHight/offset
等。只要是获取 layout 信息的操作,都会触发重排
如何减少重绘和重排
- 避免频繁操作样式,最好一次性重写
style
属性,或者将多次操作合并为一次 - 可以现将频繁操作的
DOM
元素设置为display: none
,操作结束后再显示出来 - 批量增加
DOM
可以使用createDocumentFragment
方法在批量插入 - 缓存布局信息
- 将动画效果应用到
position
属性为absolute
或fixed
的元素上 - 对
resize/scroll
等事件进行防抖节流处理 - 利用 CSS3 属性开启硬件加速
transform/opacity/filter
等
8. 浏览器路由
Hash
通过监听 hashChange
可以获取到 hash
值的变化,从而实现路由的切换
History
通过 popState
事件可监听到路由变化,需要注意的是 hashChange
事件也会触发 popState
9. 浏览器事件循环
// 0 1 2 3 4 5
0,1,2,3,4,5
[
then(0)
then(1)
then(2)
then(()=>4)
then(3)
then(4)
then(5)
]
Promise.resolve()
.then(() => {
console.log(0);
return Promise.resolve(4);// 可以改写成 Promise.resolve(4).then(()=>{return 4})
})
.then((res) => {
console.log(res);
});
Promise.resolve()
.then(() => {
console.log(1);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(5);
});