在浏览器中从输入url到页面的展示都经历了什么?

407 阅读37分钟

一. 整体过程叙述

  1. 浏览器自动补全协议和端口
  2. 先从浏览器缓存查找如果有并且命中就直接从缓存中获取(强缓存),没有就进入下一步
  3. 从缓存中获取DNS对应的ip,如果没有就去本地hosts获取,如果也没有DNS解析器就从DNS服务器进行获取到对应的ip地址
  4. 创建TCP链接(三次握手)
  5. 如果是https协议,还会进行ssL(TLS)协商
  6. TCP链接成功之后,进行请求数据(请求内容包括:请求行、请求头、请求体)
  7. 服务端根据请求头进行协商缓存得校验,校验成功返回304状态码;
  8. 否则服务端返回200状态码以及完整得响应数据(返回内容包括:响应行、响应头、响应体)
  9. 如果服务端返回301或302就会进行重定向(比如http重定向到https)
  10. 如果服务端返回304,就从浏览器缓存中获取(协商缓存)
  11. 浏览器收到数据之后,根据响应头中的content-type进行相应的解析
  12. 浏览器根据响应头中的内容完成缓存和cookie的设置
  13. 解析html文件
  • html解析成dom树
  • css解析成cssOM
  • 根据cssOM和dom树计算出dom树中每个节点的具体样式
  • 创建render渲染树,将可见的节点添加到渲染树中,并计算出节点的位置进行布局
  • 根据渲染树进行绘制并显示在页面上

1.png

二.被问到的相关面试题

说说DNS域名解析

DNS域名解析主要是把域名解析成对应的ip地址;而域名的组成包括:三级域名+二级域名+一级域名;
www.baidu.com 为例, www:三级域名;baidu:二级域名;com:一级域名;com后面还有.root表示根域名,因为根域名是顶端域名可以不写;
DNS服务器的三种类型:根DNS服务器,顶级域(一级域名)DNS服务器,权威(二级域名)DNS服务器; DNS解析过程:

  • 解析器向本地DNS服务器发送请求
  • DNS服务器没有找到就会咨询根DNS服务器获取到对应的一级域名的DNS服务器地址
  • DNS服务器再去咨询一级域名服务器获取到二级域名的DNS服务器地址
  • DNS服务器再去查询二级域名服务器获取到二级域名的ip,并且返回给解析器
  • 解析器再把ip返回给浏览器
    注意:DNS是基于UDP;DNS是应用层协议;DNS缓存在一定的时间内存缓存中获取;

前端可以对DNS域名解析做哪些优化?

  1. x-dns-prefetch-control:on 开启域名预解析(一些浏览器不需要设置也默认支持);
  2. dns-perfetch:设置预解析的域名
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel='dns-perfetch' href='www.XXX.com' />

DNS域名解析有什么优点?

  • 因为用户拿到的是域名而不是ip地址,因此可以防止服务器受到DDOS攻击
  • ip地址太长不便于记忆,域名相对便于记忆

说说http

http是超文本传输协议,是基于TCP的并且是无状态的,它出现在应用层,报文结构主要包括请求行+请求头+空行+请求体,响应行+响应头+空行+响应体;

http的请求和响应行(状态行)是由什么组成?

请求行:请求方式+空格+请求路径+空格+协议版本号

GET /a/b http/1.1

响应行:协议版本号+空格+状态码+空格+原因

HTTP/1.1 200 OK

GET和POST请求的区别?

  1. GET请求会被浏览器缓存下来,记录到历史记录中,而post不会;
  2. GET请求只能进行URL编码,只能接受ASCLL字符,而POST没有限制;
  3. get请求携带的参数会拼接在url后面,而post放在请求体中,因为Get请求携带的数据不安全,并且大小会被浏览器限制,而post不会;

URL和URI的区别?

URI (Uniform Resource Identifier),即统一资源标识符,是一个标识资源的字符串,可以是 URL 或 URN (Uniform Resource Name)。

URL (Uniform Resource Locator),即统一资源定位符,是一种具有特定格式的 URI,用于指定某个网络资源的地址和访问方式。URL 包含了用于查找资源的协议、主机名(或 IP 地址)、端口号、资源路径等信息。

简而言之,URI 是一个统一的标识符,而 URL 是一种特定类型的 URI,用于标识互联网上的资源位置和访问方式。

常见的请求头和响应头有哪些?

  1. content-type: 指定请求或响应的数据类型和编码格式;
  2. conetnt-Encoding: 响应数据的压缩方式;
  3. accept-Encoding: 请求数据的压缩方式;
  4. accept-language: 请求方支持的语言;
  5. content-language: 响应方支持的语言;
  6. accept-charset: 请求方数据的编码格式;
  7. content-length: 请求或响应的数据的长度;

