HTTP与浏览器面试题汇总

172 阅读1小时+

一、HTTP协议

1、浏览器常见的请求方法、GET/POST/PUT请求区别

(1)常见请求方法

请求方法说明
GET向服务器请求获取数据,一般用于查询
POST常用的请求,通常会造成服务器资源的修改
PUT上传文件,更新数据
DELETE删除服务器上的数据
HEAD获取报文首部,与GET方法返回的首部一致,与GET相比,不返回报文主体部分
OPTIONS询问支持的请求方法,用来跨域请求
CONNECT要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信
TRACE回显服务器收到的请求,主要用于测试或诊断
PATCH用于对资源进行部分修改

HEAD场景:在下载一个大文件之前,先获取文件大小,再决定是否下载文件,以此节约带宽资源

(2)GET和POST

区别GetPost
应用场景get请求是一个幂等的请求,多次重复请求不会对服务器产生影响,一般用于请求网络资源(多次重复请求没有额外的副作用产生破坏)post请求不是幂等请求,一般用于对服务器产生影响的场景,比如注册用户等操作
是否缓存浏览器一般会对get请求缓存很少对post请求缓存
发送的报文格式报文中实体部分为空报文中的实体部分一般为向服务器发送的数据
安全性不安全,参数放在url中,会保存在历史栈中较安全
请求长度限制长度(浏览器对url的限制)-
参数类型字符串(url的查询字符串)Post请求支持传递更多的数据类型

(3)POST和PUT

  • PUT 向服务端发送数据,更新资源,是幂等的
  • POST 是非幂等的

PUT和POST都可以给服务器发送新的资源,但是PUT是幂等的,如果没有资源,PUT会创建资源,如果已有资源,会更新资源,可以说多次执行PUT与一次执行并没有很大额外的区别。但是对于POST,非幂等性,多次请求会创建多个新资源。

通常情况下,PUT的URI指的是单一资源,POST的URI指的是资源目录。

(4)PUT和PATCH

PUT和PATCH都可以更新资源,但是PUT是整体资源更新,PATCH可以对已知资源进行部分更新

(5)OPTIONS

OPTIONS:用于获取目的资源所支持的通信选项。

主要途径:

  • 获取服务器支持的所有HTTP请求方法
  • 用来检查访问权限
    • 在进行CORS跨域资源共享时,对于复杂请求,使用OPTIONS发送嗅探请求,判断是否支持跨域访问

(6)GET请求的URL长度限制

HTTP协议并没有对GET请求做URL长度的限制,这个限制是浏览器/服务器对它的限制。

IE对URL的允许值是最小的,2083字节(2K+35),日常开发只要不超过这个限制就不会有问题。

2、常见的HTTP请求头和响应头

请求头说明
Accept浏览器能够处理的内容类型
Accept-Charset浏览器能够处理的字符集
Accept-Encoding浏览器能够处理的压缩编码
Accept-Language浏览器当前设置的语言
Connection浏览器与服务器之间连接的类型(是否长链接)
Cookie当前页面设置的Cookie
Host请求发送的目的地域
Referer发出请求的页面url
User-Agent浏览器的用户代理字符串,帮助获取用户设备信息
  • host:描述请求将被发送的目的地,仅包括域名、端口号,HTTP/1.1
  • origin:表明请求来自于哪个站点,仅包括协议、域名、端口,经常用于CORS请求或者POST请求
  • referer:告知服务器请求的原始资源URI,包括协议、域名、路径、查询参数
  • http2的referer不包含路径
响应头说明
Date表示消息发送的时间,时间描述格式由RFC822定义
server服务器名称
Connection浏览器与服务器之间连接的类型(是否长链接)
Cache-Control控制浏览器的缓存
Content-Type表示内容文档类型
Content-Type说明
text/xml提交XML格式的数据
application/json服务器消息主体是序列化之后的JSON字符串
multipart/form-data表单上传,可用于传输二进制
application/x-www-form-urlencoded提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL转码

(1)Connection:keep-alive

HTTP1.0默认使用短连接(每次请求都要建立TCP连接),HTTP1.1默认保持长连接,使用keep-alive模式使得客户端到服务器端的连接长期有效。

  • 建立过程

    • 客户端向服务器发送请求报文的同时,在首部添加Connection字段
    • 服务器收到请求并处理 Connection字段
    • 服务器回送Connection:Keep-Alive字段给客户端
    • 客户端接收到Connection字段
    • Keep-Alive连接建立成功
  • 服务端自动断开过程(也就是没有keep-alive):

    • 客户端向服务器只是发送内容报文(不包含Connection字段)
    • 服务器收到请求并处理
    • 服务器返回客户端请求的资源并关闭连接
    • 客户端接收资源,发现没有Connection字段,断开连接
  • 客户端请求断开连接过程:

    • 客户端向服务器发送Connection:close字段
    • 服务器收到请求并处理connection字段
    • 服务器回送响应资源并断开连接
    • 客户端接收资源并断开连接
  • 开启Keep-Alive的优点:

    • 较少的CPU和内存的使⽤(由于同时打开的连接的减少了);
    • 允许请求和应答的HTTP管线化;
    • 降低拥塞控制 (TCP连接减少了);
    • 减少了后续请求的延迟(⽆需再进⾏握⼿);
    • 报告错误⽆需关闭TCP连;
  • 开启Keep-Alive的缺点:

    • 长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源。

(2)强缓存

HTTP1.0中使用Expires,HTTP1.1中使用Cache-Control

  • Expires

    • 过期时间,由服务端在响应头中返回,告诉浏览器在这个时间之前直接从缓存中读取数据
    • 但是服务器的时间和浏览器的时间可能并不一致,在Cache-Control出现之后,Expires被抛弃了
  • Cache-Control

    • 过期时长
    • max-age=3600,代表1h之内可以使用缓存
    • public:客户端和代理服务器都可以缓存
    • private:只有浏览器可以缓存
    • no-cache:跳过强缓存,直接进入协商缓存
    • no-store:不使用缓存
    • s-maxage:针对代理服务器的缓存时间

(3)协商缓存

每一次获取资源之前,比较本地缓存与网络资源,若网络资源有更新,则重新请求资源,否则使用本地缓存,优先判断Etag,如果没有Etag,再根据Last-Modified判断。

协商缓存是利用【Last-Modified,If-Modified-Since】【Etag、If-None-Match】这两对来实现的。

请求头响应头
If-None-MatchEtag
If-Modified-SinceLast-Modified
  • Last-Modified:

    • 文件最后修改时间,浏览器第一次给服务器发送请求时,服务器会在响应头中加上这个字段
    • 浏览器如果再次请求,会在请求头携带If-Modified-Since,这个字段的值是第一次请求缓存的Last-Modified响应头
    • 服务器会比较浏览器传过来的If-Modified-Since和本地资源的最后修改时间,相同则返回304
  • Etag:

    • Etag是服务器根据当前文件内容给文件生成的唯一标识,只要内容有改变,Etag会变,并通过响应头再把这个值给到浏览器
    • 浏览器再次请求会将服务器返回的Etag作为If-None-Match的值传给服务端
    • 服务端比较If-None-Match和Etag,相同则返回304,否则返回新资源

Etag在精确度上优于Last-Modified,Etag是按照内容资源生成标识,内容变化,Etag也会变化。但是Last-Modified在一些特殊情况下并不能准确感知资源变化:

  • 编辑资源文件,但是文件内容并没有发生变化,这样也会造成缓存失效
  • Last-Modified能够感知的单位时间是秒,如果在1s内多次修改文件,Last-Modified并不会发生变化
  • 在性能上,Last-Modified优于Etag,Last-Modified只是记录一个时间点,Etag需要根据文件的具体内容生成hash值

浏览器中的缓存位置一共有4种,按优先级从高到低排列分别是:

  • Service Worker(浏览器的离线缓存)
  • Memory Cache(内存)
  • Disk Cache(磁盘)
  • Push Cache(推送缓存,一种存在于会话阶段的缓存)
    • 针对HTTP/2的资源推送设计的,是浏览器的最后一道缓存机制
    • 设置了Last-Modifed但没有设置Cache-Control或者Expires时触发,会自动设置过期时间:(Date - Last-Modified)*0.1,也就是当前时间减去最后更新时间后再乘10%。

