前端常见面试题---2
# 1. 1px的问题可以如何去解决
出现1px的原因是设计图上的1px指的是设备像素,而不是CSS像素。DPR这个视网膜屏幕的概念最先是由苹果公司提出,计算公式:DPR = CSS像素 / 设备像素。
以IPhone 6为例,DPR = 750 / 375 = 2,在DPR为2的屏幕下,1px的物理像素会以2px渲染出来,这里给几个思路:
我们可以用0.5px的CSS像素;
修改viewport
伪类+scale,先给伪类设200%的宽度,然后再transform:scaleY(0.5)
# 2. 在浏览器输入一个URL的整体过程是怎么样的
这个问题一般都会问,因为每一个步都可以进行延伸,也不知道是哪个老哥想出来的鬼才面试题,下面是我的理解:
- 当我们在浏览器地址栏按下键盘的时候,浏览器就会启动一个算法去书签栏和历史记录中按照我们输入的字母进行筛选、展示一个我们可能会访问的URL
- 当我们选定了URL按下回车时,浏览器就会开始构建请求行,然后检测这个域名是否合法,如果合法就将此任务给网络请求线程
- 构建好请求行后就会去检测强缓存是否有效(这个步骤不会发送网络请求),如果无效,就会开始调用DNS协议进行域名解析,如果之前访问过这个URL,那么浏览器会把DNS解析后的IP地址保存下来,下次访问就直接命中(大概可以节约50~200ms),如果没有就需要去网络运营商或者DNS服务器上寻找
- 拿到DNS解析的IP地址后,就会构建HTTP请求,开始TCP三次握手建立稳定链接:客户端向服务器发送一个SYN(同步序列编码),服务端收到后返回一个新的SYN + ACK(在第一个SYN上做计算后生成的回复消息),客户端收到后回复一个ACK,三次握手建立完毕。为什么是三次?两次太少,四次太多
- 建立好三次握手后,TCP协议为了传输方便,会将HTTP报文切割并编码成一个个数据包,随后转交给网络层
- 网络层拿到这些数据包后,通过IP地址,配合ARP协议反查出MAC地址,开始传输数据 服务器收到这些数据后,将在TCP传输层协议中被分割的报文还原成完整的,这个时候一般会校验是否有权限、是否设置了缓存以及是否过期等。如果设置了协商缓存,那么会返回304状态码通知浏览器使用协商缓存(这里可以把协商缓存的字段也说下),否则开始响应。响应完毕后,服务器会判断Connection字段是否为keep-alive(在HTTP 1.1中是默认值),不是则断开
- 接下来就是浏览器开始解析请求到的文件,首先调用GUI线程并行解析HTML和CSS文件,对HTML文件使用标记化和建树算法,根据文件中设置的<!DOCTYPE>标准来生成DOM树,对CSS文件进行格式化和标准化生成CSSOM树,最后合并成合成树。注意HTML和CSS文件解析是互不影响的,但是会影响最后的合成树生成的速度,所以CSS文件中不要放@import,它总是在CSS文件解析完毕后再去加载对应的资源
- 另外GUI线程和JS线程是互斥的,当解析到HTML文件中的script标签时,就会挂起GUI线程,从而阻塞渲染,所以script标签中不要写async,它总是异步加载,然后立即执行,但你可以写defer 拿到合成树后,为了提高渲染效率,因为复杂的图层总是会由GUP单独绘制(GPU加速),不会影响其他的图层,所以开始创建图层树。普通文档流可以算是复杂图层,除此之外absolute、transform、opacity、canvas等元素都能形成复杂图层,所以说动画最好放在absolute等元素上、用transform代替left/top
- 浏览器将这些图层的绘制生成一个个绘制指令,然后交给合成池去进行绘制,生成图块和位图,最后显示出当前的页面
# 3. 浏览器存储Cookie、localStorage、sessionStorage的区别
# Cookie
由于HTTP是无状态的应用层协议,导致服务器无法记住客户端用户的操作,而Cookie主要用来记录用户的身份信息,它的大小只有4kb,不管服务端是否需要用到它,它总是被来回的传递(但不支持跨域),它和HTTP缓存一样,也可以通过设置max-age和expires字段决定过期时间。
在安全上,由于JS脚本可以读取Cookie,所以我们得通过Set-Cookie中设置httpOnly禁止JS读取,预防XSS攻击。除此之外,不应该用Cookie传递敏感信息。
所以可以看出Cookie主要是用来维持状态,而不是做本地存储的
# localStorage
H5中新提出的Web存储技术,大小是5MB,远大于Cookie体积。由于保存在浏览器端,所以我们可以直接通过API调用:
- 存:localStorage.setItem("key","value")
- 取:localStorage.getItem("key")
它的生命周期是永久,除非手动清除。应用场景上,我们可以用它来保存图片这种内容稳定的资源
# sessionStorage
和localStorage一样,不仅都是H5提出的Web存储技术,而且用法和空间大小也极为相似,最大不同的地方有两点:
- sessionStorage的生命周期是当前标签页关闭
- 无法跨标签页访问,也就是Tab
相同点是它们三个都是存在客户端,且无法跨域
# 4. Cookie如何进行设置的,JS能改变哪些值
要搞清楚这个问题,我们得先去看看Cookie它保存的有哪些值?
# name
这个就是Cookie的name,因此同域名下的Cookie会被name值相同的覆盖掉
# value
因为Cookie是以key-value的形式存储的,所以这里的value就是对应的属性值,且必须经过URL编码
# domain
这个就指的是域名,这里有一个小知识点,只有域名完全相同才可以共用一个Cookie,比如,www.manice.com和play.mdnice.com是无法共用一个Cookie的,不过可以通过设置domain为顶级域名就可以公用了,具体的大家可以百度,一搜一大把
# HttpOnly
这个想必大家比较的熟悉,预防XSS攻击的,设置为true后,JS脚本就无法读取到Cookie里面的值
还有一些诸如path、secure等就不一一赘述了,所以JS脚本能读到Cookie,那就可以对其进行更改
# 5. 描述一下浏览器缓存
这个部分有很多内容可以说:
# 强缓存
不需要发送HTTP请求,只会构建请求行,根据HTTP协议的不同分为两种:
HTTP 1.0中的Expires,过期时间,潜在的问题是服务器时间和客户端时间不一致
HTTP 1.1中的Cache-Control,可以设置max-age来设置缓存生效时间,超过时间段就需要重新发起请求,关于Cache-Control还有很多属性:
- max-age:资源最大有效时间
- no-cache:不缓存,但实际上每次在请求静态资源的时候会向服务端发送一个过期认证请求,需要配合ETag或者last-modified
- no-store:始终都去服务端请求最新资源,优先级最高
- private/public:在请求资源的时候,可能会经过一些CND、Nginx中间代理服务器,如果设置了private,在max-age过期的情况下,即使中间服务器提示可以使用本地缓存资源,依然会向原服务器发送请求,而public相反
# 协商缓存
需要请求头中添加tag,服务器根据tag来判断是否使用缓存,所以被称为协商缓存。tag分为两种Last-Modified和ETag
- Last-Modified
最后修改时间。在第一次请求完毕后,服务器给浏览器返回的响应头里会带有Last-Modified,浏览器在下一次请求的时候会携带If-Modified-Since,表示服务器资源最后修改时间,最后进行相应的操作。否则返回304,但只能以秒为单位,所以不够精准(不在意这几秒的差距也OK)
- ETag
ETag是给当前的文件资源添加唯一的文件标识,只要内容有改动就值就会变。服务器会将其加在响应头中,浏览器会在下次请求的时候将其作为If-None-Match字段的内容发送给服务器。服务器根据值做不同的操作
- 两者对比:
ETag优先级比Last-Modified高,因为它可以精确的判断是否需要更新。虽然性能不如Last-Modified
# 缓存位置
强缓存和协商缓存的位置按优先级排列分别是:
- Service Worker
借鉴了Web Worker,让JS运行在主线程之外,脱离了浏览器窗体,所以也无法访问DOM,但可以帮助我们实现离线缓存、网络代理等功能
- Memory Cache
内存缓存。存取最快,但寿命很短
- Disk Cache
硬盘缓存。存取慢,寿命长,空间也大,如果缓存内容过大,那么就用这种方法,否则是内存缓存
- Push Cache
推送缓存。HTTP 2.0的内容,只存储在session中,当会话结束后就会被释放,而且在Chrome浏览器中只会保存5分钟
- Cookie策略机制
它是一个用于服务端和客户端之间的认证,当服务端返回了这个cookie,那么每次请求这个域名下资源的时候(即使是二级域名也会带),都会带上这个cookie,且如果不设置cookie的过期时间,只要关闭了浏览器,cookie就会失效,所以可以通过设置max-age
另外出于安全考量,我们需要给cookie设置一个httpOnly,设置secure cookie,只有在https服务下才会在application/cookie中写入cookie
# 副作用
有的时候缓存反而碍事,因为浏览器会对html文件进行一个自动的缓存,所以我们最好在进行联调的时候设置一个:
<meta http-equiv='Cache-Control' content='no-cache' />
# 总结
首先会通过Cache-Control判断可使用强缓存
如果不可以则使用协商缓存,服务器通过判断请求头中的If-Modified-Since和If-None-Match判断资源是否更新?
- 更新:返回200以及最新内容
- 无更新:返回304以及使用缓存中的资源
# 较为稳妥的实践
先说说强缓存和协商缓存的问题点:
- 强缓存:设置了cache-control字段,那么如果服务器的资源突然更新了,用户看到的内容就不是最新的了,当然你要是能接受那就OK
- 协商缓存:每次都会去询问一次服务器资源是否有更新,还是会造成一定的资源浪费,毕竟我们是追求极致的程序员
那么最好的方案是配合Webpack:
- HTML文件:使用协商缓存
- CSS、JS和图片:使用强缓存,并且给文件名都附带上Hash值
# contentHash的讲究
当css文件hash改变的时候,js文件hash也会随之改变,所以我们可以在webpack打包的时候使用contentHash
# 6. HTTP的状态码有哪些
常见的状态码:
# 1XX
- 100:等待后续处理,比如POST请求发送大实体数据时
# 2XX
- 200:请求成功处理,且返回了请求的数据
- 204:请求成功处理,但没有返回任何实体部分
# 3XX
- 301:永久重定向跳转(重定向可能会降低网页的打开速度)
- 302:临时重定向跳转(因为是临时跳转,所以也不会进行缓存)
- 304:没有资源更新,熟悉缓存的小伙伴应该知道这个状态码
# 4XX
- 401:需要认证,前端鉴权中会用到这个状态码
- 403:对当前资源没有访问权限
- 404:没有请求的资源
# 5XX
502:服务器正常,但发生未知错误。当用户账号或密码填错时,会出现这个状态码
503:服务器处于停机维护中,也就是没有开启
# 7. HTTP的请求头包含什么
由于较多就讲几个常见的
- Connection:表示是否需要长连接。在HTTP/1.1中默认为keep-alive,可以设置close字段关闭
- Cache-Control:表示缓存机制,它有多个字段,如no-cache、no-store等等
- Cookie:用来做状态存储,解决HTTP协议中无法保存用户状态的弊端,不管请求中是否需要,都会将保存在当前域下的所有cookie传递给服务器
- 还有协商缓存相关的,If-None-Match、If-Match、If-Modified-Since、If-Unmodified-Since
- Referer:请求来源,可以用来预防CSRF攻击
- Upgrade:指定某种协议,比如指定使用Websocket协议通信
# 8. 解决跨域的方法有哪几种
同源:
protocol(协议)、domain(域名)、port(端口)三者一致。
以下协议、域名、端口一致。
http://www.example.com:80/a.js
http://www.example.com:80/b.js
解决方法:
方法比较多,不一一详解,常用的方案有CORS和Nginx反向代理
# 9. 描述一下CORS的过程
CORS这个是后端进行处理的,但前端也得懂点。CORS翻译过来就是Cross-Origin-Resource-Shared
,即跨域资源共享,它分为简单请求和复杂请求,复杂请求又会多发一次预检请求,不过预检请求也不一定就是安全的,依然要注意CSRF攻击
服务端设置了Access-Control-Allow-Origin
,也就是资源控制允许源
(感觉翻译过来怪怪的),就会开启CORS,每次浏览器在发送请求的时候都会带上Origin字段
,它由协议、域名和端口号
组成,如:
Origin: https://127.0.0.0:8080
然后让服务端判断是否可以给Origin这个源返回数据。
# 10. 描述一下JSONP的原理
因为script中的src属性不受同源策略影响,所以我们可以先在全局定义一个回调函数,然后在Ajax请求时,将函数名作为参数传递,当服务器返回数据到客户端时就会之前我们之前定义好的回调函数,这里给一下代码:
// 接收三个参数,要发起请求的URL、请求的参数、回调函数
function jsonp({ url, params, cb }) {
// 这里是拼接参数的方法
let createUrl = () => {
// 申明一个字符串
let dataStr = ''
// for...in循环遍历参数
for (let k in params) {
// 进行拼接
dataStr += `${k}=${params[k]}&`
}
// 最后将回调函数也加上去
dataStr += `callback=${cb}`
// 返回最终的结果
return `${url}?${dataStr}`
}
// 返回一个Promise对象
return new Promise((resolve, reject) => {
// 在页面上创建script标签
let script = document.createElement('script')
// 将script标签中的src属性设置为上面函数返回的参数
script.src = createUrl()
// 将script标签插入到body中
document.body.appendChild(script)
// 添加回调
window[cb] = data => {
// 成功的回调,会将我们要请求的参数作为参数传入
resolve(data)
// 执行完毕后移除script标签
document.body.removeChild(script)
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34