对于定长和不定长的数据http是怎么传输的?

  1. 定长数据:对于定长数据请求和响应头都要携带content-length字段,用来执行发送和响应数据的长度;如果指定的长度小于了实际数据的长度,那么获取到的数据就是截取之后的数据,如果超出了实际数据的长度就会报错;
  2. 不定长数据:在请求或响应头中指定transfer-Encoding:chunked表示分段传输;基于长连接会持续推送数据;默认忽略content-length字符;

http怎么处理大文件传输?

  1. 客户端在请求头中指定accept-Ranges: bytes;
  2. 如果是单段传输,客户端在请求头中指定单段传输的范围;Content-Range: bytes 起始值-终止值/资源总大小
accept-Ranges: bytes;
Content-Ranges: bytes 0-20/100;  // 请求100中的0-20段
  1. 如果是多段传输,客户端请求头中指定多段传输的范围:Content-type:multipart/byteranges;boundary=00000010101;
accept-Ranges: bytes;
Content-type:multipart/byteranges;boundary=00000010101; // boundary是多段之间的分隔符

怎么解决http无状态的问题?

通过cookie,session,localStorage,sessionStorage;

  • cookie: 客户端第一次访问服务端的时候由服务端创建返回给客户端,存储在客户端;以后的每次请求客户端都会在请求头中默认的携带cookie;如果没有设置cookie的过期时间,存储在内存中那么在浏览器页面关闭的时候就被删除,如果设置了过期时间就存储在硬盘中,关闭浏览器不会被清除; js读写cookie通过document.cookie并且服务端设置为可读写的状态时;
cookie的字段:  
    name: 名称
    value:值
    Domian: 当前页的域名,限制哪些域名可以卸载此cookie;
    path: 路径, 限制哪些路径可以卸载此cookie;
    Expires:过期时间,必须转为toGMTString;到时间会被客户端删除;
    max=age: 过期时间,是一段时间间隔;
    httpOnly: 是否允许被js操作;
    secure: 只允许在https下使用
    sameSite: Node => 任何情况下都会向第三方网站发送cookie
              Lax(默认) => 只有导航到第三方链接为get的时候才会发送cookie,并且图片,iframe不会发送
              strict => 任何情况下都不会向第三方发送cookie
+ 缺点: 存储在本地不全;存储大小为4kb左右,存储量不大
+ cookie安全的措施:
    cookie中的value值进行加密;
    设置httpOnly防止XSS攻击;
    设置secure只能在https下使用;
    设置sameSite:strict只允许同源发送;
  • session: 是由服务端创建的,并且存储在服务端,把sessionId放在cookie中,每次客户端请求都会携带上cookie,当浏览器关闭session会被销毁;
    • 优点: 相对cookie较为安全
    • 缺点: 每个用户都会在服务端创建一个session,大量的用户会给服务器造成负担
  • Token: 令牌,用户登录之后,服务端会创建一个加密的token,客户端通过请求头携带token,服务端通过对比token来确定用户身份

了解http0.9、http1.0、http1.1、http2.0、http3.0的区别吗?

  • http0.9: 早期主要用于网页传输,没有响应和请求头
  • http1.0: 可以设置请求和响应头,根据请求头的不同返回不同的资源,但是每次请求都需要创建tcp连接;
  • http1.1: 默认开启长链接,管道化处理,队头阻塞(http一次只能处理一个请求),可以设置cache-control缓存信息;
  • http2.0: 使用了二进制分帧,多路复用(同一个域名下的请求都使用同一个TCP链接),头部压缩,服务器推送;
  • http3.0: 采用了UDP传输协议,解决tcp链接耗时的问题,使用QUIC协议完成数据的可靠传输;

为什么http1.1不能实现多路复用?

1.1的版本中采用的是文本传输,不能区分哪个响应对应哪个请求;

讲解下http2.0中的多路复用?

2.0中采用二进制分帧进行传输数据,帧代表数据的最小单位,每一帧中有标识表示它属于哪个数据流,每条数据流由多个帧组成,多路复用就是一个TCP连接中存在多个数据流,通过帧中的标识可以区分出该数据流属于哪个请求;

什么又是https?

  • https是http的基础上进行了SSL或TSL,它的默认端口号是443,它相对于http比较安全(http+证书认证+数据传输加)
  • TSL|SSL流程:
    • 客户端向服务端发送请求,会携带上TSL版本,加密套件,随机数这里表示为1
    • 服务端接收到请求之后会携带TLS版本,加密套件,随机数2,证书和公钥
    • 客户端接收到服务端的响应之后,验证证书,并且使用公钥对预主密钥进行加密,把加密之后的密钥和生成的随机数3发送给服务端
    • 服务端接受到数据之后,使用私钥对密钥进行解密获取到预主密钥
    • 客户端和服务端就可以使用随机数1,随机数2,随机数3和预主密钥进行加密生成会话密钥进行传输数据的加密 注意:它用到了混合加密,非对称和对称加密,通过非对称加密进行加解密预主密钥,通过对称加密对传输数据进行加密,非对称加解密比较消耗性能,上面使用的随机数保证数据是否被篡改过;TSL是SSL的升级版;它介于传输层和应用层之间;