(4)Session和Cookie

HTTP协议是无状态协议,每次服务端收到客户端的请求,都是一个全新的请求,没有携带任何请求以前的状态。Session和Cookie的主要目的就是为了弥补HTTP的无状态性。

Session

  • 客户端请求服务端,服务端第一次接收到请求时,会开辟一块session空间(也就是创建session对象),同时生成一个sessionId,并设置响应头Set-Cookie:JSESSIONID=xxx,客户端收到响应后,会设置CookieJSESSIONID=xxx,该Cookie的过期时间为浏览器会话结束。

  • 接下来客户端发送请求时,会在请求头携带Cookie信息,请求头读取Cookie中的JSESSIONID的值,得到此次请求的 sessionId。

  • 缺点:如果使用CDN将客户端请求转移到其他代理服务器,代理服务器并没有存储sessionId相关数据,会导致session失效

Cookie

  • Cookie是服务器发送到浏览器的一小块数据,浏览器会存储Cookie,并且在下次请求时,作为请求头发送到服务器。

  • Cookie也曾用于客户端存储,但是这并不是Cookie出现的本意,尤其是每次请求都会携带Cookie,使用storage存储数据会更好。

  • 有两种类型的Cookie:Session Cookie,Persistent Cookie。如果Cookie中不包含到期日期,则将其视为会话cookie,存储在内存中,随着浏览器的关闭而消失。如果包含有效期,则视为永久cookie,写入磁盘,到期之后再删除cookie。

JSON Web Tokens

  • JWT的原理是服务端认证以后,生成一个JSON对象(实际上会返回JSON对象签名后的字符串),发送给用户,由客户端保存信息(可以存在Cookie里面),每次请求时携带信息。
  • JWT由三部分组成,中间用.分隔,分别是Header头部、payload负载、Signature签名。例如:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
区别SessionJWT
存储位置SessionId信息存储在服务器中存储在客户端,一般会放在Cookie里面,实际上服务端会利用redis缓存存储token,以控制token失效

3、请求状态码

类别原因描述
1xxInformational(信息性状态码)接受的请求正在处理
2xxSuccess(成功状态码)请求正常处理完毕
3xxRedirecttion(重定向状态码)要求进行附加操作来完成请求
4xxClient Error(客户端错误状态码)服务器无法处理请求
5xxServer Error(服务器错误状态码)服务器处理请求出错

(1)2xx

  • 200:请求被服务器正常处理
  • 204:No Content
    • 请求被正常处理,但是没有返回内容
  • 206:Partical Content
    • Range指定范围,服务器返回指定字节范围的资源

(2)3xx

  • 301:Moved Permanently 永久重定向

    • 请求资源被分配了新的URI,会在 HTTP 响应头中的 Location 首部字段指定
    • 若用户已经把原来的URI保存为书签,此时会按照 Location 中新的URI重新保存该书签
    • 搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址
  • 302:Found 临时重定向

    • 请求资源被临时分配新URI
    • 不会更新书签
    • 搜索引擎抓取新内容,保存旧网址
  • 303:See Other

    • 与302功能相似,但是应使用GET方法请求资源
  • 注意

    • 当 301、302、303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成GET,并删除请求报文内的主体,之后请求会再次自动发送。
    • 301、302 标准是禁止将 POST 方法变成 GET方法的,但实际大家都会这么做。
  • 304:Not Modified

    • 告诉客户端有缓存,直接使用缓存中的数据。
    • 返回页面的只有头部信息,是没有内容部分的,这样在一定程度上提高了网页的性能。
    • 搜索引擎会更加青睐内容源更新频繁的网站。通过特定时间内对网站抓取返回的状态码来调节对该网站的抓取频次。若网站在一定时间内一直处于304的状态,那么可能会降低对网站的抓取次数。相反,若网站变化的频率非常之快,每次抓取都能获取新内容,那么日积月累,的回访率也会提高。
    • 常用于:页面长期不更新/静态页面
    • 缺点:网站快照停止、收录减少、权重下降
  • 307:Temporary Redirect 临时重定向

    • 与302类似,但是禁止将POST请求转变为GET请求
  • 302 303 307区别

    • 302是http1.0的状态码,在http1.1细化302为303和307
    • 303:客户端使用GET请求资源,会把POST请求最终转变为GET请求
    • 307:不会修改请求方法重定向请求资源

(3)4xx

  • 400:Bad Request
    • 请求报文中存在语法错误
  • 401:Unauthorized
    • 请求必须要有HTTP认证信息
    • 初次接收401会弹出认证窗口,否则表示用户认证失败
  • 403:Forbidden
    • 资源禁止访问
  • 404:Not Found
    • 服务器无法找到想要的资源
    • 也可以在服务器拒绝请求但不想说明理由的时候使用
  • 405:Method Not Allowed
    • 服务器禁止使用该请求方法
    • GET和HEAD方法,服务端应该总是允许访问
    • 可以通过OPTIONS方法查看是否允许访问 Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE

(4)5xx

  • 500:Internal Server Error
    • 服务端在执行请求时发生错误
    • 也可能是web应用存在的bug或某些临时故障
  • 502:Bad Gateway
    • 扮演网关或代理角色的服务器,从上游服务器中接收到的响应是无效的
  • 503:Service Unavailable
    • 服务器超负载或正在进行停机维护,现在无法处理请求。
    • 场景:
      • 服务器停机维护时,主动用503响应请求
      • nginx 设置限速,超过限速,会返回503。
  • 504: Gateway Timeout
    • 网关或代理服务器响应超时

4、不同的HTTP协议

(1)什么是HTTP

HTTP:超文本传输协议,是实现网络通信的一种规范,基于TCP/IP协议进行数据传输,默认是80端口。

  • 支持客户端/服务器模式
    • 用户通过客户端(比如浏览器)输入URL,客户端向服务器发起一个HTTP请求
    • 服务器收到请求后,根据请求中的内容进行处理,并将处理完成的结果包装成HTTP响应消息,发送回客户端
    • 客户端收到响应后,根据响应内容渲染页面,展示给用户
  • 无连接
    • 限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接
  • 无状态
    • 状态主要指的是协议对于事务处理没有记忆能力,缺少状态意味着如果后续的请求需要前面的信息,需要重传,可能导致传输的数据量增大,但是在服务器不需要先前信息时它的应答就比较快。
  • 媒体独立
    • 只要客户端和服务器知道如何处理数据内容,任何数据类型都可以通过HTTP传输。
  • 明文传输
    • 报文使用明文传输的文本形式,不安全

(2)HTTP1.0和HTTP1.1

区别http1.0http1.1
连接默认是非持久连接,可以使用Connection: keep-alive转变为持久连接默认持久连接,多个请求可以复用一个TCP,减少建立建立连接的时间
资源请求不支持断点续传,只能访问整个对象通过请求头range只请求资源的某个部分,返回码206,充分利用带宽和连接
缓存通过请求头的If-Modified-Since、Expires来作为缓存的判断标准引入了更多缓存控制策略:Cache-control、Etag、If-Unmodified-Since、If-Match、If-None-Match等缓存头
Hosthttp1.0认为每台服务器绑定了一个ip随着虚拟技术的发展,1台物理服务器上可以存在多个虚拟主机,并共享ip,host可以使请求发到同一个服务器的不同网站
请求方法GET、POST、HEAD新增PUT、DELETE、OPTIONS、PATCH、TRACE、CONNECT方法

在同一个TCP连接中,客户端可以同时发送多个请求,但是请求是按照顺序进行的,服务器只有处理完一个请求,才会处理下一个请求,如果前面的请求很慢,就会导致队头阻塞。

(3)HTTP1.1和HTTP2.0

