wulang8353 / DO-THE-JS-BETTER

:trollface: 我的真阳为至宝,岂肯轻与你这粉骷髅——JS :tada:

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`` 你是小公主`` » 从url到页面加载完会发生什么呢?

wulang8353 opened this issue · comments

从输入url到页面加载完成到底TMD发生了什么?

我希望是个女儿,这样我就见到你小时候的样子,弥补我迟到的20多年

单独把这一点拿出来是因为这是一道经典的面试题,浏览器渲染过程包含在了题目之中。这道题没有一个标准的答案,从前端的角度,我认为起码需要回答以下几点:

1、浏览器的地址栏输入URL并按下回车

2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期

3、DNS解析URL对应的IP

4、根据IP建立TCP连接(三次握手)

5、HTTP发起请求,浏览器接收HTTP响应

6、渲染页面,构建DOM树

7、关闭TCP连接(四次挥手)

1 浏览器的地址栏输入URL并按下回车

Web浏览器通过URL从Web服务器请求页面, 一个网页地址实例:

http://www.runoob.com/html/html-tutorial.html 

语法规则如下:

scheme://host.domain:port/path/filename

说明:

  • scheme 协议, 比如 http、https、ftp
  • domain 域名, 比如 runoob.com
  • port 端口号, http默认80, https默认443
  • host 主机名, http 的默认主机是 www
  • path 定义在服务器上文件的路径
  • filename 资源

这个属于基本知识了,其中还涉及到了同源策略、跨域、XSS与CRXF等,这里先不介绍了。

2 浏览器查找当前URL是否存在缓存,并比较缓存是否过期

根据是否需要重新向服务器发起请求来分类,可以将缓存类型分为强制缓存对比缓存。

  • 1、强制缓存: cache-control,Expires

Expires是一个绝对时间,即服务器时间。浏览器会检查当前时间,如果没有到服务器时间,说明还没有过期,直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端本地时间可能不一致,就无法判断文件的有效期,因此该字段已经很少使用。

cache-control中的 max-age 保存一个相对时间。例如Cache-Control: max-age = 484200,表示浏览器收到文件后,在484200s内缓存有效,在这段时间内,发出的请求都会直接使用缓存。 如果同时存在 cache-control 和 Expires,浏览器总是优先使用 cache-control。

default

  • 2、对比缓存:last-modified,Etag

Last-Modified/If-Modified-Since

上面说的当有效期过后,检查服务端文件是否更新的第一种方式,要配合Cache-Control使用。

last-modify1

按下 ctrl+r 刷新,ctrl+r会默认跳过 max-age 和 Expires 的检验直接去向服务器发送请求

last-modify2

last-modified 第一次请求资源时,服务器返回该字段,表示最后一次更新的时间。下一次浏览器请求资源时,请求中会发送if-modified-since 字段。服务器用自己本地 Last-modified 时间与if-modified-since 时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送 304 状态码,让浏览器继续使用缓存。

Etag/If-None-Match

ETag并不是文件的版本号,而是一串可以代表该文件唯一的字符串(文件的索引节,大小和最后修改时间进行Hash后得到的)

当客户端发现和服务器约定的直接读取缓存的时间过了,就在请求中发送If-None-Match选项,值即为上次请求后响应头的ETag值,该值在服务端和服务端代表该文件唯一的字符串对比(如果服务端该文件改变了,该值就会变),如果相同,则相应HTTP304,客户端直接读取缓存,如果不相同,HTTP200,下载正确的数据,更新ETag值。

etag

如果两者同时存在,If-None-Match/ETag优先,忽略Last-Modified/If-Modified-Since

HTTP1.1中 ETag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:

  • Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  • 如果某些文件会被定期生成,但有时内容并没有任何变化(仅仅改变了时间),但 Last-Modified 却改变了,导致文件没法使用缓存

  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

无法被浏览器缓存的请求:

  • HTTP响应头中不包含:Cache-Control/Expires
  • Last-Modified/Etag; 非 HTTP:
  • 需要根据 Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
  • 经过 HTTPS 安全加密的请求
  • POST 请求无法被缓存

default

通过上表可以看到,在按 F5 进行刷新的时候,会忽略 Expires/Cache-Control 的设置,会再次发送请求去服务器请求,而 Last-Modified/Etag 还是有效的,服务器会根据情况判断返回 304 还是 200;而当使用 Ctrl+F5 进行强制刷新的时候,只是所有的缓存机制都将失效,重新从服务器拉去资源

3 DNS解析URL对应的IP

当在浏览器中输入一个地址时,例如 www.baidu.com,其实不是百度网站真正意义上的地址。

互联网上每一台计算机的唯一标识是它的IP地址,但是IP地址并不方便记忆。用户更喜欢用方便记忆的网址去寻找互联网上的其它计算机,也就是上面提到的百度的网址。

可以这样理解, 整个网络相当于一个非常巨大的电话簿。我们在打电话的时候首先要找到名字,然后再去拨打对应的电话号码。

例如 www.baidu.com 这个域名就相当于名字,而如何找到对应的电话号码呢? DNS域名解析的过程实际是将域名还原为IP地址的过程,这样就能找到电话号码进行通信了。

DNS域名解析的过程

首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。

如果没找到则会查找本地DNS解析器缓存,如果查找到则返回。

如果还是没有找到则会查找本地DNS服务器,如果查找到则返回。

最后迭代查询,按根域服务器 ->顶级域,.cn->第二层域,hb.cn ->子域,www.hb.cn的顺序找到IP地址。

dns1

