URL是什么
URL(Uniform Resource Locator), 统一资源定位符,用于定位互联网上的资源。也就是通俗来说的网址。
scheme: // host.domain:port / path / filename ? abc = 123 # 456789
- scheme - 定义因特网服务的类型。常见的协议有 http、https、ftp、file
- host - 定义域主机(http 的默认主机是 www)
- domain - 定义因特网域名,比如 baidu.com
- port - 定义主机上的端口号(http 的默认端口号是 80)
- path - 定义服务器上的路径
- filename - 定义文档/资源的名称
- query - 即查询参数
- fragment - 即 # 后的hash值,一般用来定位到某个位置
手写URL解析中参数的方法
query
的规定是以第一个?
开始,至行尾或#
结束。 fragment
以#
为开始,行尾为结束。 也就是说query必须在fragment之前。
另外根据query
和fragment
字符限定的规定,两者中都可以含有/和?
,但不可以含有#
。
所以获取URL中的参数只需要解析第一个问号和#之间的数据即可。
function getAllUrlParams(url) {
// 用JS拿到URL,如果函数接收了URL,那就用函数的参数。如果没传参,就使用当前页面的URL
var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
// 用来存储我们所有的参数
var obj = {};
// 如果没有传参,返回一个空对象
if (!queryString) {
return obj;
}
queryString = queryString.split('#')[0];
// 将参数分成数组
var arr = queryString.split('&');
for (var i = 0; i < arr.length; i++) {
// 分离成key:value的形式
var a = arr[i].split('=');
// 将undefined标记为true
console.log(a)
var paramName = a[0];
var paramValue = typeof (a[1]) === 'undefined' ? true : decodeURIComponent(a[1]);
// 如果调用对象时要求大小写区分,可删除这两行代码
paramName = paramName.toLowerCase();
if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();
// 如果paramName以方括号结束, e.g. colors[] or colors[2]
if (paramName.match(/\[(\d+)?\]$/)) {
// 如果paramName不存在,则创建key
var key = paramName.replace(/\[(\d+)?\]/, '');
if (!obj[key]) obj[key] = [];
// 如果是索引数组 e.g. colors[2]
if (paramName.match(/\[\d+\]$/)) {
// 获取索引值并在对应的位置添加值
var index = /\[(\d+)\]/.exec(paramName)[1];
obj[key][index] = paramValue;
} else {
// 如果是其它的类型,也放到数组中
obj[key].push(paramValue);
}
} else {
// 处理字符串类型
if (!obj[paramName]) {
// 如果如果paramName不存在,则创建对象的属性
obj[paramName] = paramValue;
} else if (obj[paramName] && typeof obj[paramName] === 'string') {
// 如果属性存在,并且是个字符串,那么就转换为数组
obj[paramName] = [obj[paramName]];
obj[paramName].push(paramValue);
} else {
// 如果是其它的类型,还是往数组里丢
obj[paramName].push(paramValue);
}
}
}
return obj;
}
浏览器解析完URL后,构造http请求,会根据浏览器的缓存机制来决定是否发送http请求。
- 浏览器发送请求前会根据请求头中的
expires
和catche-control
判断是否命中强缓存,若命中强缓存,会直接读取缓存资源,不会发送请求;如果没命中,则会进入下一步。 - 没有命中强缓存,就会发送http请求,根据请求头中的
If-Modified-Since 和 If-None-Match
判断是否命中协商缓存,如果命中,也会直接读取缓存资源,没有则进入下一步。 - 如果前两步都没有命中,则直接从服务端获取资源。并保存输入内容的副本
强缓存
强缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。强缓存又分为两种Expires和Cache-Control
Expires
- 版本:HTTP/1.0
- 来源:存在于服务端返回的响应头中
- 语法:Expires: Wed, 22 Nov 2019 08:41:00 GMT(格林尼治时间)
- 缺点:服务器的时间和浏览器的时间可能并不一致导致失效
Cache-Control
- 版本:HTTP/1.1
- 来源:响应头和请求头
- 语法:Cache-Control:max-age=3600
- 缺点:时间最终还是会失效
在请求中使用Cache-Control 时,它可选的值有:
名称 | 说明 |
---|---|
no-cache | 告知服务器不直接使用缓存,要求向原服务器发起请求 |
no-store | 所有内容都不会被保存到缓存或Internet临时文件中 |
max-stale[=delta-seconds] | 告知服务器客户端愿意接收一个超过缓存时间的资源,若有定义delta-seconds则为delta-seconds秒,若没有则为任意超出时间 |
min-fresh=delta-seconds | 告知(代理)服务器客户端希望接收一个在小于delta-seconds秒内被更新过的资源 |
no-transform | 告知(代理)服务器客户端希望获取实体数据没有被转换(比如压缩)过的资源 |
noly-if-cached | 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器发去请求 |
cache-extension | 自定义扩展值,若服务器不识别该值将被忽略掉 |
在响应中使用Cache-Control 时,它可选的值有:
名称 | 说明 |
---|---|
public] | 表明任何情况下都得缓存该资源(即使是需要HTTP认证的资源) |
Private=[field-name] | 表明返回报文中全部或部分(若指定了field-name则为field-name的字段数据)仅开放给某些用户(服务器指定的share-user,如代理服务器)做缓存使用,其他用户则不能缓存这些数据 |
no-store | 所有内容都不会被保存到缓存或Internet临时文件中 |
no-cache | 告知服务器不直接使用缓存,要求向原服务器发起请求 |
no-transform | 告知客户端缓存文件时不得对实体数据做任何改变 |
noly-if-cached | 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器发去请求 |
must-revalidate | 当前资源一定是向原方法服务器发去验证请求的,如请求失败会返回504(而非代理服务器上的缓存) |
proxy-revalidate | 与must-revalidate类似,但仅能应用于共享缓存(如代理) |
max-age=delta-seconds | 告知客户端该资源在delta-seconds秒内是新鲜的,无需向服务器发请求 |
s-maxage=delta-seconds | 同max-age,但仅能应用于共享缓存(如代理) |
cache-extension | 自定义扩展值,若服务器不识别该值将被忽略掉 |
在Cache-Control 中,这些值可以自由组合,多个值如果冲突时,也是有优先级的,而no-store优先级最高。
public和private的选择 如果你用了CDN,你需要关注下这个值。CDN厂商一般会要求cache-control的值为public,提升缓存命中率。如果你的缓存命中率很低,而访问量很大的话,可以看下是不是设置了private,no-cache这类的值。如果定义了max-age,可以不用再定义public,它们的意义是一样的。
协商缓存
在强制缓存失效后,浏览器携带缓存标识符Last-Modified和ETag,向服务器发起请求。由服务器根据缓存标识符决定是否使用缓存。
缓存标识符的作用:验证缓存是否有效。比如服务器的资源更新了,客户端需要及时刷新缓存;又或者客户端的资源过了有效期,但服务器上的资源还是旧的,此时并不需要重新从服务器获取资源。
- Last-Modified:表示请求的URL(资源)最后一次更新的时间
服务端在返回资源时,会将该资源的最后更改时间通过Last-Modified字段返回给客户端。客户端下次请求时通过If-Modified-Since或者If-Unmodified-Since带上Last-Modified,服务端检查该时间是否与服务器的最后修改时间一致:如果一致,则返回304状态码,不返回资源;如果不一致则返回200和修改后的资源,并带上新的时间。
If-Modified-Since和If-Unmodified-Since的区别是: If-Modified-Since:告诉服务器如果时间一致,返回状态码304 If-Unmodified-Since:告诉服务器如果时间不一致,返回状态码412 - etag:资源的实体标识(哈希字符串),当资源内容更新时,Etag会改变
单纯的以修改时间来判断还是有缺陷,比如文件的最后修改时间变了,但内容没变。对于这样的情况,我们可以使用etag来处理。 etag的方式是这样:服务器通过某个算法对资源进行计算,取得一串值(类似于文件的md5值),之后将该值通过etag返回给客户端,客户端下次请求时通过If-None-Match或If-Match带上该值,服务器对该值进行对比校验:如果一致则不要返回资源。
If-None-Match和If-Match的区别是: If-None-Match:告诉服务器如果一致,返回状态码304,不一致则返回资源 If-Match:告诉服务器如果不一致,返回状态码412
缓存头部对比
头部 | 优势 | 劣势 |
---|---|---|
Expires | 1、HTTP 1.0 产物,可以在HTTP 1.0和1.1中使用,简单易用。2、以时刻标识失效时间。 | 1、时间是由服务器发送的(UTC),如果服务器时间和客户端时间存在不一致,可能会出现问题。2、存在版本问题,到期之前的修改客户端是不可知的。 |
Cache-Control | 1、HTTP 1.1 产物,以时间间隔标识失效时间,解决了Expires服务器和客户端相对时间的问题。2、比Expires多了很多选项设置。 | 1、HTTP 1.1 才有的内容,不适用于HTTP 1.0 2、存在版本问题,到期之前的修改客户端是不可知的。 |
Last-Modified | 1、不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间如果相同则返回304,不同返回200以及资源内容。 | 1、只要资源修改,无论内容是否发生实质性的变化,都会将该资源返回客户端。例如周期性重写,这种情况下该资源包含的数据实际上一样的。2、以时刻作为标识,无法识别一秒内进行多次修改的情况。3、某些服务器不能精确的得到文件的最后修改时间。 |
ETag | 1、可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况。2、不存在版本问题,每次请求都回去服务器进行校验。 | 1、计算ETag值需要性能损耗。2、分布式服务器存储的情况下,计算ETag的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时发现ETag不匹配的情况。 |
用户操作对浏览器缓存的影响
用户操作 | 强缓存 | 协商缓存 |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面连接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进回退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
ctrl+F5强制刷新 | 无效 | 无效 |