区别http1.1http2.0
二进制协议报文头信息必须是文本(ASCII编码),数据体可以是文本,可以是二进制头信息和数据体都可以是二进制,并且统称为帧(头信息帧和数据帧),帧的概念是实现多路复用的基础。
多路复用-http/2实现了多路复用,仍然复用了TCP,但是一个连接里,客户端和服务器都可以同时发送多个请求或回应,避免队头堵塞
数据流-http/2使用了数据流的概念,数据流由数据包构成,数据流有独一无二的编号,数据包发送时必须标记数据流ID以区分数据包属于哪个请求。因为数据包不是按顺序发送的,同一个连接里面的不同数据包可能属于不同的请求。
头信息压缩http1.1不带状态,每次请求都必须附上所有的信息,因此请求的很多字段都是重复的,一样的内容每次请求都会附带,浪费带宽,也影响速度http2.0采用了头信息压缩机制,可以先压缩请求头信息再发送。此外客户端和服务器同时维护了一张头信息表,所有的字段会存入这个表中并生成一个索引号,可以使请求后续不必发送相同的字段,只发送索引号。
服务器推送-http/2允许服务器未经请求,主动向客户端发送请求,这叫做服务器推送,http/2推送的是静态资源

(4)HTTP2的头部压缩算法

HTTP2的头部压缩采用HPACK算法,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率

具体来说:

  • 在客户端和服务器使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送
  • 首部表在HTTP2的连接存续期内始终存在,由客户端和服务器共同渐进的更新
  • 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前存的值

两个请求,如果请求一发送了所有的头部数据,第二个请求只需要发送差异数据,这样可以减少数据冗余,降低开销

(5)HTTP对于请求发送的限制

  • HTTP1:浏览器对一个域名下的最大TCP连接数限制为6,也就是说只能同时处理6个请求,剩下的请求需要排队。可以使用多域名部署方式,提高请求数。
  • HTTP2:HTTP2支持多路复用,可以在一个TCP连接中发送多个请求,对请求数量没有限制

(6)HTTP请求报文是什么样的

请求报文由4部分组成:

  • 请求行
    • 请求行包括请求方法、URL、HTTP协议版本,他们之间使用空格分隔
  • 请求头部
    • 请求头由键值对组成,每行一对,键值之间用英文冒号分隔
  • 空行
  • 请求体
    • 携带的数据

(7)HTTP响应报文是什么样的

响应报文由4部分组成:

  • 响应行
    • 响应行由网络协议版本、状态码、状态码原因短语组成
  • 响应头
  • 空行
  • 响应体

(9)HTTP3

HTTP3基于UDP协议实现了类似于TCP的多路复用数据流、传输可靠性等功能,这套功能被称为QUIC协议。

  • 流量控制、传输可靠性功能:QUIC在UDP的基础上增加了一层来保证数据传输的可靠性,它提供了数据包重传、拥塞控制以及其他一些TCP的特性
  • 集成TLS加密功能:目前QUIC使用TLS1.3,减少了握手所花费的RTT数
  • 多路复用:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了TCP的队头阻塞问题
  • 快速握手:由于基于UDP,可以实现使用0~1个RTT来建立连接

(10)HTTPS协议

HTTPS:超文本传输安全协议,是一种通过计算机网络进行安全通信的传输协议。

HTTPS = HTTP + SSL/TLS(以前称为安全套接字层SSL,现在称为传输层安全性TLS)

HTTPS经由HTTP进行通信,利用SSL/TLS来加密数据包,比HTTP多了一层安全层,位于应用层和传输层之间。HTTP具有信息窃听、信息篡改、信息劫持的风险,使用HTTPS可以进行信息加密、完整性校验、身份验证等优势。

虽然可以对整个信息都采用非对称加密的方式传输,但是报文可能很长很大,而且非对称加密的效率很低,因此采用对称加密 + 数字签名

HTTPS采用对称加密传输数据。使用非对称加密进行证书验证。

  • 服务器运营人员向数字证书认证机构提出证书申请,并提供自己的公钥等信息
  • 机构使用自己的私钥对申请者的公钥进行加密,得到的密文加上证书的过期时间、颁发者等信息组成了数字证书,返回给申请者
  • 在进行HTTPS请求时,会进行HTTPS握手
    • 客户端向服务器发起HTTPS请求,该消息将包含
      • 客户端支持的 TLS 版本
      • 支持的密码套件
      • 一串称为“客户端随机数(client random)”的随机字节
    • 服务器收到请求后
      • 确认加密方法
      • 向客户端发送一个随机数和数字证书
    • 客户端收到后,进行证书验证(设备OS内置知名的CA公钥),验证通过后
      • 生成一个随机数,使用证书中的公钥加密返回给服务端
      • 使用三个随机数生成对称加密密钥发送给服务端
      • 再提供一个前面所有内容的hash数据并使用新生成的密钥加密
    • 服务端接收后
      • 用私钥解密随机数
      • 使用相同的算法,将三个随机数生成为密钥
      • 根据之前获取的相关信息生成hash数据,解密客户端发送过来的数据,验证数据和密钥的正确性
      • 将密钥和加密后的hash数据也返回给客户端
    • 客户端验证服务端返回的加密密钥,握手成功

摘要算法:

实现完整性的手段主要是摘要算法,也就是常说的散列函数、哈希函数。能够把任意长度的数据映射成固定长度的散列字符串,对于相同的输入,散列后的结果是一样的。

(11)HTTP和HTTPS

区别HTTPHTTPS
安全性明文传输,安全性较低使用SSL/TLS加密传输,安全性较高
证书不需要需要证书,且费用较高
端口80443
性能不需要加密,性能略高于HTTPS需要加解密,有一定的性能开销
SEO搜索引擎可能会对HTTP网站降权搜索引擎倾向于优先索引和展示HTTPS网站

5、浏览器输入地址并回车之后发生了什么

(1)解析URL

  • 首先会对URL进行解析,分析所需要使用的传输协议、域名(服务器名称)、请求资源的路径
  • 如果URL协议或主机名不合法,会把地址栏的内容整体传递给搜索引擎
  • 如果没有问题,会继续检查URL中是否有非法字符,如果存在会对非法字符进行转义

(2)缓存判断

  • 判断请求资源是否在浏览器缓存里
  • 如果在缓存里,直接从缓存返回数据,否则继续向服务器请求资源

(3)DNS解析(DNS:Domain Name System 域名系统)

  • 首先搜索浏览器的 DNS 缓存,缓存中维护一张域名与 IP 地址的对应表
  • 若没有命中,则继续搜索操作系统的 DNS 缓存
  • 若仍然没有命中,则操作系统将域名发送至本地域名服务器,本地域名服务器采用递归查询自己的 DNS 缓存,查找成功则返回结果
  • 若本地域名服务器的 DNS 缓存没有命中,则本地域名服务器向上级域名服务器进行迭代查询
    • 首先本地域名服务器向根域名服务器发起请求,根域名服务器返回顶级域名服务器的地址给本地服务器
    • 本地域名服务器拿到这个顶级域名服务器的地址后,就向其发起请求,获取权限域名服务器的地址
    • 本地域名服务器根据权限域名服务器的地址向其发起请求,最终得到该域名对应的 IP 地址
  • 本地域名服务器将得到的 IP 地址返回给操作系统,同时自己将 IP 地址缓存起来
  • 操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起
  • 至此,浏览器就得到了域名对应的 IP 地址,并将 IP 地址缓存起

(4)获取MAC地址

  • 当浏览器得知IP地址之后,还需要知道目的主机的MAC地址
    • 应用层下发数据给传输层,TCP协议会指定源端口号和目的端口号
    • 传输层下发给网络层,网络层会将本机地址作为源地址,获取的域名IP作为目标地址
    • 网络层下发给数据链路层,数据链路层的发送需要加入通信双方的的MAC地址
      • 通常将IP地址与本机的子网掩码相与,可以判断是否与请求主机在同一个子网里
      • 如果在同一个子网里,可以使用APR协议获取目的主机的MAC地址
      • 如果不在同一个子网里,那么将请求转发给网关,由它代为转发,此时同样可以通过APR协议来获取网关的MAC地址,此时目的主机的MAC地址应该为网关的地址

(5)TCP三次握手

  • 首先客户端向服务器发送一个SYN连接请求报文段和一个随机序号
  • 服务端接收请求后,向客户端发送一个SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号
  • 客户端接收服务器的确认应答之后,进入连接建立的状态,同时向服务器发送ACK确认报文段,服务器接收之后也进入连接状态

(6)HTTPS握手

  • 如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程。
  • 首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。
  • 服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。
  • 客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。
  • 服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。
  • 这个时候双方都有了三个随机数,按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。

