浏览器原理篇 (持续更新中)
-
- 1、浏览器的缓存策略?强缓存和协商缓存的区别?
- 2、实际项目中用到的缓存?
- [3、从输入一个 URL 地址到浏览器完成渲染的整个过程?](#3、从输入一个 URL 地址到浏览器完成渲染的整个过程?)
- [4、cookie、session、localStorage 和 sessionStorage 有什么区别?](#4、cookie、session、localStorage 和 sessionStorage 有什么区别?)
- [5、cookie 可以跨域吗?怎么实现跨域?](#5、cookie 可以跨域吗?怎么实现跨域?)
- [6、什么是 cors 预检请求?](#6、什么是 cors 预检请求?)
- 7、内存优化建议(如何避免内存泄露)?
1、浏览器的缓存策略?强缓存和协商缓存的区别?
浏览器缓存主要分为强缓存(也称本地缓存)和协商缓存(也称弱缓存)。
强缓存
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。
强缓存策略可以通过两种方式来设置,分别是 http
头信息中的 Expires
属性和 Cache-Control
属性。
- (1)服务器通过在响应头中添加
Expires
属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。 - (2)
Expires
是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是Cache-Control
属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,如下:
Cache-Control 可设置的字段:
-
public: 设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用
max-age
来精确控制; -
private: 设置了该字段值的资源只能被用户浏览器缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的 HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存;
-
no-cache: 设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则直接使用缓存好的资源;
-
no-store: 设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源;
-
max-age: 设置缓存的最大有效期,单位为秒;
-
s-maxage: 优先级高于
max-age
,仅适用于共享缓存(CDN),优先级高于max-age
或者Expires
头; -
max-stale: 设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。
一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,
Cache-Control
的优先级要高于Expires
。
no-cache 和 no-store 很容易混淆:
no-cache
是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存;no-store
是指不使用任何缓存,每次请求都直接从服务器获取资源。
协商缓存
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。
上面已经说到了,命中协商缓存的条件有两个:
- max-age=xxx 过期了
- 值为 no-cache
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。
协商缓存也可以通过两种方式来设置,分别是 http
头信息中的 Etag
和 Last-Modified
属性。
- (1)服务器通过在响应头中添加
Last-Modified
属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个If-Modified-Since
的属性,属性值为上一次资源返回时的Last-Modified
的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回304
状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是Last-Modified
标注的最后修改时间只能精确到秒级,如果某些文件在1
秒钟以内,被修改多次的话,那么文件已将改变了但是Last-Modified
却没有改变,这样会造成缓存命中的不准确。 - (2)因为
Last-Modified
的这种可能发生的不准确性,http
中提供了另外一种方式,那就是Etag
属性。服务器在返回资源的时候,在头信息中添加了Etag
属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个If-None-Match
属性,这个属性的值就是上次返回的资源的Etag
的值。服务接收到请求后会根据这个值来和资源当前的Etag
的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比Last-Modified
的方式更加精确。
当 Last-Modified
和 Etag
属性同时出现的时候,Etag
的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified
应该保持一致,因为每个服务器上 Etag
的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag
属性。
总结:
强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
使用缓存有下面的优点:
- 减少冗余的数据传输
- 减少服务器负担
- 加快客户端加载网页的速度
2、实际项目中用到的缓存?
用 webpack
打包文件,除了 index.html
入口文件不缓存,其他静态文件用 hash
值命名,hash
值不变就走强缓存,否则就走协商缓存。
3、从输入一个 URL 地址到浏览器完成渲染的整个过程?
- 1.浏览器地址栏输入 URL 并回车
- 2.浏览器查找当前 URL 是否存在缓存,并比较缓存是否过期。
- 3.DNS 解析 URL 对应的 IP
- 4.根据 IP 建立 TCP 连接(三次握手)
- 5.发送 http 请求
- 6.服务器处理请求,浏览器接收 HTTP 响应
- 7.浏览器解析并渲染页面
- 8.关闭 TCP 连接(四次挥手)
4、cookie、session、localStorage 和 sessionStorage 有什么区别?
cookie 和 session 的区别:
cookie
数据存放在客户端,session
数据放在服务器端。cookie
本身并不安全,考虑到安全应当使用session
。session
会在一定时间内保存在服务器上。如果访问量比较大,会比较消耗服务器的性能。考虑到减轻服务器性能方面的开销,应当使用cookie
。- 单个
cookie
保存的数据不能超过4K
,很多浏览器都限制一个域名最多保存50
个cookie
。
将登陆信息等重要信息存放为 session
、其他信息如果需要保留,可以放在 cookie
中。
cookie、localStorage 以及 sessionStorage 的异同点:
分类 | 生命周期 | 存储容量 | 存储位置 |
---|---|---|---|
cookie | 默认保存在内存中,随浏览器关闭失效(如果设置过期时间,在到过期时间后失效) | 4KB | 保存在客户端,每次请求时都会带上 |
localStorage | 理论上永久有效的,除非主动清除。 | 4.98MB(不同浏览器情况不同,safari 2.49M) | 保存在客户端,不与服务端交互。节省网络流量 |
sessionStorage | 仅在当前网页会话下有效,关闭页面或浏览器后会被清除。 | 4.98MB(部分浏览器没有限制) | 保存在客户端,不与服务端交互。节省网络流量 |
应用场景:
- localStorage 适合持久化缓存数据,比如页面的默认偏好配置等;
- sessionStorage 适合一次性临时数据保存。
5、cookie 可以跨域吗?怎么实现跨域?
- 1.前端请求时在
request
对象中配置 "withCredentials": true; - 2.服务端在
response
的header
中配置"Access-Control-Allow-Origin", "http://xxx:${port}"; - 3.服务端在
response
的header
中配置"Access-Control-Allow-Credentials", "true"
6、什么是 cors 预检请求?
为什么会有预检请求?
预检请求的发生,来源于浏览器的跨域请求,浏览器对跨域的处理方式一般有两种:
- 1.浏览器限制客户端发起跨域请求
- 2.跨域请求正常发起,但是服务器返回的结果被浏览器拦截
一般情况下,跨域产生是第二种情况,服务器对数据已经进行了处理然而结果被浏
览器拦截了,造成请求失败。
所以为了避免这种情况,浏览器使用了 HTTP
的 OPTIONS
方法发起了一个预检请求,预检请求成功之后才会发起真实的带数据的请求,否则阻止。
何种情况才会触发预检请求呢?
CORS 分为两种请求:简单请求和非简单请求。
-
简单请求
- 1.
GET
、POST
、HEAD
请求 - 2.Content-Type 的类型:
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
- 3.HTTP 请求头:
Accept
、Accept-Language
、Content-Language
、DPR
、Downlink
、Save-Data
、Viewport-Width
、Width
- 4.请求中的
XMLHttpRequestUpload
对象没有注册任何事件监听器;XMLHttpRequestUpload
对象可以使用 XMLHttpRequest.upload 属性访问。 - 5.请求中没有
ReadableStream
对象
- 1.
-
非简单请求
- 1.除了
GET
、POST
、HEAD
请求以外的其他请求 - 2.
Content-Type
的类型:不属于简单请求的类型的以外的类型 - 3.HTTP 请求头: 除简单请求以外的字段
- 1.除了
当是非简单请求时,浏览器会先进行一次预检请求以确定能否正常访问,是一种对数据修改的保护。
7、内存优化建议(如何避免内存泄露)?
- 1.减少组件 DOM 渲染(可以通过:数据懒加载、组件懒加载、虚拟滚动、数据分页等方式,来减少组件的 DOM 渲染)
- 2.尽可能少地创建全局变量(当进行垃圾回收时,在标记阶段因为 window 对象可以作为根节点,在 window 上挂载的属性均可以被访问到,并将其标记为活动的从而常驻内存,因此也就不会被垃圾回收,只有在整个进程退出时全局作用域才会被销毁。如果你遇到需要必须使用全局变量的场景,那么请保证一定要在全局变量使用完毕后将其设置为 null 从而触发回收机制。)
- 3.手动清除定时器
- 4.window 上的监听事件没有移除或移除错误
- 5.console 导致的内存泄漏(只有 devtools 打开时,console 打印才会引起内存泄漏的,如果不打开控制台,console 是不会引起内存变化的。稳妥起见,需要在生产环境时使用插件过滤掉 console 打印)
- 6.闭包的错误使用(闭包引用的变量定义在函数中。这样随着外部引用的销毁,该闭包就会被 gc 自动回收 (推荐),无需人工干涉)
- 7.绑在 EventBus 的事件没有解绑
- 8.弱引用:weakset、weakmap 它们对值的引用都是不计入垃圾回收机制的,如果其他对象都不再引用该对象,那么 gc 会自动回收该对象所占用的内存