https的SSL和TLS(握手过程中)中客户端如何验证证书的合法性?

  • 校验证书的颁发机构是否受客户端信任
  • 通过CRL验证证书是否被吊销
  • 对比系统时间,验证证书是否过期
  • 判断证书的网站域名是否和证书颁发的域名一致

https中数据的传输都是通过加密传输的,为什么还需要数字证书呢?

数据传输虽然是加密传输,确保了数据不会被篡改,但是黑客也可以劫持DNS获取到对应的ip地址,把ip地址改成黑客自己的服务器ip地址,这样就被攻击了,数字证书就是服务器向浏览器证明自己的身份;

说下TCP

  • 传输控制协议,应用在传输层,负责端到端的链接并且是面向字节流的,它的传输是可靠的,流量可控制,拥塞可控制,并且是全双工;

说下TCP的三次握手和四次挥手

  • TCP三次握手
    • 由于tcp是全双工的,因此客户端和服务端都可发起连接;假如客户端发起连接;

    • 客户端发送syn包和一个随机序列号到服务端表示要建立链接,客户端进入SYN_send状态;

    • 服务端接收到SYN包之后,响应一个SYN和ACK包、一个随机序列号和确定号,确认号为客户端的序列号加1,服务端进入SYN_RCVD状态;

    • 客户端接收到服务端的响应之后再次发送ACK包进行确认,携带序列号(第一次的序列号加1)和一个确认号(服务端返回的序列号加1);客户端进入到estab_lished状态,服务端接收到之后也进入这个状态;表示连接建立完成;

image.png

  • 四次挥手
    • 由于tcp是全双工的,因此客户端和服务端都可发起断开;假如客户端发起断开;

    • 客户端向服务端发送FIN包和一个随机序列号表示要断开链接,客户端进入FIN-wait1状态;

    • 服务端接收到客户端的请求之后发送ACK包和一个随机序列号,确认号(确认号为客户端的序列号加1)进行响应;此时服务端进入close-wait状态,客户端进入FIN-wait-2状态;此时服务端还有未发送完的数据,等待一会之后确定数据发送完毕进行下一步;

    • 服务端发送FIN包,ACK包,一个随机序列号和确认号(客户端的序列号加1)表示确认断开,此时服务端进入last-ack最后确认状态;

    • 客户端接收到响应之后再次发送ACK包,序列号(第一次握手的序列号加1)和一个确认号(服务端的序列号加1),客户端进入time-wait状态,服务端接收到之后进入close状态;客户端等待2MSL之后进入close状态;

image.png

序列号:去除重复的数据,按照序列号接收,可以确定发出的数据包哪些被接收了;
确认号:为了确保这是同一个请求链接;
TCP报文中有SYN(Synchronization同步),ACK(acknowledgment确认),FIN(finish结束);

TCP握手为什么是三次不是两次?

  • 因为两次或三次服务端响应客户端之后,无法确定客户端是否接收到了响应,而客户端再次请求就是为了确保客户端接收到了服务端的响应
  • 防止历史连接错乱,同一个请求进行多次连接,刚开始客户端发送syn进行连接,由于网络问题造成阻塞,超时之后客户端会进行重传,此时如果发送成功,服务端就会携带syn和ack进行响应;这时旧的syn也发送出去了,那么服务端接收到之后也会进行响应,那么就会进行两次链接;
  • 避免资源浪费,客户端发送了SYN到服务端由于网络阻塞,超时会进行重传,多次发送SYN,如果没有第三次握手,服务端会进行多次响应,造成资源浪费;
  • 确保双方的序列号被可靠的同步;

TCP挥手为什么是四次而不是三次?为什么发送方最后进入time-wait状态?

如果没有第四次,那么在第三次服务端向客户端发送FIN包之后就处于断开,此时由于网络原因造成阻塞,客户端一直没有接收到FIN包,那么客户端会一直重传;
如果最后没有time-wait状态或2MSL状态,第四次挥手,客户端向服务端发送完ACK包,就处于close状态,由于网络问题造成阻塞服务端一直未收到ACK包,服务端就会一直重复第三次挥手,并且一直处于close-wait状态;

TCP第一次握手的时候,SYN丢包了会怎样?

如果第一次握手,客户端在规定时间内没有接收到服务端的响应,客户端会重传SYN,每次发起重传的时间间隔都是翻倍的,比如1秒、3秒、7秒、15秒、32秒等;如果重传超过了设置的次数(tcp_syn_retries),那么就不再重传;

TCP第二次握手SYN和ACK丢包会怎样?

客户端在规定时间内没有接收到服务端返回的SYN和ACK,那么客户端会重传SYN到服务端,服务端没有接收到客户端的第三次请求也会重传SYN和ACK;超过了重传的次数(tcp_synack_retries)就会停止;