(7)返回数据

  • 当页面请求发送到服务器端之后,服务器会响应返回html,浏览器接收到响应之后,开始对html进行解析渲染

(8)页面渲染

  • 根据html构建DOM树,根据CSS构建CSSOM树
  • 如果遇到script标签,判断是否含有defer、async判断脚本执行时机
  • 当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树
  • 渲染树构建好后,会根据渲染树来进行布局
  • 布局完成后,最后使用浏览器的 UI 接口对页面进行绘制

(9)TCP四次挥手

  • 若客户端认为数据发送完成,则它需要向服务端发送FIN连接释放请求,此时进入FIN_WAIT1状态,并且不能再发送数据给服务端

  • 服务端收到连接释放请求后,会响应一个ACK报文,并进入 CLOSE_WAIT 状态,客户端接收之后进入FIN_WAIT2状态

  • 服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送FIN连接释放请求,然后服务端便进入 LAST-ACK 状态

  • 客户端收到释放请求后,向服务端发时确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。

  • 当服务端收到确认应答后,也便进入 CLOSED 状态。

6、UDP和TCP协议的区别

(1)UDP协议

UDP(User Datagram Protocal),用户数据包协议,是一个简单的面向数据报的通信协议,对应用层交下来的报文,不合并,不拆分,只在报文上面加上首部之后,就传给网络层。

UDP报头包括4个字段,每个字段占用2个字节,标题短,开销小。

  • UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务
  • 当传输过程中出现丢包时,也不负责重发
  • 当数据包的顺序出现乱序时,也没有纠正功能
  • 直接将应用层报文包装一下就发送到网络层。即使出现网络拥堵,也不能进行相应的流量控制等操作避免网络拥塞行为。

(2)TCP

TCP(Transmission Control Protocol),传输控制协议,是一种可靠的、面向字节流的通信协议,把应用层交下来的数据看成无结构的字节流来发送。

可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候发送,TCP会根据当前网络的拥塞状态来确定每个报文段的大小。

TCP报文首部有20个字节,额外开销大

  • TCP充分实现了数据传输时的各种控制功能,丢包时可以重发,对次序乱掉的包进行重排,这都是UDP没有的功能
  • TCP是一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而避免通信流量的浪费
  • 根据TCP的这些机制,在IP这种无连接的网络上也能够实现高可靠的通信(主要通过检验和、序列号、确认应答、重发控制、连接管理、窗口控制等机制实现)
区别TCPUDP
可靠性可靠、传输过程中采用流量控制、编号确认、计时器等手段确保数据没有差错,保证不丢包,顺序不错乱不可靠,尽可能的传递数据,但不保证交付给对方
连接性面向连接:3次握手,4次挥手无连接,发送端只负责发送数据,接收端从消息队列中读取数据
报文面向字节流,将应用层报文看成一串无结构的字节流,分解成多个TCP报文段传输,在目的站重新装配面向报文,将应用层报文添加UDP首部后直接发送给网络层
效率
双共性点对点全双工一对一、一对多、多对一、多对多
应用场景电子邮件、万维网HTTP域名转换DNS

7、OSI七层模型

OSI(Open System Interconnect)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互联为网络的标准框架。

OSI主要划分为7层

  • 应用层:为应用程序提供服务
    • 最靠近用户的一层,为计算机用户提供应用服务
    • 常见的网络服务协议:HTTP、FTP、SMTP
  • 表示层:数据格式转化、数据加密
    • 将计算机内部的多种数据格式转换成通信中采用的标准表示形式
  • 会话层:建立、管理、维护表示层实体之间的会话
    • 该层的通信由不同设备中的应用程序之间的服务请求和响应组成
  • 传输层:建立、管理、维护端到端的连接
    • TCP、UDP
  • 网络层:IP选址以及路由选择
  • 数据链路层:提供介质访问和链路管理
  • 物理层

8、TCP/IP协议

TCP/IP,传输控制协议/网际协议,能够在多个不同网络之间实现信息传输的协议簇。TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,IP是用于封包交换数据网络的协议。

TCP/IP不仅仅指的是两个协议,而是由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇,由于TCP和IP协议更有代表性,所以称为TCP/IP协议。

分为四层和五层,五层体系的协议结构是综合OSI和TCP/IP优点的一种协议,只是为介绍网络原理而设计的,实际应用还是四层体系结构

五层:应用层(七层模型中的应用层、表示层、会话层的功能集合)、传输层、网络层、数据链路层、物理层

四层:应用层(七层模型中的应用层、表示层、会话层的功能集合)、传输层、网络层、网络接口层(数据链路层、物理层)

OSI和TCP/IP:

  • 相同点
    • 都采用了层次结构
    • 都能够提供面向连接和无连接的两种通信服务机制
  • 不同点
    • 结构层数不同
    • OSI虽然划分为7层,但是实现起来比较困难,TCP/IP的四层模型是现在使用的方式
    • TCP/IP协议去掉表示层和会话层是因为,会话层、表示层、应用层都是在应用程序内部实现的,最终产出的是一个应用数据包,而应用之间几乎无法实现代码的抽象共享,因此OSI的应用程序分层是无法实现的。

9、DNS协议

DNS(Domain Names System)域名系统,将域名翻译成IP

(1)域名

域名是一个层次结构,从上到下依次是根域名、顶级域名、二级域名、三级域名...

域名为www.baidu.com,www是三级域名,baidu是二级域名,com是顶级域名,系统为用户做了兼容,域名末尾的根域名.一般不需要输入

域名每一层对应的服务器分别是根域名服务器、顶级域名服务器、权限域名服务器,本地域名服务器

(2)域名查询方式

  • 递归查询
    • 本地域名服务器请求根域名服务器
    • 根域名服务器去查询顶级、权限域名服务器,获取IP地址后,最终返回给本地域名服务器
  • 迭代查询
    • 本地域名服务器请求根域名服务器
    • 根域名服务器返回对应的顶级域名服务器
    • 本地域名服务器请求顶级域名服务器
    • 顶级域名服务器返回对应的权限域名服务器
    • 本地域名服务器请求权限域名服务器,获取最终IP

(3)DNS查询流程

  • 搜索浏览器的DNS缓存
  • 操作系统的DNS缓存(用户维护的hosts文件)
  • 本地域名服务器递归查询自身的缓存
  • 向上级域名进行迭代查询,获得IP
  • 本地域名服务器将IP返回给操作系统,操作系统返回给浏览器

10、CDN

(1)CDN

CDN(Content Delivery Network)内容分发网络,构建在现有网络基础上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问的响应速度和命中率。

简单来说,CDN就是根据用户位置分配最近的资源,用户在上网的时候不用直接访问源站,而是访问距离用户最近的CDN节点(术语叫边缘节点),降低传输延迟,提升用户访问速度。

(2)原理

CDN有两大主要功能

  • 负载均衡系统:智能调度边缘节点
  • 缓存系统:缓存命中直接返回给用户,否则回源

负载均衡系统

在没有应用CDN时,使用域名访问资源时,DNS返回的是IP地址,应用CDN之后,DNS返回的不是IP地址,而是一个CNAME(Canonical Name)别名记录,指向CDN的全局负载均衡。

CNAME实际上在域名解析过程中承担了中间人的角色,这是实现CDN的关键

由于没有返回IP地址,本地DNS会向负载均衡系统再次发送请求,进入到CDN的全局负载均衡系统进行智能调度

  • 看用户的IP地址,查表得知用户的地理位置,找相对最近的边缘节点
  • 看用户所在的运营商网络,找相同网络的边缘节点
  • 检查边缘节点的负载情况,找负载最轻的节点
  • 其他:比如节点的带宽、响应时间等

结合上面的因素,找到最合适的边缘节点,把节点返回给用户,用户就可以就近访问CDN

缓存代理

两个重要的衡量CDN服务质量的指标

  • 回源率:回源(缓存里面没有)次数/所有访问次数
    • 缓存系统也可以划分出层次,分成一级缓存节点和二级缓存节点。一级缓存配置高一些,直连源站,二级缓存配置低一些,直连用户。
    • 回源的时候二级缓存只找一级缓存,一级缓存没有才回源站,可以有效地减少真正的回源
  • 命中率:用户访问的资源恰好在缓存系统中,命中次数/所有访问次数
    • 现在的商业CDN命中率都在90%以上

