前端面试浏览器、网络部分

2019-01-06  本文已影响11人  冷小谦

问题1.前端面试经常会问及:输入url到整个页面呈现发生了什么。

这个问题可以分两个方面回答:网络通信和浏览器渲染。

网络通信

DNS解析

用户输入url点击回车。先会进行DNS解析。
由于用户输入的url浏览器不能识别,因此需要通过DNS域名系统(域名和IP地址映射)对url进行解析,得到想要得到网站的主机名对应的ip地址。

TCP三次握手

拿到对应的ip之后,浏览器会以随机端口向服务器的http80端口发起TCP连接请求,这个请求通过4层模型的封包,根据IP地址,通过各种路由、网关(ARP,MAC),到达服务器端,进入内核的TCO/IP协议栈,层层解包,最终到达应用层,建立TCP/IP连接。

因为TCP是面向连接的,所以需要先通过三次握手建立连接,三次握手的目的:同步连接双方的序列号和确认号,交换TCP窗口信息。


三次握手
  1. 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位 置为1,Sequence Number(seq)为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
  2. 服务器收到SYN报文段。对报文进行确认,将ACK设置为x+1(seq+1) ,服务器端将所有信息(即SYN+ACK+seq报文段)放到一个报文段中,一并发送给客户端,此时服务器进入SYN_RECV状态;
  3. 客户端收到SYN+ACK+Seq报文,将ACK设置为y+1(seq+1),向服务器发送ACK报文段,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

为什么要三次握手
防止已失效的连接请求报文段传送给服务器。client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。这样,server的很多资源就白白浪费掉了

数据传输完毕,断开TCP连接,就是四次挥手。


四次挥手
  1. 主机1(挥手既可以是客户端也可以是服务器端),设置sequence number ,向主机发送一个FIN报文段,主机1进入FIN_WAIT_1状态,表示主机1没有数据发给主机2了
  2. 主机2收到主机1发送的FIN报文段,向主机1回一个ACK报文段,主机1进入FIN_WAIT_2状态
  3. 主机2向主机1发送FIN报文段,请求关闭连接,主机2进入LAST_ACK状态
  4. 主机1收到主机2的报文段,向主机2发送ACK报文,主机2接到后关闭连接,主机1等待MSL后,关闭连接(MSL报文最大生存时间,TCP的TIME_WAIT状态也称为2MSL等待状态)
    为什么要四次分手
    TCP面向连接,且全双工,主机1没有数据发送的时候,主机2可能还在发送数据,所以主机2告诉主机1收到数据,还要告诉主机1发送完数据。

为什么要等待2MSL
它是任何报文段被丢弃前在网络内的最长时间,超过这个时间报文会被丢弃。如果主机1直接close,然后再向相同服务器相同端口发送请求,可能会混淆数据包。此外主机2没有收到ACK,那么就会继续发送一次FIN

发起http请求

建立TCP/IP连接后,客户端向服务器端发起http请求,

浏览器渲染流程

浏览器主要组成
浏览器主要组件
渲染引擎流程
渲染流程
  1. 解析html构建DOM树
    浏览器接收到服务器响应来的html文档开始,遍历文档节点,字节转化为字符,字符转化为标记,生成dom树。

html解析分为标记化和树构建。词法分析时将输入内容解析成多个标记,HTML标记包括起始标记、结束标记、属性名称和属性值。标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。 标记生成器发送的每个节点都会由树构建器进行处理。
初始状态是数据状态。遇到字符 < 时,状态更改为“标记打开状态”。接收一个 a-z字符会创建“起始标记”,状态更改为“标记名称状态”。这个状态会一直保持到接收> 字符。在此期间接收的每个字符都会附加到新的标记名称上。


构建dom树

2.解析css 构建cssom树:
浏览器解析css文件并城市css树。css从右向左解析,嵌套越多越增加浏览器工作量

  1. 渲染阻塞:
    浏览器遇到script标签,dom构建将停止,直到脚本完成执行,然后继续构建dom。所以在渲染时遵循两个原则:css引入在前,js放在页面底部,尽量少影响dom的构建,延长首绘时间,可以用async和defer实现异步加载。js解析由js解析器执行。

浏览器碰到script标签,会立刻执行脚本
async加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步),加载完立刻执行
defer加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
defer和async区别在于下载完后何时执行,defer性能更优

  1. 构建渲染树:
    渲染引擎渲染。浏览器先从dom树中遍历可见节点,并找到其适配的css样式。dom树 css树 渲染树交叉进行,边加载、边解析、边渲染
    渲染树构建完成,渲染树和dom树并非一一对应,每个节点都是可见节点并且都含有其内容和对应规则的样式。display:none不会被显示在树中,visibility:hidden会显示在树中。
  2. 渲染树布局
    布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。

布局过程为:
父节点确定自己的宽度

父节点完成子节点放置,确定其相对坐标

节点确定自己的宽度和高度
父节点根据所有的子节点高度计算自己的高度

  1. 渲染树绘制
    在绘制阶段,遍历渲染树,调用渲染器的paint方法在屏幕上显示其内容。由浏览器的ui负责

问题2.回流和重绘