TCP第三次握手丢包会怎样?

第三次丢包,服务端会重传SYN和ACK,直到达到重传的次数(tcp_synack_retries),超过之后就会自动断开TCP连接;客户端没有收到服务端的包,会一直处于重传SYN的状态;直到超过了tcp_retries2的次数;此时客户端处于ESTABLISHED状态,通过保活机制,在指定时间内没有任何链接相关的活动,隔一段时间向服务端发送探测报文,连续几次都没有得到响应,那么认定TCP连接死亡,把错误通报给上层应用;

SYN攻击?

客户端伪造大量的ip,在短时间内发起大量的请求,客户端就会和服务端建立tcp链接,在第二次握手的时候由于客户端的源ip是伪造的,因为第二次握手一直不会成功,服务端在超时之后就会进行重传,这样大量的请求和重传就会导致半连接队列溢出,导致服务瘫痪崩溃;这就是典型的DOS攻击;

TCP怎么解决丢包和乱序问题?

tcp会有一个数据缓存区域,可以把数据分成多个包进行一次或多次发送,每个包的报文由序列号,长度和数据组成;接收端接收到数据之后,会回复ACK进行确认,ACK就是接收到的数据包中的序列号加长度,也就是发送方发送下一条数据的起始序列号,这样就可以防止乱序的问题;
如果中间数据包有丢失,那么接收方在接收到数据之后会通过序列号进行重组,发现不是连续的数据包,就会再次发送ACK为缺失的序列号让发送端进行重传;这样就解决了丢包的问题;

TCP和UDP的区别?

  • TCP和UDP都是应用在传输层,而TCP需要确认链接,分包传输,而UDP无需确认链接并且不进行分包。TCP相对于UDP是比较安全可靠,但是需要三次握手比较耗时,UPD会丢包,但是建立比较快,经常运用在视频和聊天中

说下OSI模型

  • 七层
    • 应用层 -> 表示层 -> 会话层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层
  • 五层模型从上到下
    • 应用层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层
  1. 应用层:实际的应用场景,比如网页,邮箱;代表协议有http,ftp等;
  2. 传输层:主要面向传输过程可以选择不同的传输过程,代表有tcp和udp;
  3. 网络层:定位到具体的目标,比如ip,ARP等
  4. 数据链路层:将数据可靠的传输到目标,比如以太网;
  5. 物理层:物理设备,网线,光纤等;

http和TCP分别在哪一层?

  • http在应用层
  • TCP在传输层

http的状态码有哪些?

http的状态码分以下五大类:

  1. 1XX: 表示目前处于处理状态;
  2. 2XX: 表示成功状态;
    • 200:表示成功状态,有完整的响应数据;
    • 204: 表示成功状态,但是没有响应体;
    • 206: 表示成功状态,会返回部分响应数据,常见的场景为分片下载和断点续传;
  3. 3XX:重定向状态;
    • 301: 永久重定向,重定向的地址会缓存在客户端,下次访问直接从缓存中获取地址进行请求;
    • 302:临时重定向,客户端不会缓存重定向的地址,每次请求都会从服务端获取;
    • 304:协商缓存;
  4. 4XX: 客户端的错误;
    • 400:客户端笼统的错误;
    • 403: 客户端携带了敏感信息或非法信息,服务器禁止访问;
    • 404:访问的地址不对;
    • 405:请求的方式不对;
  5. 5XX: 服务端的错误;
    • 500:服务器笼统的错误;
    • 501:客户端请求的功能不支持;
    • 502:网关或代理服务器错误;
    • 503:服务器很忙,暂时无法响应;

Http的301和302状态码和应用场景?

  • 301表示永久重定向,服务端返回301状态码,客户端会进行缓存重定向的地址,后续直接从缓存中获取;301适合永久转移的场景,比如域名的变更;
  • 302表示临时重定向,客户端不会缓存重定向的地址;302适合临时转移的场景,比如首页跳到活动页;

大文件如何断点续传?

客户端将文件的二进制内容分成多片,每片都按照顺序标识一个序列号,上传的时候把序列号也传递上去,服务端接收到之后,记录每片的序列号;如果终止上传之后再次上传,客户端先向服务端要一个已经上传的序列号,客户端就可以把未上传的片段再次上传;全部上传完成之后,服务端按照序列号进行顺序组装分片;

GET和POST区别?

  1. get请求不会携带请求体,直接拼接在链接后面,因此受浏览器的影响,它的大小有限制;
  2. post请求会把数据放在请求体中,没有大小的限制;
  3. get请求只能传递ASCLL数据,非ASCLL数据会进行编码,post没有这个限制;
  4. get的参数拼在链接后面,因此不安全,对于安全的数据应该使用post传输;
  5. get链接会被浏览器保存到历史记录中,可以再次打开,若打开post链接会提示是否重新提交;