二、浏览器安全

1、XSS攻击

tech.meituan.com/2018/09/27/…

(1)概念

XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。

攻击者可以通过这种攻击方式可以进行以下操作:

  • 获取页面的数据,如DOM、cookie、localStorage;
  • DOS攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器;
  • 破坏页面结构;
  • 流量劫持(将链接指向某网站);

(2)攻击类型

XSS 可以分为存储型、反射型和 DOM 型:

  • 存储型指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。
  • 反射型指的是攻击者诱导用户访问一个带有恶意代码的 URL 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。 
  • DOM 型指的通过修改页面的 DOM 节点形成的 XSS。

1)存储型 XSS 的攻击步骤:

  1. 攻击者将恶意代码提交到⽬标⽹站的数据库中。
  2. ⽤户打开⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

这种攻击常⻅于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。

2)反射型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. ⽤户打开带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。

反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如⽹站搜索、跳转等。 由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。

3)DOM 型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. ⽤户打开带有恶意代码的 URL。
  3. ⽤户浏览器接收到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端JavaScript ⾃身的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。

(3) 如何防御 XSS 攻击?

可以看到XSS危害如此之大, 那么在开发网站时就要做好防御措施,具体措施如下:

  • 可以从浏览器的执行来进行预防,一种是使用纯前端的方式,不用服务器端拼接后返回(不使用服务端渲染)。另一种是对需要插入到 HTML 中的代码做好充分的转义。对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断。
  • 使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。
  1. CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。
  2. 通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式
  • 对一些敏感信息进行保护,比如 cookie 使用 http-only,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。

2、CSRF攻击

浏览器会依据加载的域名附带上对应的cookie

tech.meituan.com/2018/10/11/…

(1)概念

CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。

(2)攻击类型

常见的 CSRF 攻击有三种:

  • GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。
  • POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。
  • 链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。

(3)如何防御CSRF攻击

CSRF 攻击可以使用以下方法来防护:

  • 进行同源检测,服务器根据 http 请求头中 origin 或者 referer 信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当 origin 或者 referer 信息都不存在的时候,直接阻止请求。这种方式的缺点是有些情况下 referer 可以被伪造,同时还会把搜索引擎的链接也给屏蔽了。所以一般网站会允许搜索引擎的页面请求,但是相应的页面请求这种请求方式也可能被攻击者给利用。(Referer 字段会告诉服务器该网页是从哪个页面链接过来的)
  • 使用 CSRF Token 进行验证,服务器向用户返回一个随机数 Token ,当网站再次发起请求时,在请求参数中加入服务器端返回的 token ,然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题,但是这种方法存在一个缺点就是,我们需要给网站中的所有请求都添加上这个 token,操作比较繁琐。还有一个问题是一般不会只有一台网站服务器,如果请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这个 token 的话,就没有办法验证了。这种情况可以通过改变 token 的构建方式来解决。
  • 对 Cookie 进行双重验证,服务器在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串,然后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较,来进行验证。使用这种方式是利用了攻击者只能利用 cookie,但是不能访问获取 cookie 的特点。并且这种方法比 CSRF Token 的方法更加方便,并且不涉及到分布式访问的问题。这种方法的缺点是如果网站存在 XSS 漏洞的,那么这种方式会失效。同时这种方式不能做到子域名的隔离。
  • 在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为被第三方使用,从而可以避免被攻击者利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用,在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。

3、中间人攻击

中间⼈ (Man-in-the-middle attack, MITM) 是指攻击者与通讯的两端分别创建独⽴的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过⼀个私密的连接与对⽅直接对话, 但事实上整个会话都被攻击者完全控制。在中间⼈攻击中,攻击者可以拦截通讯双⽅的通话并插⼊新的内容。

4、网络劫持

网络劫持分为两种:

(1)DNS劫持: (输⼊京东被强制跳转到淘宝这就属于dns劫持)

  • DNS强制解析: 通过修改运营商的本地DNS记录,来引导⽤户流量到缓存服务器
  • 302跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是可以进⾏劫持处理的,再对劫持的内存发起302跳转的回复,引导⽤户获取内容

(2)HTTP劫持: (访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告)

DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。

5、有哪些可能引起前端安全问题

  • 跨站脚本 (Cross-Site Scripting, XSS): ⼀种代码注⼊⽅式, 为了与 CSS 区分所以被称作 XSS。早期常⻅于⽹络论坛, 起因是⽹站没有对⽤户的输⼊进⾏严格的限制, 使得攻击者可以将脚本上传到帖⼦让其他⼈浏览到有恶意脚本的⻚⾯, 其注⼊⽅式很简单包括但不限于 JavaS

  • 跨站点请求伪造(Cross-Site Request Forgeries,CSRF): 指攻击者通过设置好的陷阱,强制对已完成认证的⽤户进⾏⾮预期的个⼈信息或设定信息等某些状态更新,属于被动攻击 cript / CSS / Flash 等;

  • iframe的滥⽤: iframe中的内容是由第三⽅来提供的,默认情况下他们不受控制,他们可以在iframe中运⾏JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端⽤户体验;

  • 恶意第三⽅库: ⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤多数时候都是在借助开发框架和各种类库进⾏快速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起安全问题。

三、浏览器进程与线程

1、进程

操作系统中最核心的概念就是进程,进程是对正在运行中的程序的一个抽象,是系统进行资源分配和调度的基本单位。

进程是一种抽象的概念,没有统一的标准定义,一般由程序、数据集合、进程控制块三部分组成:

  • 程序:用户描述进程要完成的功能,是控制进程执行的指令集
  • 数据集合:程序在执行时所需的数据和工作区
  • 程序控制块:包含进程的描述信息和控制信息,是进程存在的唯一标志

进程和程序的区别:程序仅仅只是一堆代码,进程指的是程序的运行过程。

2、线程

线程是操作系统能够进行运算调度的最小单位,是进程中的一个执行任务(控制单元),负责当前进程中程序的执行。

一个进程至少有一个线程,一个进程可以运行多个线程,这些线程共享同一块内存,线程之间可以共享对象、资源,如果有冲突或需要协同,可以随时沟通以解决冲突或保持同步。

但实际上,并不是线程越多,进程的工作效率越高,因为在一个进程内,不管我们创建了多少个线程,他们总是被限定在一颗CPU内,或者多核CPU的一个核内。

这意味着,多线程在宏观上看是并行的,在微观上则是分时切换串行的,多线程编程无法充分发挥多核计算资源的优势。

这导致使用多线程做任务并行处理时,线程数量超过一定数值后,线程越多,速度反而越慢。

3、线程和进程的区别

区别进程线程
本质区别操作系统资源分配的基本单位任务调度和执行的基本单位
开销有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间的切换开销小
所处环境在操作系统中能同时运行多个进程(程序)同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
内存分配系统在运行的时候,会为每个进程分配不同的内存空间除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属的进程的资源),线程组之间只能共享资源
包含关系没有线程的进程可以看做是单线程,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线共同完成的线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程

4、并发和并行

并发:伪并行,看起来是同时运行,通过单核CPU+多道技术实现 并行:同时运行,需要具备多核CPU

5、浏览器渲染进程的线程有哪些

(1)GUI渲染线程

负责渲染浏览器页面,解析HTML、CSS,构建DOM树、CSSOM树、渲染树,绘制页面;当页面需要重绘或回流时,该线程就会执行。

GUI渲染线程和JS引擎线程是互斥的,当JS引擎线程执行时,GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时,再执行。

(2) JS引擎线程

JS引擎线程也称为JS内核,负责解析执行JS脚本。JS引擎线程一直等待着任务队列中任务的到来,然后加以处理,一个Tab页面中无论什么时候都只有一个JS引擎线程在运行JS程序。

(3)事件触发线程

浏览器的事件触发线程用来控制事件循环。当JS引擎执行代码遇到定时器、ajax等任务时,会把事件添加到任务队列中,当宏任务执行完毕之后,执行微任务。

(4)定时器触发进程

定时器触发进程即setTimeout、setInterval所在的线程。

浏览器的定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果线程处于阻塞状态,会影响计时的准确性。

