HTTP
HTTP 缓存机制
基本原理
浏览器在加载资源时,根据请求头的expires和cache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modified和etag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源
如果前面两者都没有命中,直接从服务器加载资源
HTTP 强缓存和协商缓存
相同点
如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据。
不同点
强缓存不发请求到服务器,协商缓存会发请求到服务器。
强缓存
Expires
Expires
是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回。Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效1
Expires: Wed, 11 May 2018 07:20:00 GMT
Cache-Control
Cache-Control
出现于 HTTP / 1.1,优先级高于 Expires ,表示的是相对时间1
Cache-Control: max-age=315360000
Cache-Control: no-cache
不会缓存数据到本地的说法是错误的,详情《HTTP权威指南》P182Cache-Control: no-store
才是真正的不缓存数据到本地Cache-Control: public
可以被所有用户缓存(多用户共享),包括终端和CDN等中间代理服务器Cache-Control: private
只能被终端浏览器缓存(而且是私有缓存),不允许中继缓存服务器进行缓存
协商缓存
当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的http状态为304并且会显示一个Not Modified的字符串。
Last-Modified
,If-Modified-Since
Last-Modified
表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since
(上次返回的Last-Modified
的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。ETag
、If-None-Match
Etag
就像一个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的If-None-Match
的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变动就会发送新的资源回来。
ETag的优先级比Last-Modified更高,
具体为什么要用ETag,主要出于下面几种情况考虑:- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
- 某些服务器不能精确的得到文件的最后修改时间
介绍一下 HTTP/2 新增特性
多路复用
HTTP/2将一个TCP连接分为若干个流(Stream),每个流中可以传输若干消息(Message),每个消息由若干最小的二进制帧(Frame)组成。这也是HTTP/1.1与HTTP/2最大的区别所在。 HTTP/2中,每个用户的操作行为被分配了一个流编号(stream ID),这意味着用户与服务端之间创建了一个TCP通道;协议将每个请求分割为二进制的控制帧与数据帧部分,以便解析。
HPACK 算法
HTTP/2 引入HPACK算法对HTTP头部做压缩。
服务器推送
服务器推送简单来说就是,还没有收到浏览器的请求,服务器就把各种资源推送给浏览器。
比如,浏览器只请求了index.html,但是服务器把index.html、style.css、example.png全部发送给浏览器。这样的话,只需要一轮 HTTP 通信,浏览器就得到了全部资源,提高了性能。
JavaScript
语言基础问题
下列代码运行后结果
1
2
3
4
5
6
7var a = 2
function a() {
console.log('1')
}
a() // 1将
[1, 2, 3, 4, 5]
变成[2, 3, 4, 5, 6]
都有哪些方式?1
2
3
4
5
6
7
8
9let arr = [1, 2, 3, 4, 5]
arr = arr.map(item => item + 1)
arr.forEach((item, index) => {
arr[index] = arr[index] + 1 // 改变原数组
})
Array.form(arr, t => t + 1)写出至少5种数组浅拷贝的方式
1
2
3
4
5
6
7
8let arr = [{a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}]
const arr1 = [...arr]
const arr2 = arr.concat()
const arr3 = arr.map(item => item)
const arr4 = Array.from(arr)
const arr5 = arr.slice(0)
const arr6 = JSON.parse(JSON.stringify(arr)) // 不安全的哪些方法不会修改原数组,哪些方法会修改原数组
1
2
3
4// 会修改原数组的方法
// 不会修改原数组的方法如何判断变量类型
1
2
3
4
5
6
7
8
9var val1 = null
var val2
var val3 = function() {
console.log(111)
}
Object.prototype.toString.call(val1) // "[object Null]"
Object.prototype.toString.call(val2) // "[object Undefined]"
Object.prototype.toString.call(val3) // "[object Function]"如何截取上面类型字符串
1
2
3
4let str = "[object Function]"
console.log(str.slice(8, -1))
str.match(/\[object\s(\w+)\]/)[1]