浏览器默认流式布局。js会打破这种布局,改变dom的外观样式和大小位置,即reflow和repaint
repaint:即渲染树重新绘制,不影响整体布局,元素几何尺寸和位置不变。
reflow:元素几何尺寸变化,重新布局和绘制渲染树。

触发reflow一定会触发repaint,但是触发repaint不一定会reflow。

display:none会使元素消失,触发reflow,repaint。visibility:hidden会触发。

问题3.优化

  1. 解决渲染阻塞
    减少脚本阻塞页面渲染,在标签中使用async和defer
  2. 减少dom操作
    减少对DOM元素的查询和修改
  3. 让需要经常改动的节点脱离文档流
  4. 使用CDN
  5. 精简js和css

问题4.TCP流量控制、拥塞控制

流量控制

如果发送方把数据发送过快,急售房可能来不及接收,就会造成数据的丢失,所以需要流量控制让发送方发送速率不要太快,接收方来的及接收。
设置接收窗口rwnd(receive window)字节,数据报文中,大写ACK表示首部中的确认位ACK,小写ack表示确认字段的值ack


image

发送方向接收方发送seq,如果接收方进行流量控制,发送方发送的部分数据会丢失,那么接收方回复发送方的报文中,ACK=1说明字段有意义,ack是重发送的字段的值。rwnd=0不允许发送方发送数据

拥塞控制

慢开始和拥塞避免
发送方维持一个拥塞窗口(cwnd),拥塞窗口的大小取决于网络的拥塞程度,动态变化。网络没有堵塞,窗口就增大,网络堵塞,窗口就减小。
如果刚开始发送大量数据,不清楚网络负荷情况,会引起网络堵塞,所以先用慢开始算法,有小到大增大拥塞窗口。
通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。


慢开始

每经过一个传输轮次,拥塞窗口 cwnd 就加倍。一个传输轮次所经历的时间其实就是往返时间RTT。不过“传输轮次”更加强调:把拥塞窗口cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。

让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。


image

那么这两个算法是怎么配合使用的呢

为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。慢开始门限ssthresh的用法如下:

当 cwnd < ssthresh 时,使用上述的慢开始算法。
当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。

无论是慢开始还是拥塞避免阶段,只要发送方判断网络出现拥塞,就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。


慢开始和拥塞避免

慢开始,指数增长。到达门限值,拥塞避免线性增长。网络拥塞,门限设置为窗口值的一半,拥塞窗口从1重新开始。

快重传和快恢复

快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认。


image

M3丢失,M4是失序报文段,所以接收方会连续三次发送M2的确认。

快恢复:当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。
与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为 慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。


image

问题5.跨域

跨域问题是前端必考考点,主要他有很多的陷阱,而且涉及很多的浏览器、网络上的知识。

首先要交代的跨域是浏览器行为,所有请求都会发到服务器端,如果符合要求,服务器都会返回数据。但是浏览器为了web安全,会做出一些限制,也就是:同源策略

同源策略

http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。它的同源情况如下:
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

插播一下一个域名地址的组成。


image.png

在域名地址中,排在后面的域名包含前面的域名,也就是主域名在后面,子域名在前面。

我一直觉得域名和url很类似,其实确实很类似。
域名是个文字形式记录的IP地址
IP地址是计算机在网络中的门牌号。
URL是网页地址
例如 http://zhidao.baidu.com/question/14674128.html 是URL
zhidao.baidu.com就是域名
xx.xx.xx.xx 就是IP地址
计算机通过你输入的url找到域名,再通过域名拿到ip,到了服务器,通过url后面的参数知道资源具体存放端口地址 ,文件位置
由协议,子域名,主域名,端口号和请求资源地址。
这就是ip、url、域名三者的关系。在nodejs中,有专门的url模块,parse方法可以对url地址进行解析。

如何跨域

其实网上有很多方法,jsonp,CORS,但是里面的原理却讲的很少,其实成功跨域的方法就是解决限制跨域的机制。主要有三个方面:

服务器正向代理
和你浏览器同源的服务器,比如本地服务器,向不同源的服务器去请求数据然后转发给,这样就避开了浏览器同源策略,因为服务器之间是没有同源策略的。
反向代理:反向代理是使用ngix地址映射。具体做法是,将你要请求的不同源的服务接口映射到同源的一个地址,然后你是向这个同源的地址请求数据。举个梨子:

源A上的一个页面:http://baidu.com/index
源B上的一个资源:http://tx.com/accept
源A上的这个页面要请求源B上的这个资源,直接请求肯定是不行的,所以我们在源A的服务器上虚拟出这样一个服务地址,假装我们提供这样一个服务,http://baidu.com/expresslove
但是其实我们偷偷的把这个地址映射到 http://tx.com/accept上面去了。
这时候,源A的页面是请求 http://baidu.com/expresslove 这个服务,源A服务器实际是向 http://tx.com/accept 接口请求,然后把数据返回给前端。

其实不管是正向代理还是反向代理,都是利用服务器没有同源策略可以拿到数据这一特性,解决的跨域问题。

上一篇下一篇

猜你喜欢

热点阅读