因此使用单独的线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待JS引擎空闲时执行。因而定时器中的任务在设定的时间点不一定能够准时执行,只是在指定时间点将任务添加在事件队列中。

W3C在HTML标准中规定,定时器的定时时间不能小于4ms,如果小于4ms,则默认为4ms。

(5)异步http请求线程

XMLHttpRequest通过浏览器新开一个线程请求。

检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行。

6、进程之间的通信方式

(1)管道通信

管道通信是一种最基本的进程间通信机制。

管道就是操作系统在内核中开辟的一段缓冲区,进程1可以将需要交互的数据拷贝到这段缓冲区,进程2就可以读取了。

管道的特点:

  • 只能单向通信
  • 只能血缘关系的进程进行通信(通常指父子进程)
  • 依赖于文件系统
  • 生命周期跟随进程
  • 面向字节流的服务
  • 管道内部提供了同步机制

(2)消息队列通信

消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。

A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了。同理,B 进程要给 A 进程发送消息也是如此

消息队列和管道通信相比,优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是根据自定义条件接收特定类型的消息。

使用消息队列进行进程间的通信,会受到数据块最大长度限制的约束。如果频繁的发生进程间的通信行为,那么进程需要频繁的读取队列中的数据到内存,相当于间接地从一个进程拷贝到另一个进程,开销大。

(3)信号量通信

共享内存最大的问题就是多进程竞争内存的问题,类似于线程安全的问题。我们可以使用信号量来解决这个问题。

信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。

例如,信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。

(4)信号通信

信号(Signals)是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。

(5)共享内存通信

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问(使多个进程可以访问同一块内存空间)。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

(6)套接字通信

上面说的共享内存、管道、信号量、消息队列,他们都是多个进程在一台主机之间的通信,那两个相隔几千里的进程能够进行通信吗?答是必须的,这个时候 Socket 这家伙就派上用场了,例如我们平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这种就是采用 Socket 的通信方式了。

7、孤儿进程和僵尸进程

  • 孤儿进程:父进程退出了,但他的一个或多个子进程还在运行,那这些子进程都会成为孤儿进程。

    • 孤儿进程将被init进程(进程号为1)所收养,并由init进程对他们完成状态收集工作。
  • 僵尸进程:子进程比父进程先结束,而父进程又没有释放子进程占用的资源,那么子进程的进程描述符仍然保存在系统中

8、死锁产生的原因

死锁:多个进程在运行过程中因争夺资源而造成的一种僵局,即一个进程等待一个已经被占用且永不释放的资源。进程处于这种僵持状态时,如无外力作用,他们都将无法再向前推进。

系统中的资源可以分为两类:

  • 可剥夺资源:某进程在获得这类资源之后,该资源可以再被其他进程或系统剥夺
    • CPU、主存
  • 不可剥夺资源:当系统把这类资源分配给某进程之后,再不能强行收回,只能等进程用完之后自动释放
    • 磁带机、打印机

产生死锁的原因:

(1)竞争资源

  • 竞争不可剥夺资源

    • 例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞
  • 竞争临时资源,通常消息通信顺序进行不当,则会产生死锁

    • 临时资源包括硬件中断、信号、消息、缓冲区内的消息等
    • 例如,SI,S2,S3是临时性资源,进程P1产生消息S1,又要求从P3接收消息S3;进程P3产生消息S3,又要求从进程P2处接收消息S2;进程P2产生消息S2,又要求从P1处接收产生的消息S1。

(2)进程间推进顺序非法

有三个线程,P1,P2和P3,分别生产数据M1,M2,M3,同时分别接收别的线程产生的数据M3,M2,M1,如果线程推进的顺序正确,即三个线程都先生产数据,再接收,那么没有问题,但是一旦线程先接受数据,再生产数据,因为一开始没有数据产生,那么就会造成三个线程的死锁问题。

产生死锁的必要条件:

  • 互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内,某资源仅为一个进程所占用
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放
  • 环路等待条件:在发生死锁时,必然存在一个进程-资源的环形链

预防死锁的办法

  • 资源一次性分配:一次性分配所有的资源,就不会再有资源请求了(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏请求保持条件)
  • 某个进程获得了部分资源,但得不到其他资源,则释放已占用资源(破坏不可剥夺条件)
  • 资源有序分配:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件

9、如何实现浏览器多个标签页之间的通信

实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没办法直接通信,我们可以找一个中介者,标签和中介者进行通信,然后让中介者进行消息的转发。

  • websocket:因为websocket协议可以实现服务器推送,标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发
  • ShareWorker:会在页面存在的生命周期内创建一个唯一的线程,并且开启多个页面也只会使用同一个线程。这个共享线程可以充当中介者,多个标签页共享,用来实现数据交换
  • localStorage:监听localStorage变化
  • postMessage:如果能获得对应标签页的引用,就可以使用这个方法进行通信

10、对Service Worker的理解

Service Worker是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。传输协议必须使用https,因为Service Worker中涉及到请求拦截,需要使用https保证安全。

// index.js
if (navigator.serviceWorker) {
  navigator.serviceWorker
    .register('sw.js')
    .then(function(registration) {
      console.log('service worker 注册成功')
    })
    .catch(function(err) {
      console.log('servcie worker 注册失败')
    })
}
// sw.js
// 监听 `install` 事件,回调中缓存所需文件
self.addEventListener('install', e => {
  e.waitUntil(
    caches.open('my-cache').then(function(cache) {
      return cache.addAll(['./index.html', './index.js'])
    })
  )
})
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(function(response) {
      if (response) {
        return response
      }
      console.log('fetch source')
    })
  )
})

四、浏览器缓存

1、对浏览器缓存机制的理解

浏览器缓存的全过程:

  • 浏览器第一次向服务器请求资源,服务器返回200,浏览器会缓存资源文件与请求响应头,以供下次加载时对比使用。
  • 浏览器下一次加载资源时,由于强缓存的优先级较高,先比较当前时间与上一次请求返回的时间差,如果没有超过cache-control设置的max-age,则缓存没有过期,命中强缓存。如果浏览器不支持http1.1,则使用expires判断是否过期
  • 如果没有命中强缓存,开始协商缓存,请求头携带If-None-Match和If-Modified-Since
  • 服务器收到请求后,优先根据Etag的值判断被请求的文件有没有被修改,没有修改则命中协商缓存,返回304。如果不一致,直接返回新的资源文件,响应头带上最新的Etag
  • 如果服务器收到的请求没有Etag,则判断last-modified的值,处理逻辑同上一步

2、强缓存

(1)Expires

服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。

Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。

(2)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[=]:设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。

no-cache和no-store很容易混淆:

  • no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存;
  • no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源。

3、协商缓存

(1)Last-Modified

  • 服务器在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间
  • 当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值
  • 当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改
  • 如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存
  • 如果资源已经被修改了,则返回修改后的资源
  • 使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确

(2)Etag

判断逻辑和Last-Modified一样,Etag是根据文件内容生成的资源标识符。

对应的请求头If-None-Match,优先级高于Last-Modified。

4、为什么需要浏览器缓存

浏览器缓存主要针对的是前端的静态资源,静态资源内容不经常变化,把资源缓存在本地,可以大大提高用户的加载速度。

与此同时,减少了服务器的负担,减少了多余网络数据传输,提高网站性能。

5、点击刷新按钮、按F5、按Ctrl+F5、地址栏回车缓存区别

  • 刷新按钮/F5:浏览器直接对本地缓存文件设为过期,但是会带上If-None-Match和If-Modified-Since进行协商缓存
  • Ctrl+F5(强制刷新):本地缓存文件过期,并且请求不会携带If-None-Match和If-Modified-Since
  • 地址栏回车:浏览器发起请求,按照正常流程先强缓存,再协商缓存

五、浏览器组成

1、对浏览器的理解

浏览器的主要功能是将用户选择的web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器的窗口中,资源的格式通常是HTML,也包含PDF、image以及其他格式。用户用URI(Uniform Resource Identifier 统一资源标识符)来指定所请求的资源位置。

HTML和CSS规范中规定了浏览器解释html文档的方式,由W3C组织对这些规范进行维护,W3C是负责制定Web标准的组织。