什么是CDN,它是干嘛的?

  • CDN(content-delivery-network)内容分发网络,缓存源服务器的资源用户访问的时候直接从最近的CDN服务器上返回不用直接访问源服务器,它是用来提高资源的下载速度,让用户能够从距离最近的CDN服务器进行下载,减少了路由的次数,提升了访问速度

从CDN获取资源的流程是什么

  • DNS解析器向DNs服务器发起请求获取对应的ip地址,如果有CDN就把解析权交CNAME指向的CDN专用的DNS服务器
  • CDN的DNS服务器会把负载均衡的服务器ip返回
  • 浏览器向负载均衡的服务器发送请求,获取到一个适合的CDN缓存服务器的ip(适合的服务器是选择一台距离最近的,具有访问资源的并且负载最小的CDN服务器)
  • 浏览器访问CDN缓存服务器,如果有缓存就返回缓存,没有缓存就从源服务器获取返回

15.png 图片来源于网络

CDN缓存策略是什么?

  • CDN节点缓存一般遵循http协议
  • 先根据源服务器的响应头中的缓存数据进行缓存,如果没有缓存数据
  • 根据CDN控制台配置的缓存规则进行缓存,如果没有配置
  • 根据CDN默认的缓存规则,不同平台的CDN默认缓存规则不同 注意: CDN只对get请求进行缓存;只缓存静态资源;当源服务器修改了资源文件之后,CDN缓存此资源的时间没有过期,那么用户再次访问还是获取到的是旧的缓存文件
    --- 通过强制刷新或修改链接的参数可以清除缓存

说下前端缓存

前端缓存有两种分别是强缓存和协商缓存

缓存

强缓存优先于协商缓存,若强缓存生效直接采用强缓存,否则采用协商缓存;协商缓存是由服务器决定是否使用缓存,如果协商缓存失效返回200,如果生效返回304;

强缓存

强缓存主要可以通过http头中的Expires和Cache-control两个字段来控制,如果两个字段都存在Cache-control优先于Expires;如果命中强缓存,浏览器不再发起请求,直接从缓存中获取;

  • Expires:是HTTP1.0中的属性,只能设置于响应头中,其值表示缓存的时间是服务端的时间,在这个时间段内再次访问就会直接从缓存中获取,它和客户端的时间进行比较;
    优点:可以设置过期时间;
    缺点:到了过期时间,不管文件是否被修改都直接从服务端获取;依赖于客户端的时间,如果时间不准确导致缓存失效;

  • Cache-control: 是HTTP1.1中的属性,可以设置于请求和响应头中,可以组合使用多条属性,属性之间通过,连接;优先级高于Expires;

- 响应头中的Chache-control属性
    1. max-age:过期时间,单位是秒,相对于请求时间;
    2. s-maxage: 用于代理服务器上的缓存过期时间,优先级高于max-age;
                 配合public一起使用;
    3. no-cache: 只能终端浏览器缓存,每次使用缓存前需要向服务端进行确认是否过期;
    4. no-store: 禁止任何缓存;
    5. public: 被任何节点缓存;
    6. private: 只能被终端浏览器缓存
- 请求头中的Cache-control属性
    1. max-stale: 过期之后的几秒内还是有效的;
    2. min-fresh: 缓存到期前的几秒前有效;
    3. only-if-cache: 只获取代理服务器的缓存,如果代理服务器上的缓存失效直接返回5044. no-cache: 不使用强制缓存,使用协商缓存;
协商缓存

如果没有命中强缓存,浏览器会请求服务器,服务器会进行协商缓存的校验,协商缓存主要通过以下四个属性进行判断是否命中;Etag和If-None-Match,Last-Modified和If-Modified-Since;它们两两组合使用;如果命中协商缓存,服务端返回304,浏览器从缓存中获取;

Etag: 存在于响应头中,资源标识;根据资源大小和修改时间生成;
If-None-Match: 存在于请求头中,上一次服务端返回的Etag的值,服务端会通过这个值和服务端的Etag进行对比,判断资源是否被修改;

Last-Modified: 存在于响应头中,资源最近修改的时间;
If-Modified-Since: 存在于请求头中,上次服务端返回的Last-Modified的值;服务端会通过这个值和服务端的Last-Modified的值进行对比,判断资源是否被修改过;
  • Last-Modified和If-Modified-Since 客户端请求服务端的时候,服务端响应头中会携带Last-modified字段,表示修改的时间,精确到秒;客户端再次请求的时候会携带If-Modified-Since字段,它的值为服务端返回的Last-Modified字段的值,服务端会比较两个值,如果不一样表示修改过资源,直接返回资源状态为200;如果一样表示未修改过,直接返回304状态码,浏览器从缓存中获取;它们出现在HTTP1.0中;

