前端学习笔记 - 浏览器渲染页面的过程及页面优化
前端学习笔记 - 浏览器渲染页面的过程及页面优化
从用户输入浏览器输入 url 到页面最后呈现,中间有哪些过程?
网络部分
1. 用户输入 URL 地址,回车
2. 浏览器解析 URL 解析出主机名和端口号
大多数URL的结构组成:<scheme>://<host>:<port>/<path>;<params>?<query>#<frag>
,更详细查询Wikipedia - URL,维基百科 - 统一资源定位符
除了合法的格式,浏览器还会检查字符是否合法。URL 为了在不同协议不同传输机制都可以安全的运送信息,采用的字符都是符合 ASCII 集的。这其中还包含一些保留字符,比如上面的 URL 协议中的分割字符,等特殊含义的字符。
而不安全的字符,非 ASCII 的 Unicode(中文等) 字符,就会通过转义去处理,使用 % 。
此部分就是检查 url 的合法性,并对不合法的进行转义。提取出 domain name 之后,就该是 DNS 解析相关的了,不过还要提一下,浏览器还会检查 HSTS 列表。
HSTS 是一种安全策略的机制,是为了让浏览器强制使用 HTTPS 访问网站。当你的网站均采用 HTTPS,并符合它的安全规范,就可以申请加入 HSTS 列表,之后用户不加 HTTPS 协议再去访问你的网站,浏览器都会定向到 HTTPS。详细请看 HSTS 详解。
3. 浏览器将主机名转换成服务器 IP 地址。
即 DNS 过程,步骤如下:
-
查看浏览器内部缓存
检测域名是否存在于浏览器缓存中,如果有缓存直接使用,没有则下一步。打开chrome://net-internals/#dns
即可查看本机浏览器的 DNS 缓存。 -
系统缓存
浏览器会调用一个类似gethostbyname
的库函数,此函数会先去检测本地 hosts 文件,查看是否有对应 ip。
PS: 这里有一个点,localhost 默认 ip 是 172.0.0.1,这是一个回路段,也叫换回接口。也就是不会发往服务器,是直接在本地打开的。
-
路由器缓存、ISP 缓存
如果浏览器和系统缓存都没有,系统的 gethostname 函数就会向 DNS 服务器发送请求。而网络服务一般都会先经过路由器以及网络服务商(电信),所以会先查询路由器缓存,然后再查询 ISP 的 DNS 缓存。 -
本地 DNS 服务器
通常为自己计算机搭建的小型 DNS 服务器,自我使用,属于 DNS 优化的一部分。 -
域名服务器
到此处的过程为:根域服务器(.) -> 顶级域名服务器(eg: .com,.org)->
主域名服务器(eg: google.com)
如果域名正常,应该就会返回 IP 地址,如果没有浏览器就会提示找不到服务器地址。
DNS 优化
DNS 查询的过程经历了很多的步骤,如果每次都如此,是不是会耗费太多的时间,资源。所以我们应该尽早的返回真实的 IP 地址,减少查询过程,也就是 DNS 缓存。
浏览器获取到 IP 地址后,一般都会加到浏览器的缓存中,本地的 DNS 缓存服务器,也可以去记录。另外,每天几亿网名的访问需求,一秒钟几千万的请求域名服务器如何满足?就是 DNS 负载均衡。
通常我们的网站应用各种云服务,或者各种服务商提供类似的服务,由他们去帮我们处理这些问题。DNS 系统根据每台机器的负载量,地理位置的限制
(长距离的传输效率)等等,去提供高效快速的 DNS 解析服务。
4. 浏览器建立一条与目标 Web 服务器的 TCP 连接
TCP 三次握手
IP 和端口号可以使 TCP 连接对应的服务器,网络信号经由网线传输到对应服务器的网络调节器后,再转化成数字信号,使计算机可以处理(发出和接收的过程是一样的)。那是不是这样就可以建立 TCP 连接了呢?还不行。TCP 为了建立可靠稳定的比特传输管道,会执行多次。
建立连接是有名的 TCP 协议三次握手,通俗理解就是:
- 客户端(我们的浏览器等)向服务器询问是否可连接
- 如果可以,服务器回复我们收到了,可连接我这没问题
- 客户端收到再确认,OK,可以传输了。
但为什么是“三次握手”?不是一次两次四次?
为什么是三次握手
资料里说的是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。失效的连接是怎样一种情况呢?我们都经历过网络不好的时候,网络中的延迟现象也是时有发生的。当客户端发送的一个请求在网络的某个地方停滞的时候,服务器端并不会感知到,延迟到一定时间就会发生超时现象,客户端通常会断开连接。而这时候停滞在途中的某个请求,又发送服务器了,假设不是约定“三次握手”,服务器认定客户端此时要建立连接,便会占用程序去监听处理此连接,但是客户端是完全“无感”的,并无连接,就会导致连接“错开”的现象。
同样的,服务器像客户端发请求也是一样的,请求滞后也会导致这种现象。所以要彼此确认,再建立连接。
所以说,“三次握手”是很必要的。第一次 client 端发送给 server 端,server 确认了 client 的请求功能正常以及自己的接收监听服务正常。第二次 server 再发送给 client,告诉 client 我已经收到了,client 端就可以确认,我的请求是没问题的,并且确认了 server 的请求功能正常。但是 server 并不知道自己请求是否成功了啊,所以 client 再发送一次请求,如果 server 同样接收到,server 就可以确认自己的请求是没问题的。这样,client,server 的收发才真正确认成功,通信正常,连接可以建立。
三次握手只能确认了双方的收发通信功能正常,要确保数据的正确稳定传输,还需要序列号、确认号等机制的辅助。
5. 浏览器向服务器发送一条 HTTP 请求报文
请求报文的格式:
<method> <request-URL> <version>
<headers>
<entity-body>
请求报文的详细说明见下一条。
6. 服务器向浏览器返回一条 HTTP 响应报文
响应报文的格式(注意,只有起始行的语法有所不同):
<version> <status> <reason-phrase>
<headers>
<entity-body>
起始行和首部就是由行分隔的 ASCII 文本。每行都以一个由两个字符组成的行终止序列作为结束,其中包括一个回车符(ASCII 码 13)和一个换行符(ASCII 码 10)。 这个行终止序列可以写做 CRLF。
起始行(Start Lines)
所有的 HTTP 报文都以一个起始行作为开始。请求报文的起始行说明了要做些什么。
响应报文的起始行说明发生了什么。
请求行(Request line)
请求报文请求服务器对资源进行一些操作。请求报文的起始行,或称为请求行,包含了一个方法和一个请求 URL,还包含 HTTP 的版本。所有这些字段都由空格符分隔。
响应行(Response line)
响应报文承载了状态信息和操作产生的所有结果数据,将其返回给客户端。响应报文的起始行,或称为响应行,包含了响应报文使用的 HTTP 版本、数字状态码,以 及描述操作状态的文本形式的原因短语。
方法(Methods)
请求的起始行以方法作为开始,方法用来告知服务器要做些什么。比如,在行 “GET /specials/saw-blade.gif HTTP/1.0”中,方法就是 GET。
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 |
2 | HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
状态码 (Status codes)
状态码是在每条响应报文的起始行中返回的。会返回一个数字状态和一个可读的状态。数字码便于程序进行差错处理,而原因短语则更便于人们理解。方法是用来告诉服务器做什么事情的,状态码则用来告诉客户端,发生了什么事情。
整体范围 | 已定义范围 | 分类 |
---|---|---|
100 ~ 199 | 100 ~ 101 | 信息提示 |
200~299 | 200~206 | 成功 |
300 ~ 399 | 300 ~ 305 | 重定向 |
400 ~ 499 | 400 ~ 415 | 客户端错误 |
500 ~ 599 | 500 ~ 505 | 服务器错误 |
更详细请看菜鸟教程 - HTTP状态码。
原因短语 (Reason phrases)
原因短语是响应起始行中的最后一个组件。它为状态码提供了文本形式的解释。比如,在行 HTTP/1.0 200 OK 中,OK 就是原因短语。
版本号(Version numbers)
版本号会以 HTTP/x.y 的形式出现在请求和响应报文的起始行中。
关于版本号更详细请看:HTTP 协议各版本特性
首部
跟在起始行后面的就是零个、一个或多个 HTTP 首部字段。
HTTP 首部字段向请求和响应报文中添加了一些附加信息。本质上来说,它们只是一些名 / 值对的列表。
首部分类
HTTP 规范定义了几种首部字段。应用程序也可以随意发明自己所用的首部。HTTP 首部可以分为以下几类。
通用首部(General headers):既可以出现在请求报文中,也可以出现在响应报文中。
请求首部(Request headers):提供更多有关请求的信息。
响应首部(Response headers):提供更多有关响应的信息。
实体首部(Entity headers):描述主体的长度和内容,或者资源自身。
扩展首部(Extension headers):规范中没有定义的新首部。
每个 HTTP 首部都有一种简单的语法:名字后面跟着冒号,然后跟上可选的空格,再跟上字段值,最后是一个 CRLF。
实体的主体部分(Entity Bodies)
HTTP 报文的第三部分是可选的实体主体部分。实体的主体是 HTTP 报文的负荷。 就是 HTTP 要传输的内容。HTTP 报文可以承载很多类型的数字数据:图片、视频、HTML 文档、软件应用程 序、信用卡事务、电子邮件等。
7. 关闭连接,浏览器解析文档
四次挥手
TCP 数据传输完,是要断开连接的。我说的数据传输完,是某一端发送完数据,要断开连接,去发送一个断开的请求,客户端和服务端都可以主动断开连接。为什么断开要四次呢?
第一次是连接的某一端 A 请求断开连接,发送报文段给 B,设置 seq = x 和 ackn = y,另外加一个标识位 FIN,表示已经没有数据发送了,请求断开。
B 收到请求,需要进行确认,即设置 ACK = 1,然后是 ackn = x(A 的 seq) + 1,B 的 seq 仍然是 y,只是确认收到 A 的关闭请求。
隔一段时间,B 再向 A 发送 FIN 报文段,请求关闭,FIN = 1,seq = y,ackn = x + 1。此时是最后确认阶段。
A 收到 B 的 FIN,向 B 发送 ACK,确认关闭,seq = x + 1,ACK = 1,ackn = y + 1。发送完之后,A 会进入 TIME_WAIT 的阶段,如果 B 收到 ACK 关闭连接,A 在 2MSL (报文最大生存时间)收不到 B 的响应就自己默认关闭了。
为什么断开要四次
对比 TCP 建立连接的时候,区别大概就是第二步拆成了两步。“三次握手”的时候确认 ACK 和同步 SYN 是一块返回的,断开连接则是分开发送,先发送 ACK 确认,再发送 FIN。这里主要是因为 B 端是被动断开的一方,A 发送完数据了,发送 FIN 表示我已经完事了,但是 B 不一定,也能还有数据会发送给 A。所以 B 会先 ACK 确认,然后当它真的没有数据要发送了,才会执行 FIN。
这种情况主要是由于 TCP 全双工传输的特性决定的。什么是全双工?先说一下半双工吧,举个栗子,有一条很窄的道路,只有单通道,但是却两个方向的车都可以走。当有一个方向的车进入,另一个方向的车就只能等待它通过才能进入。而全双工就是互不影响,你走你的,我走我的。所以 TCP 的数据传输也是这样,两端同时可以向对方发送数据,所以当 A 要断开连接的时候,B 接收到 FIN 表示没有数据会发来了,但是我还可以继续发送数据,可能还有数据要发,为了数据不丢失,即采用先确定后断开的方式。
8. 如果文档中有资源,重复6-7-8流程直至资源全部加载完毕
浏览器部分:
浏览器部分详细可浏览:浏览器的工作原理:新式网络浏览器幕后揭秘,非常详细,这里只做关键摘录。
主流程
呈现引擎从网络层获取文档内容以后,进入下面的主流程进行渲染。
呈现引擎将开始解析 HTML 文档,并将各标记逐个转化成“内容树”上的 DOM 节点。同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树。
呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。
呈现树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制 - 呈现引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。
需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。
主流程示例:
Webkit 主流程
Gecko 呈现引擎主流程
1. HTML解析出DOM Tree
这一步将文本的 HTML 文档,提炼出关键信息,形成如图的嵌套层级的树形结构,便于计算拓展。
这一步的具体算法上文链接有详细介绍,包括标记算法、树构建算法、容错机制等解释。
2. CSS解析出Style Rules
CSS Parser 将 CSS 解析成 Style Rules,Style Rules 也叫 CSSOM(CSS Object Model)。
StyleRules 也是一个树形结构,根据 CSS 文件整理出来的类似 DOM Tree 的树形结构:
与 HTML Parser相似,CSS Parser 作用就是将很多个 CSS 文件中的样式合并解析出具有树形结构 Style Rules。
脚本处理
浏览器解析文档,当遇到 <script>
标签的时候,会立即解析脚本,停止解析文档(因为 JS 可能会改动 DOM 和 CSS,所以继续解析会造成浪费)。
如果脚本是外部的,会等待脚本下载完毕,再继续解析文档。现在可以在 script
标签上增加属性 defer
或者 async
。
脚本解析会将脚本中改变DOM和CSS的地方分别解析出来,追加到 DOM Tree 和 Style Rules 上。
3. 将二者关联生成Render Tree(呈现树)
Render Tree的构建其实就是 DOM Tree 和 CSSOM Attach 的过程。
呈现器是和 DOM 元素相对应的,但并非一一对应。Render Tree 实际上就是一个计算好样式,与 HTML 对应的(包括哪些显示,哪些不显示)的 Tree。
样式计算
构建呈现树时,需要计算每一个呈现对象的可视化属性。这是通过计算每个元素的样式属性来完成的。
样式包括来自各种来源的样式表、inline 样式元素和 HTML 中的可视化属性(例如“bgcolor”属性)。其中后者将经过转化以匹配 CSS 样式属性。
样式计算存在以下难点:
-
样式数据是一个超大的结构,存储了无数的样式属性,这可能造成内存问题。
-
如果不进行优化,为每一个元素查找匹配的规则会造成性能问题。要为每一个元素遍历整个规则列表来寻找匹配规则,这是一项浩大的工程。选择器会具有很复杂的结构,这就会导致某个匹配过程一开始看起来很可能是正确的,但最终发现其实是徒劳的,必须尝试其他匹配路径。
Webkit 浏览器和 Firefox 浏览器通过共享样式数据、构建规则树、结构划分等方法计算样式,并最终和 Dom Tree 合并,得到呈现树。算法相当复杂,想详细了解还是请看链接。
渐进式处理
WebKit 使用一个标记来表示是否所有的顶级样式表(包括 @imports)均已加载完毕。如果在附加过程中尚未完全加载样式,则使用占位符,并在文档中进行标注,等样式表加载完毕后再重新计算。
4. Layout 根据 Render Tree计算每个节点的信息
呈现器在创建完成并添加到呈现树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。
HTML 采用基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历。
布局是一个递归的过程。它从根呈现器(对应于 HTML 文档的 <html>
元素)开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的呈现器计算几何信息。
布局的方式有全量布局和增量布局,全量布局指触发了整个呈现树范围的布局,增量布局指只对 Dirty 呈现器布局。全量布局往往是同步的,增量布局往往是异步的。
布局处理
布局通常具有以下模式:
- 父呈现器确定自己的宽度。
- 父呈现器依次处理子呈现器,并且:
- 放置子呈现器(设置 x,y 坐标)。
- 如果有必要,调用子呈现器的布局(如果子呈现器是 dirty 的,或者这是全局布局,或出于其他某些原因),这会计算子呈现器的高度。
- 父呈现器根据子呈现器的累加高度以及边距和补白的高度来设置自身高度,此值也可供父呈现器的父呈现器使用。
- 将其 dirty 位设置为 false。
5. Painting 根据计算好的信息绘制整个页面
在绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。
全局绘制和增量绘制
和布局一样,绘制也分为全局(绘制整个呈现树)和增量两种。在增量绘制中,部分呈现器发生了更改,但是不会影响整个树。更改后的呈现器将其在屏幕上对应的矩形区域设为无效,这导致 OS 将其视为一块“dirty 区域”,并生成“paint”事件。OS 会很巧妙地将多个区域合并成一个。在 Chrome 浏览器中,情况要更复杂一些,因为 Chrome 浏览器的呈现器不在主进程上。Chrome 浏览器会在某种程度上模拟 OS 的行为。展示层会侦听这些事件,并将消息委托给呈现根节点。然后遍历呈现树,直到找到相关的呈现器,该呈现器会重新绘制自己(通常也包括其子代)。
动态变化
在发生变化时,浏览器会尽可能做出最小的响应。因此,元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大html
元素的字体)会导致缓存无效,使得整个呈现树都会进行重新布局和绘制。
如何优化页面渲染?
从浏览器渲染的角度
浏览器对上文介绍的关键渲染路径进行了很多优化,针对每一次变化产生尽量少的操作,还有优化判断重新绘制或布局的方式等等。
在改变文档根元素的字体颜色等视觉性信息时,会触发整个文档的重绘,而改变某元素的字体颜色则只触发特定元素的重绘;改变元素的位置信息会同时触发此元素(可能还包括其兄弟元素或子级元素)的布局和重绘。
某些重大改变,如更改文档根元素的字体尺寸,则会触发整个文档的重新布局和重绘,据此及上文所述,推荐以下优化和实践:
-
HTML文档结构层次尽量少,最好不深于六层;
-
脚本尽量后放,放在前即可;
-
少量首屏样式内联放在标签内;
-
样式结构层次尽量简单;
-
在脚本中尽量减少DOM操作,尽量缓存访问DOM的样式信息,避免过度触发回流;
-
减少通过JavaScript代码修改元素样式,尽量使用修改class名方式操作样式或动画;
-
动画尽量使用在绝对定位或固定定位的元素上;
-
隐藏在屏幕外,或在页面滚动时,尽量停止动画;
-
尽量缓存DOM查找,查找器尽量简洁;
-
涉及多域名的网站,可以开启域名预解析。
从资源加载的角度
本部分摘自知乎 - 极乐科技专栏 - 李银成 - Effective 前端7:加快页面打开速度,本博客只摘录优化方法和结论,原文有十分具体的分析过程可供学习、理解。
1. 减少渲染堵塞
(1) 避免head标签JS堵塞
所有放在head标签里的css和js都会堵塞渲染。如果这些CSS和JS需要加载和解析很久的话,那么页面就空白了。
有两种解决办法,第一种是把script放到body后面,这也是很多网站采取的方法。
第二种是给script加defer的属性,defer是html5新增的属性。一旦script是defer延迟的,那么这个script将会异步加载,但不会马上执行,会在readystatechange变为Interactive后按顺序依次执行。
defer的脚本会正常加载,但是延后执行,在interactive后执行,所以不会block页面渲染。因此放在head标签的script可以加一个defer,但是加上defer的脚本发生了重大的变化,不能够影响渲染DOM的过程,只能是在渲染完了,例如绑的click事件在整个页面没渲染好之前不能生效。并且很多人要把它写在head里面,是为了在页面中间的script能调用到这些库,影响DOM的渲染,加上defer就违背本意了。但是把script写在head标签始终是不推荐的,所以在页面中间的script要么使用原生的API,要么把一些用到的函数写成head标签里面的内联script。
head标签里面的defer脚本跟放在body后面的脚本有什么区别呢,区别在于defer的脚本会马上加载,一旦页面interactive了便可以马上执行,而放body后面是document快interactive的时候才去加载。我们知道,浏览器同时加载资源数是有限的,例如Chrome对同一个域名的资源,每次最多同时只能建立6个TCP连接(读者可以试试)。所以写在head标签里面的外链脚本会影响页面图片的加载,特别是当脚本很多时。因此,如果页面的交互比图片的展示更重要,那么把script写在head标签加上defer是可取的,如果页面的展示更为重要,那么应该把脚本放在body后面。
另外,defer可能会有兼容性问题,在老的浏览器上某些行为表现可能会不一致。
同样地,head标签里面的CSS资源也会block页面渲染。
(2) 减少head标签里的CSS资源
由于CSS必须要放在head标签里面,如果放在body里面,一旦加载好之后,又会对layout好的dom进行重排,样式可能又会发生闪烁。但是一旦放在head标签里面又会堵塞页面渲染,若要加载很久,页面就会保持空白状态。所以要尽可能地减少CSS的代码量。
a) 不要放太多base64放在CSS里面
b)把CSS写成内联的:
2. 优化图片
(1)使用响应式图片
响应式图片的优点是浏览器能够根据屏幕大小、设备像素比ppi、横竖屏自动加载合适的图片,如果屏幕的ppi = 1的话则加载1倍图,而ppi = 2则加载2倍图,手机和mac基本上ppi都达到了2以上,这样子对于普通屏幕来说不会浪费流量,而对于视网膜屏来说又有高清的体验。
如果浏览器不支持srcset,则默认加载src里面的图片。
(2)延迟加载图片
对于很多网站来说,图片往往是占据最多流量和带宽的的资源。特别是那种瀑布式展示性的网站,一个页面展示50本书,50张图片,如果一口气全部放出来,那么页面的Loaded时间将会较长,并且由于并行加载资源数是有限,图片太多会导致放body后面的js解析比较慢,页面将较长时间处于不可交互状态。所以不能一下子把全部图片都放出来,这对于手机上的流量也是不利的。
3. 压缩和缓存
(1) gzip压缩
使用gzip压缩可以大大减少文件的体积,一个180k的CSS文件被压成了30k,减少了83%的体积。
(2) Cache-Control
(3) 使用etag
4. 其它优化方案
(1) DNS预读取
域名解析可能会花很长的时间,而一个网站可能会加载很多个域的东西,例如使用了三个自已子域名的服务,再使用了两个第三方的CDN,再使用了百度统计/谷歌统计的代码,还使用了其它网站上的图片,一个网站很可能要加载七、八个域的资源,第一次打开时,要做七、八次的DNS查找,这个时间是非常可观的。因此,DNS预读取技术能够加快打开速度,方法是在head标签里面写上几个link标签:
<link rel="dns-prefection" href="https://www.google.com">
<link rel="dns-prefection" href="https://www.google-analytics.com">
<link rel="dns-prefection" href="https://connect.facebook.net">
<link rel="dns-prefection" href="https://googleads.g.doubleclick.net">
<link rel="dns-prefection" href="https://staticxx.facebook.com">
<link rel="dns-prefection" href="https://stats.g.doubleclick.net">
如上,对以上向个网站提前解析DNS,由于它是并行的,不会堵塞页面渲染。这样可以缩短资源加载的时间。
(2) html优化
把本地的html布署到服务器上前,可以先对html做一个优化,例如把注释remove掉,把行前缩进删掉。
(3) 代码优化
对自己写的代码做优化,提高运行速度,例如说html别嵌套太多层,否则加重页面layout的压力,CSS的选择器别写太复杂,不然匹配的计算量会比较大,对JS,别滥用闭包,闭包会加深作用域链,加长变量查找的时间。