浏览器可以分为两部分,shell和内核。其中shell的种类比较多,内核比较少。也有一些浏览器并不区分外壳和内核。从Mozilla将Gecko独立出来后,才有了外壳和内核的明确划分。

  • shell:浏览器的外壳,例如菜单栏,工具栏等,主要为用户提供界面操作、参数设置等。它是调用内核来实现各种功能的。
  • 内核:浏览器的核心,基于标记语言显示内容的程序或模块。

2、对浏览器内核的理解

浏览器内核主要分成两部分:

  • 渲染引擎:职责是页面渲染,默认情况下,渲染引擎可以显示HTML、XML文档及图片,也可以借助插件显示其他数据类型,例如使用PDF阅读器插件吗,可以显示pdf格式的文件。
  • JS引擎:解析和执行JavaScript来实现网页的动态效果。

最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。

3、常见的浏览器内核比较

  • Trident:IE浏览器内核

    • 早期IE浏览器市场占有率高,微软很长时间没有更新Trident内核,导致Trident与W3C标准脱节。此外Trident的大量bug等安全问题没有解决,加上一些专家学者公开认为IE浏览器不安全,很多用户开始转向其他浏览器。
  • Gecko:Firefox和Flock采用的内核

    • 优点:功能强大、丰富,可以支持很多复杂网页效果和浏览器扩展接口
    • 缺点:消耗很多资源(比如:内存)
  • Presto:以前Opera浏览器采用的内核,被公认为浏览网页速度最快的内核

    • 优点:速度快,在处理JS等脚本语言时,会比其他内核快3倍左右
    • 缺点:兼容性不好
  • Webkit:Safari浏览器采用的内核

    • 优点:速度快,虽然不及Presto,但是比Gecko和Trident快
    • 缺点:兼容性较低
  • Blink:Chrome浏览器采用的内核,实际上是Webkit内核的一个分支

    • Blink引擎现在是谷歌公司和Opera Software共同研发,Opera弃用了Presto内核,和谷歌一起研发Blink

4、常用浏览器的内核

(1) IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;

(2) Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink内核;

(3) Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;

(4) Safari 浏览器内核:Webkit 内核;

(5) Opera 浏览器内核:最初是自己的 Presto 内核,后来加入谷歌大军,从 Webkit 又到了 Blink 内核;

(6) 360浏览器、猎豹浏览器内核:IE + Chrome 双内核;

(7) 搜狗、遨游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);

(8) 百度浏览器、世界之窗内核:IE 内核;

(9) 2345浏览器内核:好像以前是 IE 内核,现在也是 IE + Chrome 双内核了;

(10)UC 浏览器内核:这个众口不一,UC 说是他们自己研发的 U3 内核,但好像还是基于 Webkit 和 Trident ,还有说是基于火狐内核。

5、浏览器的主要组成部分

  • 用户界面:浏览器除了主窗口之外的显示部分,比如:地址栏、前进后退等
  • 浏览器引擎:在用户界面和渲染引擎之间传送指令
  • 渲染引擎:解析展示页面
  • JS引擎:解析执行JS脚本
  • 网络:用于网络调用
  • 用户界面后端:用于绘制基本的窗口小部件,比如组合框和窗口
  • 数据存储:浏览器在硬盘上保存各种数据,例如cookie

和大多数浏览器不同,Chrome浏览器的每个标签页都分别对应一个渲染引擎实例,每个标签页都是一个独立的进程。

六、浏览器渲染原理

1、浏览器的渲染过程

  • 解析文档,生成DOM树和CSSOM规则树
  • 根据DOM树赫尔CSSOM规则树构建渲染树。
    • 渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形。
    • 渲染对象和DOM元素相对应,但这种对应关系不是一对一的,不可见的DOM元素不会被插入渲染树。
    • 还有一些DOM元素对应几个可见的渲染对象,他们一般是一些具有复杂结构的元素,无法用一个矩形来描述。
  • 渲染对象被创建并添加到树中,他们并没有位置和大小,所以浏览器在生成渲染树以后,就会根据渲染树进行布局(回流)。这一阶段浏览器要做的事情就是弄清楚各个节点在页面中的确切位置和大小。通常这一行为也称为“自动重排”。
  • 布局结束之后是绘制阶段,遍历渲染树并调用渲染对象的paint方法将他们显示在屏幕上。

注意:  这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

2、浏览器渲染优化

(1)JavaScript

JavaScript脚本的执行会阻碍页面的解析,因此可以改变脚本的执行方式:

  • 将JavaScript文件放在body的最后
  • async:异步加载,加载完成之后立即执行,不能保证加载顺序
  • defer:异步加载,等DOM树解析好之后再执行

(2)CSS

CSS有三种引入方式:link、@import、内联

  • link:浏览器会派发一个新的线程(HTTP线程)去加载资源文件,于此同时GUI渲染线程会继续解析渲染HTML
  • @import:GUI渲染线程会停止解析渲染,去加载资源文件
  • style:GUI直接渲染

外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用默认样式,确保首次渲染的速度。所以CSS一般写在header中,让浏览器尽早发送请求获取资源。

在开发过程中,导入外部样式使用link取代@import

(3)DOM树、CSSOM树

  • HTML文件的代码层级尽量不要太深
  • 使用语义化标签,来避免不标准语义化标签的特殊处理
  • 减少CSS选择器的层级,选择器是从右向左进行解析的

(4)减少回流、重绘

3、文档预解析

Webkit和Firefox都做了这个优化,当执行JavaScript脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。

这种方式可以使资源并行加载从而使整体速度更快,但是预解析并不改变DOM树,它将这个工作留给渲染引擎解析,自己只解析外部资源的引用,比如外部脚本、样式表以及图片。

4、CSS如何阻塞文档解析

JavaScript脚本执行时,可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本得到错误的值,这会导致很多问题。

因此如果浏览器尚未完成CSSOM的下载和构建,我们却想在此时运行脚本,那么浏览器将延迟脚本的执行和文档的解析,直到完成所需的CSSDOM的下载和构建。

也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后再继续解析文档。

七、浏览器本地存储

1、Cookie

Cookie是最早被提出来的本地存储方式。

最早,服务端是无法判断网络中的两个请求是否是同一用户发起的,Cookie的出现是为了解决这个问题。

它是一种纯文本,每次发起HTTP请求都会携带Cookie,可以在Cookie中携带用户信息给到服务器。

由于Cookie可以被用来存储数据,最开始被用为数据存储,它的大小只有4kb,并且在每次请求都会携带,并不适合作为前端存储。

Cookie由以下字段组成:

  • Name:cookie的名称
  • Value:cookie的值
  • Size:cookie的大小
  • Path:可以访问cookie的页面路径
  • Domain:可以访问cookie的域名,cookie机制并未遵循严格的同源策略,允许子域设置/访问父域的cookie。
    • 当要实现单点登录时,这个特性非常有用,然而也增加了cookie受攻击的风险。
  • Secure:true代表仅在HTTPS协议下会发送Cookie
  • HTTPOnly:设置cookie是否可以通过脚本访问,默认为空,可以通过脚本访问。
    • 只能通过服务端设置,防止客户端篡改httponly的值,有助于保护Cookie不被跨站脚本攻击窃取或篡改。
    • HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。
  • Expires/Max-size:cookie的超时时间。 若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。

2、localStorage

localStorage是Html5新引入的特性。

优点

  • 存储大小一般为5MB
  • 持久存储,永久存在
  • 仅存储在本地,不会被HTTP请求携带

缺点

  • IE8以下不兼容
  • 无痕模式下读取不到
  • 受到同源策略限制
// 保存数据到 localStorage
localStorage.setItem('key', 'value');

// 从 localStorage 获取数据
let data = localStorage.getItem('key');

// 从 localStorage 删除保存的数据
localStorage.removeItem('key');

// 从 localStorage 删除所有保存的数据
localStorage.clear();

// 获取某个索引的Key
localStorage.key(index)

3、sessionStorage

sessionStorage也是Html5提出的新特性,主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页会删除数据。

SessionStorage和LocalStorage对比

  • 存储大小一般为5MB
  • 存储在本地,不会被HTTP请求携带
  • 受同源策略限制,sessionStorage有一条更严格的限制,即只有在同一浏览器同一窗口下才能共享

4、IndexedDB