4 根据IP建立TCP连接(三次握手)

通过DNS域名解析后拿到IP地址,在获取到IP地址后,便会开始建立一次连接,这是由TCP协议完成的。

主要通过三次握手进行连接。

》 第一次握手: 建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认; 

》 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

》 第三次握手: 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

tcp

从图中就能看出为什么是三次握手而不是二次。因为要确保客户端与服务器双方的发送和接收的功能均正常。

default

  • 第一次握手,客户端传到服务器,建立连接

  • 第二次握手,服务器传到客户端

    服务器接收到客户端的消息,并发送消息到客户端,落脚点在客户端

    说明服务器能够正常接收消息,所以客户端的发送正常,服务器的接收也正常 -> 服务器 接收 √ | 客户端 发送

  • 第三次握手,客户端传到服务器

    客户端接收到服务器的消息,并发送消息到服务器,落脚点在服务器

    说明客户端能够正常接收消息,所以服务器的发送正常,客户端的接收也正常 -> 客户端 发送接收 √ | 服务器 接收发送

5 HTTP发起请求,浏览器接收HTTP响应

HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。

http

这里就不用说太多,平常开发调试中应该是经常遇的到,就讲讲状态码吧:

  • 1xx:指示信息–表示请求已接收,继续处理

  • 2xx:成功–表示请求已被成功接收、理解、接受

    • 204:无内容。服务器成功处理,但未返回内容
  • 3xx:重定向–要完成请求必须进行更进一步的操作

    • 301:永久移动。请求的资源已被永久的移动到新URI,会返回新的URI,并且自动定向到该地址

    • 302:临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI

    • 304: 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。 缓存经常遇到。

  • 4xx:客户端错误–请求有语法错误或请求无法实现

    • 401:请求身份认证,需要验证

    • 405:请求中的方法被禁止,接口中限定了请求方法

    • 415:服务器无法处理请求附带的媒体格式,一般是MIME

  • 5xx:服务器端错误–服务器未能实现合法的请求

    • 503:服务器暂时的无法处理客户端的请求,服务器未开启

    • 504:网管超时,一般是服务器方法有问题

6 渲染页面,构建DOM树

浏览器拿到内容后,渲染大概可以划分成以下几个步骤:

  • 1、DOM树:从上到下解析HTML文档建立DOM树
  • 2、CSSOM树:加载解析样式生成CSSOM树
  • 3、执行JavaScript:加载并执行JavaScript代码
  • 4、构建渲染树(render tree):根据DOM树和CSSOM树,生成渲染树(render tree)
  • 5、布局(layout 也叫Reflow回流):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置
  • 6、绘制(painting):遍历渲染树绘制所有节点
  • 7、浏览器会将各层的信息发送给GPU,GPU会将各层合成,显示在屏幕上

dom

如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。实际页面中,CSS 与 JavaScript 往往会多次修改 DOM 和 CSSOM。

HTML页面加载和解析流程具体一点来说:

1、解析HTML结构。发现 ``<head>`` 标签中有一个 ``<link> `` 标签引用外部CSS文件

2、浏览器又发出CSS文件的请求,服务器返回这个CSS文件

3、浏览器继续载入html中 ``<body>`` 部分的代码,并且CSS文件就位,开始渲染页面了

4、浏览器在代码中发现一个 ``<img>`` 标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码

5、服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码

6、浏览器发现了一个包含一行Javascript代码的 ``<script>`` 标签,解析并运行

7、Javascript命令浏览器隐藏掉掉某个DOM元素,浏览器重新渲染这部分代码

8、解析至</html>的到来,整个流程结束

defer与async

由上可知,script标签的存在会阻塞渲染,但浏览器总是并行加载资源,当浏览器遇到script时候:

1、<script src="script.js"></script>

没有defer和async,浏览器会立即加载并执行脚本,此时会将渲染挂起,页面会有延迟的感觉,所以一般会将其放在</body>之前

2、<script src="script.js" async></script>

有async,加载和渲染后续文档元素的过程和script.js的加载与执行并行进行,不一定要按照JS的顺序执行

3、<script src="script.js" defer></script>

有defer。加载后续文档元素的过程和script.js的加载并行,但是和script.js的执行要在所有元素解析完成之后, DOMContentLoaded事件(DOM树构建完成)之前完成,并且是按照JS的顺序执行

defer

7 关闭TCP连接(四次挥手)

这一过程可以由客户端或者服务器任一方来触发,流程如下:

default

第一次挥手:浏览器发完数据后,发送FIN请求断开连接。

第二次挥手:服务器发送ACK表示同意

第三次挥手:但考虑到服务器可能还有数据要发送,那么就等服务器发送数据结束后再发送FIN,表示断开连接

第四次挥手:浏览器需要返回ACK表示同意

为什么是四次挥手?

客户端发送 FIN 仅仅表示不再发送数据,但是还能接受数据。服务器发送ack表示知道你准备关闭,但是我这还没发送完。直到服务器也发送了 FIN 表示不再发送数据,同意现在关闭连接。浏览器接收后发送ack到服务器,整个连接关闭。

抽象理解成:

客户端:"老服同志,我不想讲了,要关闭连接了哈" -> FIN、ack

服务器:“你个老客,你要走了啊? 等回,我还没说完” -> ack

(服务器啰嗦了一会,看到没回应觉得没意思)

服务器: “算了,不说了,没意思,回见了您” -> FIN

客户端:“回见” -> ack

💔 兜兜转转两年, 到现在还是孑然一身。虽然自己也感觉到有所成长,但越来越孤独了呢~