缺点:精确到秒,在最后一秒即修改了资源同时又获取资源,那么还是会命中协商缓存;

  • Etag和If-None-Match 客户端请求服务端的时候,服务端响应头中会携带Etag字段,Etag由资源大小和修改时间计算生成;客户端再次请求的时候请求头中携带If-None-Match字段,其值为服务端返回的Etag字段的值,服务端会对比两个值,如果一样直接返回304,否则返回新的资源和200状态码;它们出现在HTTP1.1中;它们优先级高于Last-Modified;

优点:解决了时间精确到秒的问题;
缺点: 相比Last-Modified比较消耗性能;

普通刷新和强制刷新
  • 普通刷新(F5):不使用强制缓存,使用协商缓存;
  • 强制刷新:不使用任何缓存,直接从服务器获取资源;
小结

客户端首次请求资源的时候,服务端返回资源和状态码200,客户端会把响应头,请求时间和资源缓存起来;下一次请求的时候,浏览器会从缓存中获取到对应的资源,比较本次请求时间和缓存中对应的时间的差,如果这个差值没有超过了响应头中的cache-control的max-age的值,或缓存中的时间没有超过Expires的值。那么就直接获取缓存中的资源返回200状态码;否则请求服务器;请求头中携带If-None-Match或If-Modified-Since字段;服务端会通过Etag或Last-Modified字段和If-None-Match或If-Modified-Since的值进行比较,判断值是否相同,相同就命中协商缓存直接返回304,客户端从缓存中获取,否则没有命中协商缓存,返回新的资源和200状态码;

9.png

浏览器缓存在什么地方?

浏览器缓存在三个地方分别为:from memory, from disk cache, from Serviveworker

  • from memory:缓存在浏览器内存中,随着页面的关闭而消失
  • form disk cache: 缓存于硬盘中,持久缓存,需要手动清理,并且可跨站点跨页面访问
  • from Serviceworker: 永久性缓存,需要在浏览器中手动清除

说下前端的垃圾回收机制?如何优化?

js中的垃圾回收是自动清除的,他的作用主要是防止页面占用内存过大导致卡顿;

v8引擎的垃圾回收主要由两部分部分组成,栈和堆,对象存储在堆中,垃圾回收主要在堆中

堆的组成:
  • New Space(新生代)
    由space from和space to组成,这两块的空间是严格对半分,存储暂时的对象,在64位操作系统中的内存大小为64M,32位的操作系统中的内存大小为32M;使用到的算法为Scavenge(新生代互换)
    Scavenge(新生代互换):  
    当space from内存达到一定程度的时候,继续添加的话,就会删除掉没有引用的对象;  
    把剩下的对象复制到space to中,把新的对象添加到里面;  
    把space from变为space to,space to变为space from;
  • Old Space(老生代) 由old pointer space和old data space组成,存储持久性的对象,如果新生代中执行过一次垃圾回收并且space to的内存已经使用了25%就会把对象存储到老生代;在64位操作系统中内存为1400M,32位为700M;使用到了Mark-Sweep(标记清除法),Mark-Compact(标记整理法)和引用计数法;
  1. 引用计数法
    对象没有被其他对象应用的时候就是0引用可以被清除
    缺点:两个对象循环引用无法被回收,因为引用永远不会为0
  2. Mark-Sweep(标记清除法)
    从根对象开始广度扫描
    把根引用的对象一层一层的进行标记
    垃圾回收执行的时候把没有标记的对象进行清除
    缺点:空间碎片化
    优点:解决了循环引用问题,只要没有被根对象引用的就会被清除
  3. Mark-Compact(标记整理法)
    从根对象开始广度扫描
    把标记的对象进行整理,放在连续的空间中
    把剩余的空间连续空间中的对象进行清除
    优点:连续的空间便于存储更大的数据 而在v8引擎之前用到的标记是停顿标记法,现在用到的是增量标记和三色标记法
  4. 停顿标记法
    一次广度扫描把根对象引用的对象全部找到进行标记
  5. 增量标记和三色标记法
    相对于停顿标记法,垃圾回收机制会频繁执行
    每次执行都会标记一层,把根对象标记为灰色把根对象的下一层标记为黑色, 再次执行的时候会把根对象标记为白色把黑色一层标记为灰色,把黑色的下一层标记为黑色
    优点:相对于停顿标记耗时较短
  • Large object space
  • code space
  • cell space
  • Property Cell Space
  • Map space
调整node运行内存:
    node max-old-space-size = 内存大小 (最大运行内存一般是空闲内存的75%)
查看浏览器内存的情况
   window.performance

为什么css放在header中而js放在底部?

  • css放在底部的话,页面的渲染是从上到下的,边解析边渲染,等到Css解析的时候dom树已经渲染一部分,当再次解析css的时候会引起回流和重绘,页面已经呈现出结构了但是没有样式
  • js放在底部是因为js的引入会阻塞页面的解析