IndexedDB 具有以下特点:

  • 键值对储存:IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
  • 异步:IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  • 支持事务:IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  • 同源限制: IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  • 储存空间大:IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
  • 支持二进制储存:IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

5、四种数据存储方式对比

image.png

八、浏览器同源策略

1、什么是同源策略

跨域问题其实就是浏览器的同源策略造成的。

同源策略限制了从一个源加载的文档或脚本如何与另一个源的资源进行交互。

这是浏览器的一个用于隔离潜在恶意文件的重要安全机制。

同源指的是:协议、端口号、域名必须一致。

同源政策主要限制了以下三个方面:

  • 当前域的js不能访问其他域的cookie、web storage、indexDB
  • 当前域的js不能访问/操作其他域的DOM
  • 当前域无法向其他域发送ajax跨域请求

2、如何解决跨域问题

跨域并不是请求发不出去,而是服务器正常返回结果后,被浏览器拦截返回结果。

(1)CORS

下面是MDN对于CORS的定义:

跨域资源共享(CORS)是一种机制,它使用额外的HTTP头来告诉浏览器让运行在一个origin(domain)上的web应用被准许访问来自不同源服务器上的指定资源。当一个资源从与该资源本身所在服务器不同的域、协议、端口请求一个资源时,资源会发起一个跨域HTTP请求。

CORS需要浏览器和服务器的支持,整个CORS过程都是由浏览器完成的,因此实现CORS的关键就是服务器,只要服务器实现了CORS,就可以跨源通信了。

浏览器将CORS分为简单请求非简单请求,简单请求不会触发CORS预检请求。

满足以下两个条件的请求是简单请求,否则是非简单请求

1)请求方法是以下三种方法之一

  • HEAD
  • GET
  • POST

2)HTTP的头信息不超出下面几种字段

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 text/plain、application/x-www-form-urlencoded、multipart/form-data

1)对于简单请求的跨域处理

浏览器直接发送CORS请求,并且会在请求头中携带origin字段,值为域名 + 端口 + 协议,服务器会根据这个值来决定是否同意这个请求。

如果服务器允许跨域请求,会设置以下响应头:

// 至少需要设置 Access-Control-Allow-Origin
Access-Control-Allow-Origin: http://api.bob.com  // 和Orign一致
Access-Control-Allow-Credentials: true   // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar   // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8   // 表示文档类型

2)非简单请求过程

浏览器会先发送预检请求,用来询问当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则会报错。

预检请求的请求方法是OPTIONS,以及两个头信息:

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到的HTTP方法
  • Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段

服务器响应:

Access-Control-Allow-Origin: http://api.bob.com  // 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header  // 服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true   // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000  // 用来指定本次预检请求的有效期,单位为秒

减少OPTIONS请求

OPTIONS请求次数过多就会损耗页面加载性能,降低用户体验。

服务器可以在请求的响应头中添加Access-Control-Max-age:number,表示预检请求的返回结果可以被缓存多久,单位是秒。

该字段只对完全一样的url生效,在这个缓存时间范围内,再次发送请求就不需要预检请求了。

CORS设置允许cookie跨域

需要满足以下三个条件

  • 请求头设置withCredentials
  • Access-Control-Allow-Credentials 设置为 true
  • Access-Control-Allow-Origin 设置为非 *

(2)JSONP

  • 利用<script>没有跨域限制的特性,通过<script>标签的src属性,发送带有callback参数的GET请求
  • 服务端返回回调函数的调用,并将处理结果作为参数传递进去
  • 浏览器解析执行函数,拿到数据。

缺点:

  • 具有局限性,只支持GET
  • 对传输的数据有限制

(3)PostMessage

PostMessage方法允许来自不同源的脚本采用异步的方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,多用于窗口间数据通信。

postMessage(data, origin)接收两个参数

  • data:任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify序列化
  • origin:协议+主机+端口号,也可以设置为*(任意窗口)或者/(与当前窗口同源)
// a.html:(domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };
    // 接受domain2返回数据
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>

// b.html:(domain2.com/b.html)
<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' + e.data);
        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;
            // 处理后再发回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

(4)Nginx

Nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头来实现。

location / {
  add_header Access-Control-Allow-Origin *;
}

使用Nginx做反向代理,通过Nginx配置一个代理服务器域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问。

#proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;
        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

(5)nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发。

  • 非Vue框架跨域:node + express + http-proxy-middleware搭建一个proxy服务器
// 前端代码
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();

// 中间服务器代码
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({
    // 代理跨域目标接口
    target: 'http://www.domain2.com:8080',
    changeOrigin: true,
    // 修改响应头信息,实现跨域并允许带cookie
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },
    // 修改响应信息中的cookie域名
    cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
  • Vue框架跨域:node + vue + webpack + webpack-dev-server
module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口
            changeOrigin: true,
            secure: false,  // 当代理某些https服务报错时用
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
        }],
        noInfo: true
    }
}

3、正向代理和反向代理

  • 正向代理:

    • 对客户端进行代理,服务器并不知道真实的客户端
  • 反向代理:

    • 对服务器进行代理,客户端并不知道真实的服务器

九、浏览器的事件机制

1、事件是什么?事件模型?

事件是用户操作网页时发生的交互动作,比如用户触发的动作click/move,以及文档加载、窗口滚动、窗口大小调整等。

事件被封装成一个event对象。包含了事件发生时的所有相关信息(event的属性)以及可以对事件进行的操作(event的方法)。

现代浏览器一共有3种事件模型:

  • 原始事件模型(DOM0级)
    • 这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的形式实现
  • 标准事件模型(DOM2级)
    • 在该事件模型中,一次事件共有三个过程,事件捕获阶段、事件处理阶段、事件冒泡阶段
    • 事件捕获阶段:事件从document向下传播到目标元素,依次执行节点绑定的事件,剩下的两个阶段和IE一样
    • 这种模型通过addEventListener来添加监听函数,其中第三个参数可以指定事件在什么阶段执行
  • IE事件模型(基本不用)
    • 在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。
    • 首先是事件处理阶段:执行目标元素绑定的监听事件
    • 然后是事件冒泡阶段:事件从目标元素冒泡到document,依次执行过程中节点绑定的事件
    • 这种模型通过attachEvent来添加监听函数,可以添加多个监听函数,会按顺序依次执行

2、如何阻止事件冒泡

  • 普通浏览器:event.stopPropagation()
  • IE浏览器:event.cancelBubble = true

3、事件委托

(1)事件委托的概念

事件委托本质上是利用了浏览器事件冒泡的机制。

可以将子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子节点的事件。

使用事件委托可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存的消耗。

(2)事件委托的局限性

  • focus、blur之类的事件没有事件冒泡机制,无法实现事件委托
  • 事件委托会影响页面性能
    • 最底层元素到父元素之间的DOM层数太多

4、事件循环

JS是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。

在执行过程中,如果遇到异步事件,JS引擎不会等待异步事件执行完成,而是会将这个事件挂起,继续执行执行栈中的其他任务。

当异步事件执行完毕之后,会将对应的回调函数放入任务队列中,等待引擎空闲时执行。

任务队列分为宏任务队列和微任务队列,当执行栈中的事件执行完毕之后,JS引擎会优先执行微任务队列中的任务,将任务压入执行栈中执行,其次执行宏任务队列中的任务。

执行顺序

  • 一开始整个脚本作为一个宏任务执行
  • 执行过程中同步代码直接执行,执行到异步操作时,将任务放在任务队列中(宏任务、微任务)
  • 当前宏任务执行完毕,读取微任务列表,执行所有的微任务
  • 执行浏览器UI线程的渲染工作
  • 检查执行web worker任务
  • 执行完本轮宏任务,回到步骤二,循环执行,直到所有的宏任务和微任务全部执行完毕

宏任务

  • script
  • setTimeout、setInterval
  • I/O
  • requestAnimationFrame
  • postMessage

微任务

  • promise的then、catch、finally await之后的代码

  • 对 Dom 变化监听的MutationObserver

  • process.nextTick(node环境中)

参考文章:https://vue3js.cn/interview/http/HTTP_HTTPS.html

参考文章:https://juejin.cn/post/6908327746473033741#heading-25

参考文章:https://juejin.cn/post/6916157109906341902/#heading-53