以前面试时候常常被问及这个问题,支支吾吾回答没有底气,仔细研究了一下,发现里面学问还真很多。css
从输入 cnblogs.com 到博客园首页彻底展示这个过程能够大体分为 网络通讯 和 页面渲染 两个步骤。html
网络通讯走的五层因特网协议栈(OSI标准是七层模型,但实际实现一般是五层)。画了一张图:前端
五层因特网协议栈webpack
DNS属于应用层协议。客户端会先检查本地是否有对应的 ip 地址,若是有就返回,不然就会请求上级 DNS 服务器,知道找到或到根节点。这一过程可能会很是耗时,使用 dns-prefetch 可以使浏览器在空闲时提早将这些域名转化为 ip 地址,真正请求资源时就避免了这个过程的时间。例如京东首页的处理:web
京东首页dns-prefetch处理面试
HTTP也是应用层协议。HTTP(HyperText Transport Protocol)定义了一个基于请求/响应模式的、无状态的、应用层的协议,用于从万维网服务器传输超文本到本地浏览器。绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。客户端组织并发送 http 请求报文,包含 method、url、host、cookie 等信息,下面是访问博客园首页时 http 请求报文的样子:浏览器
GET https://www.cnblogs.com/ HTTP/1.1 Host: www.cnblogs.com Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36 Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: __gads=ID=b62b1e22b7de2e02:T=1493954370:S=ALNI_MYRebVRavER2PJmwdeFwpl33ACNoQ; If-Modified-Since: Mon, 27 Nov 2017 12:21:04 GMT
请求头里的每一个字段都有各自的做用,具体含义可查阅 http 协议相关文章。缓存
TCP 将 http 长报文划分为短报文,经过“三次握手”与服务器创建链接,进行可靠传输。“三次握手”创建链接的过程和打电话极像:性能优化
客户端:喂,我要和 Server 通话
服务端:你好,我是 Server,你是 Client 吗
客户端:没错,我是 Client服务器
链接创建成功,接下来就能够正式传送数据了。
数据传完以后断开tcp链接还要经过“四次挥手”,大概意思以下:
客户端:Server 小宝贝,我话说完了,你挂电话吧
服务端:我不挂,我不挂,你先挂,你不挂我也不挂
---------------- Client 一阵无语 --------------
服务端:你挂了吗
客户端:行,那我先挂了
至此完成了一次完整的资源请求响应。
须要注意的是,浏览器对同一域名下并发的tcp链接数是有限制的,2个到10个不等。为了解决这个资源加载瓶颈,有几种流行的优化方案:
好比页面样式所有打包在一个 css 文件内,页面逻辑所有打包在一个 js 文件内,图片拼合成雪碧图,这样可有效减小页面的资源请求数量。webpack 是时下最流行的模块打包工具之一,它能够将页面内全部资源(包括js,css,图片,字体等等)都打包进一个 js 文件,不明觉厉。
当浏览器向服务器请求一个静态资源时,会先发送该域名下的 cookies,服务器对于这些 cookie 根本不会作任何处理,所以它们只是在毫无心义的消耗带宽,因此应该确保对于静态内容的请求是无 cookie 的请求(也就是所谓的 cookie-free)。将站点的 js、css、图片等静态文件放在一个专门的域名下访问,因为该域名与主站域名不一样,因此浏览器就不会把主域名下的 cookies 传给该域,从而减小网络开销,特别是细碎静态文件特别多的状况下效果显著。
另外一方面,因为浏览器是基于域名的并发链接数限制,而不是页面。所以将资源部署在不一样的域名下可使页面的总并发链接数获得线性提高。
在 http 早期,每一个 http 请求都要打开一个 tcp 链接,请求完就关闭这个链接,致使每一个请求都要来一遍“三次握手”和“四次挥手”,从而磨磨唧唧多出来大量无谓的等待时间。就比如出去吃饭,等饭等半个小时,端上来十分钟吃完了,结帐排队又等了半个小时,要是刚进来就吃现成的吃完就跑那多爽啊。keep-alive 干的就是这件事,当第一个请求数据传输完毕以后,服务器说“客户端你不要关闭这个链接,直接换下个请求,我不想再握你的破手了”。这样下个请求就直接传输数据而不用先走“三次握手”的流程了。这比如你又去吃饭,吃你最喜欢的红烧肉,饭店在今天第一个客人点红烧肉的时候就炒了一大锅红烧肉,你点餐的时候直接吃现成的就好了,吃完直接跑,哈哈美滋滋。
将静态资源强制缓存在客户端,经过添加文件指纹等方式使客户端只请求发生了变动的资源,可有效下降静态资源请求数量。具体可参看前端静态资源缓存控制策略。
不少页面浏览量虽然很大,但其实很大比例用户扫完第一屏就直接跳走了,第一屏如下的内容用户根本就不感兴趣。 对于超大流量的网站,这个问题尤为重要。这时可根据用户的行为进行按需加载,用户用到了就去加载,用不到就不去加载。
以上都是从减小创建tcp链接数量的角度去优化页面性能,以后会分享更多前端性能优化方面的实用方法。
Internet Protocol 是定义网络之间彼此互联规则的协议,主要解决逻辑寻址和网络通用数据传输格式两个问题。
全部链接到因特网上的设备都会被分配一个惟一的 IP 地址,就像网购时填写的收货地址同样。因为一个网络设备的 IP 地址能够更换,可是 MAC 硬件地址(就像身份证号)通常是固定不变的,因此首先使用 ARP 协议来找到目标主机的 MAC 硬件地址。当通讯的双方不在同一个局域网时,须要屡次中转(路由器)才能找到最终的目标,在中转的过程当中还须要经过下一个中转站的 MAC 地址来搜索下一个中转目标。
传输层传来的 TCP 报文会在这一层被 IP 封装成网络通用传输格式——IP数据包,IP 数据包是真正在网络间进行传输的数据基本单元。
经过逻辑寻址定位到前面应用层 DNS 解析出来的 IP 地址的主机网络位置,而后把数据以 IP 数据包的格式发送到那去。
数据链路层负责将 IP 数据包封装成适合在物理网络上传输的帧格式并传输。设计数据链路层的主要目的就是在原始的、有差错的物理传输线路的基础上,采起差错检测、差错控制与流量控制等方法,将有差错的物理线路改进成逻辑上无差错的数据链路,向网络层提供高质量的服务。当采用复用技术时,一条物理链路上能够有多条数据链路。
上面这么多层其实都是在为不一样的目的对要传输的数据进行封装处理,而物理层则是经过各类传输介质(双绞线,电磁波,光纤等)以信号的形式将上面各层封装好的数据物理传送过去。
至此一个 http 请求漂洋过海终于到达了服务器,接下来就是从物理层到应用层向上传递,将封装的数据一层层剥开,服务器在应用层拿到最原始的请求信息后快速处理完,而后就开始向客户端发送响应信息。此次是以服务器为起点,客户端为终点再走一遍五层协议栈。
服务器的响应消息跋山涉水终于到达了浏览器,接下来就是页面渲染(更具体可参看浏览器内部工做原理)。
页面的渲染工做主要由浏览器的渲染引擎来完成(这里以Chrome为例)。
下面是渲染引擎在取得内容后的基本流程:
解析html构建dom树 -> 解析css构建render树 -> 布局render树 -> 绘制render树
渲染引擎首先开始解析html,并将标签转化为dom树中的dom节点。接着,它解析外部css文件及style标签中的样式信息,这些样式信息以及html标签中的可见性指令将被用来构建另外一棵树——render树。render树构建好了以后,将会执行布局过程,该过程将肯定render树每一个节点在屏幕上的确切坐标。最后是绘制render树,即遍历render树的每一个节点并将它们绘制到屏幕上。
偷了一张图片(Chrome和Safari所用内核webkit页面渲染主流程):
webkit页面渲染主流程
为了更好的用户体验,渲染引擎将会尽量早地将内容绘制在屏幕上,而不会等到全部的html都解析完成后再去构建、布局和绘制render树,它是解析完一部份内容就绘制一部份内容,同时可能还在经过网络下载其他内容(图片,脚本,样式表等)。好比说,浏览器在代码中发现一个 img 标签引用了一张图片,因而就向服务器发出图片请求,此时浏览器不会等到图片下载完,而是会继续解析渲染后面的代码,等到服务器返回图片文件,因为图片占用了必定面积,影响了后面段落的布局,浏览器就会回过头来从新渲染这部分代码。
render树节点和dom树节点相对应,但这种对应关系不是一对一的,不可见的dom元素不会被插入render树,例如head元素、script元素等。另外,display属性为none的元素也不会在渲染树中出现(visibility属性为hidden的元素将出如今渲染树中,这是由于visibility属性为hidden的元素虽然不可见但保留了元素的占位)。
又偷了一张图:
render树与dom树
当渲染对象被建立并添加到render树后,它们并无位置和大小,计算这些值的过程称为layout(布局)。
布局的坐标系统相对于根渲染对象(它对应文档的html标签,可用 document.documentElement
拿到),使用top和left坐标。根渲染对象的位置是 (0,0),它的大小是viewport即浏览器窗口的可见部分。布局是一个递归的过程,由根渲染对象开始,而后递归地经过一些或全部的层级节点,为每一个须要几何信息的渲染对象进行计算。
为了避免由于每一个小变化都所有从新布局,浏览器使用一个 dirty bit(页面重写标志位)系统,一个渲染对象发生了变化或是被添加了,就标记它及它的children为dirty——须要layout。
当layout在整棵渲染树触发时,称为全局layout,这可能在下面这些状况下发生:
layout也能够是增量的,这样只有标志为dirty的渲染对象会从新布局(也将致使一些额外的布局)。增量layout会在渲染对象dirty时异步触发,例如,当网络接收到新的内容并添加到dom树后,新的渲染对象会添加到render树中。
绘制阶段,遍历render树并调用渲染对象的paint方法将它们的内容显示在屏幕上。和布局同样,绘制也能够是全局的(绘制完整的树)或增量的。在增量的绘制过程当中,一些渲染对象以不影响整棵树的方式改变,改变的渲染对象使其在屏幕上的矩形区域失效(invalidate),这将致使操做系统将其看做dirty区域,并产生一个paint事件,操做系统很巧妙的处理这个过程,并将多个区域合并为一个。
浏览器老是试着以最小的动做响应一个变化,因此一个元素颜色的变化将只致使该元素的重绘,元素位置的变化将致使元素的布局和重绘,添加一个dom节点,也会致使这个元素的布局和重绘。一些主要的变化,好比增长html元素的字号,将会致使缓存失效,从而引发整个render树的布局和重绘。
等到绘制完毕,页面就彻底地展示在咱们面前了。
看似再简单不过的操做,背后支撑的技术链已经复杂到不可想象。上面只是粗浅的轮廓,其中的每一步深挖进去都是一门大学问。不过我们前端了解一下就好了,不必较这个劲,否则就舍本逐末了。
以为不错就点个推荐吧:)