defer和async区别?

  • defer:js异步加载,等到页面解析渲染完成之后才会执行
  • async:js异步加载,加载完毕之后就会立即执行并且会阻塞页面的解析

2.png

DOMContentLoaded和load的区别?

  • DOMContentLoaded:是在页面解析完毕之后执行
  • load:页面渲染完毕包括图片音频视频渲染完成

什么是重绘?什么是回流?怎么避免回流?

  • 重绘:元素的颜色,visiblty显示和隐藏的变化
  • 回流:元素的大小,位置发生变化,就会触发重新布局,重新生成渲染树,浏览器窗口的变化也会引起回流
    注意:回流一定会触发重绘,重绘不一定会回流
  • 避免(优化):
    1. 使用文档碎片fragment去批量操作dom
    2. 使用class等去批量操作css
    3. 分离读写,操作dom的就放在一起,读取的放在一起(现在浏览器都有渲染队列的机制)
eg:
    不应该
     a.style.width = '100px'
     const h = a.style.height
     a.style.color = 'red'
    应该:
     // 写
     a.style.width = '100px'
     a.style.color = 'red'
     // 读
     const h = a.style.height
     浏览器解析完第一行之后不会立马去修改,而是等待一会解析完下一行看看是否也是操作dom,
     如果是就再解析下一行,如果不是就立马修改
    1. 动画应该放在position:absolute或fixed的元素上从而脱离文档流
    2. css3硬件加速器(GPU加速) tranform、opacity等会触发硬件加速,不会引起回流和重绘,但是不能过多的使用,过多的使用会占用大量的内存,造成性能负担
    3. 避免使用Table布局和css表达式

并发优化,发送N个请求,但是服务只能同时处理三个请求,封装一个函数,不断的发送发送完N个请求(实现 promise.map,限制 promise 并发数)

class Map {
    constructor (list, n) {
        // 存储实际请求的数量
        this.origLen = list.length
        // 存储请求
        this.list = [...list]
        // 一次最多发送的条数
        this.len = n
        // 当前发送了几条
        this.index = 0
        // 存储返回结果
        this.result = []
    }
    // 开始执行
    start () {
        return new Promise(res => {
            // 首次执行n次
            for (let i = 0; i < this.len; i ++) {
                this.doNext(res)
            }
        })
    }
    // 执行每个请求
    async doNext (resolve) {
        // 如果当前下标小于最大请求个数,并且请求列表有值
        if (this.index < this.len && this.list.length) {
            // 下标加1
            this.inde++
            // 拿出一项
            const p = this.list.shift()
            // 执行请求 错误信息也要返回
            const r = await p().catch(e => e)
            // 请求返回之后下标-1
            this.inde--
            // 执行下一个请求
            this.doNext(resolve)
            // 结果存储起来
            this.result.push(r)
        }
        // 全部请求完成之后返回resolve
        if (this.origLen === this.result.length) {
            resolve(this.result)
        }
    }
    }
    function a () {
    return new Promise(res => {
        console.log('执行了1')
        setTimeout(() => { res(1) }, 3000)
    })
    }
    function b () {
    return new Promise((res,rej) => {
        console.log('执行了2')
        setTimeout(() => { rej(2) }, 1000)
    })
    }
    function c () {
    return new Promise(res => {
        console.log('执行了3')
        setTimeout(() => { res(3) }, 100)
    })
    }
    const map = new Map([a,b,c], 2)
    map.start().then(res => {
    console.log(res) // [2,3,1]
    })

XSS和csrf攻击?

  1. XSS:跨站脚本攻击,是指客户端执行了未知的脚本;
  • XSS的危害:获取到cookie等本地存储的数据;修改网页内容;劫持页面的跳转;利用当前用户的身份做一些恶意的事情;
  • XSS攻击有三类:反射型,存储型和dom型;
    • 反射型:一个链接中携带恶意的js代码参数,打开这个链接会发送到服务端,服务端处理之后再返回给客户端,客户端执行了这段代码;
    • 存储型:客户端提交数据到服务端,数据中携带恶意的代码,服务端把数据存储到数据库,后续获取的时候从数据库中获取到返回到客户端;
      场景:评论区提交的时候植入一段恶意代码,被用户提交到了服务端存入到数据库中;
    • dom型:客户端使用eval执行一段字符串代码,恶意代码被放入到字符串中; 防范措施:
    1. 服务端进行转义和过滤,把箭头符号,斜杠等进行转义和过滤;
    2. cookie设置httponly防止js修改
    3. html中设置csp浏览器安全策略,只允许加载执行域名下的脚本和样式
    4. js中尽量不使用eval或v-html
  1. CSRF:跨站点请求伪造,在用户已登录的网页上执行一些恶意的操作;用户访问一个危险的网站,当用户访问自己的网站的时候,网站会向被攻击的站点发送一个请求,请求中会携带cookie,这样就可以伪造用户的身份了;
  • 避免CSRF攻击:不使用cookie,或设置cookie中sameSit同源;服务端检查Referer;
  1. SQL攻击 把SQL命令通过表单等方式提交到服务端,在服务端执行这些恶意的sql命令;
    原理:服务端在执行sql的时候,可能会拼接前端传递的参数,比如用户登录,服务端可能通过传递过来的用户名或密码进行查询sql,那么用户名或密码是恶意Sql,就会被执行;

防范措施:对客户端传递的参数进行校验;机密信息通过加密存储;

怎么解决跨域?

跨域:受浏览器同源策略的影响,协议,域名,端口三者相同就是同源;

  1. 使用JSONP: 客户端准备好一个接收服务端数据的函数,并且创建一个script元素,src指向请求地址并且参数携带函数名;服务端会返回一个执行该函数的脚本,从而客户端就可以接收到数据了;它的优点兼容性好;缺点:只支持Get方法的请求;
  2. cors(跨域资源共享): 浏览器就cors请求分为两种,一种是简单请求,一种是非简单请求,对于简单的请求客户端会在请求头中自动添加origin字段,告诉服务器来自于哪里,服务端会响应一些头部字段表示是否允许跨域访问;对于非简单请求,浏览器会先进行预请求,询问服务端是否允许本次请求;
  3. 设置代理服务器

预加载和预连接和预解析

  1. 预加载:prefetch和preload,告诉浏览器提前加载一些文件;它们只会提前加载不会执行;
  • prefetch: 利用浏览器空闲时间来下载指定的资源,在使用的时候浏览器会从prefect的缓存中获取该资源;
<link rel='prefetch' href="xxx.xx.js" />
  • preload:指定哪些资源在页面加载完成之后就需要。不会阻塞页面的初次渲染;
// as指明作为什么类型
<link rel='preload' href="xxx.xx.js" as="script />
  1. 预连接:preconnect,提前进行连接,可以提前完成dns寻址,tcp握手等过程;连接成功之后只会保留10秒,并且有连接次数限制;

  2. 预解析:dns-prefetch,预算解析DNS;

事件模型

事件触发的三个阶段
  • 捕获阶段: 从最外层往事件目标处传播,遇到注册的捕获事件会触发;
  • 目标阶段:传播到目标处触发目标事件;
  • 冒泡阶段:从目标处向外传播,遇到注册的冒泡的事件会触发; 捕获阶段 -> 目标阶段 -> 冒泡阶段
捕获事件和冒泡事件

捕获事件:从外向里传播的过程,从最外层元素传递到目标元素; 冒泡事件:从里向外传播的过程,从目标元素传递到最外层元素的过程;

// 第三个参数为true表示设置捕获,默认触发冒泡
addEventListener(事件名,回调,是否触发捕获或冒泡)
如何阻止捕获和冒泡的事件
  • 捕获是无法阻止的,只要不设置捕获事件就不会被捕获
  • e.stopPropagation()阻止冒泡
  • e.preventDefault()阻止默认事件
事件委托

事件委托:点击目标元素,对应的事件绑定在父元素上,触发父元素上的这个事件;可以利用事件冒泡实现事件委托;直接给外层元素添加事件,通过event.target对象可以获取到具体操作了哪个元素;
优点:减少事件的绑定和监听,可以动态的添加子元素,不用给添加的子元素添加事件;

// 通过事件委托给点击的每个li添加颜色,这样就不用给每个li元素添加一个事件,只需要给ul添加一个事件;
<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
document.getElementByElement('ul')[0].addEventListener('click',function (e) {
    e.target.style.color = 'orange'
})

event.target和event.currentTarget是不相等的,target表示当前操作的元素对象,而currentTarget是当前绑定事件的对象;

前端路由

hash和history模式

hash模式

通过location.hash来修改hash连接的变化,通过onhashChange来监听hash的变化来执行显示对应的页面;hash值的变化不会引起页面的刷新,只是地址栏中hash的值变化了;

onhashChange

地址栏中的hash值变化了,或者点击浏览器的前进后退按钮引起hash值的变化都可以通过onhashChange事件监听到;

history模式

通过window.history记录了当前窗口的浏览历史记录,可以通过length属性获取到当前窗口的历史记录的长度,但是不能获取到具体的地址;
通过以下方法修改地址栏中的地址:

  1. history.pushState():向历史记录中添加一条数据,不能引起页面的刷新;
  2. history.replaceState():替换历史记录中的一条数据,不能引起页面的刷新;
  3. history.go(n):向前或向后跳转n次,超出历史记录不做跳转,如果为0刷新当前页面;
  4. history.back(): 向后移动一页;
  5. history.forward(): 向前移动一页;

通过以下方法监听history地址的变化:

  1. popsState可以监听到通过go,back,forward修改地址的变化;
  2. 对于replaceState 和 pushState方法改变的地址,可以通过重写这两个方法实现监听地址的变化;