

> 本文最初发表于[博客园](https://www.cnblogs.com/smyhvae/p/8550195.html)，并在[GitHub](https://github.com/qianguyihao/Web)上持续更新**前端的系列文章**。欢迎在GitHub上关注我，一起入门和进阶前端。

> 以下是正文。


## 前言

提升页面性能优化的方法有哪些：

- 1、资源压缩合并，减少http请求

- 2、**非核心代码异步加载** --> 异步加载的方式 --> 异步加载的区别

如果回答出`非核心代码异步加载`，就会层层深入。

- 3、利用浏览器缓存  --> 缓存的分类  --> 缓存的原理

**缓存**是所有性能优化的方式中最重要的一步，这个一定要答好。【重要】

有的人可能会回答local storage 和session storage，其实不是这个。浏览器缓存和存储不是一回事。

- 4、使用CDN

浏览器第一次打开页面时，缓存是起不了作用的。CDN这一条，一定要说出来。

- 5、DNS预解析

## 一、资源压缩合并，减少http请求

- 合并图片（css sprites）、CSS和JS文件合并、CSS和JS文件压缩

- 图片较多的页面也可以使用 lazyLoad 等技术进行优化。

- 精灵图等

## 二、非核心代码异步加载

异步加载的方式：（这里不说框架，只说原理）

- 动态脚本加载

- defer

- async

### 动态脚本加载

使用document.createElement创建一个script标签，即`document.createElement('script')`，然后把这个标签加载到body上面去。

参考链接：

- [javascript 异步加载](https://www.jianshu.com/p/13cf23a90328)  动态脚本加载的那部分代码，看不太懂。

### defer

通过异步的方式加载defer1.js文件：

```html
    <script src="./defer1.js" defer></script>
```

### async

> HTmL5新增特性。

通过异步的方式加载async1.js文件：

```html
    <script src="./async1.js" async></script>
```

### defer和async的区别

- defer：在HTML解析完之后才会执行。如果是多个，则按照加载的顺序依次执行。

- async：在加载完之后立即执行。如果是多个，执行顺序和加载顺序无关。

代码举例：

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <!--通过异步的方式引入两个外部的js文件-->
    <script src="./defer1.js" defer></script>
    <script src="./defer2.js" defer></script>
</head>

<body>
<script>
    console.log('同步任务');
</script>
</body>

</html>
```

上方打印的结果是：

```
同步任务
defer1
defer2
```

因为defer的加载是有顺序的，所以两个引入defer文件按顺序执行。如果把引入的文件改为async的方式加载，打印的结果可能是：

```
同步任务
async2
async1
```

参考链接：

- [浅谈script标签的defer和async](https://segmentfault.com/a/1190000006778717)


## 三、利用浏览器缓存


**缓存**：资源文件（比如图片）在**本地的硬盘**里存有副本，浏览器下次请求的时候，可能直接从本地磁盘里读取，而不会重新请求图片的url。

缓存分为：

- 强缓存

- 协商缓存

### 强缓存

**强缓存**：不用请求服务器，直接使用本地的缓存。

强缓存是利用 http 响应头中的**`Expires`**或**`Cache-Control`**实现的。【重要】

浏览器第一次请求一个资源时，服务器在返回该资源的同时，会把上面这两个属性放在response header中。比如：

![](http://img.smyhvae.com/20180310_2310.png)

**注意**：这两个response header属性可以只启用一个，也可以同时启用。当response header中，Expires和Cache-Control同时存在时，**Cache-Control的优先级高于Expires**。

下面讲一下二者的区别。

**1、`Expires`**：服务器返回的**绝对时间**。

是较老的强缓存管理 response header。浏览器再次请求这个资源时，先从缓存中寻找，找到这个资源后，拿出它的Expires跟当前的请求时间比较，如果请求时间在Expires的时间之前，就能命中缓存，否则就不行。

如果缓存没有命中，浏览器直接从服务器请求资源时，Expires Header在重新请求的时候会被更新。

**缺点：**

由于`Expires`是服务器返回的一个绝对时间，存在的问题是：服务器的事件和客户端的事件可能不一致。在服务器时间与客户端时间相差较大时，缓存管理容易出现问题，比如随意修改客户端时间，就能影响缓存命中的结果。所以，在http1.1中，提出了一个新的response header，就是Cache-Control。


**2、`Cache-Control`**：服务器返回的**相对时间**。

http1.1中新增的 response header。浏览器第一次请求资源之后，在接下来的相对时间之内，都可以利用本地缓存。超出这个时间之后，则不能命中缓存。重新请求时，`Cache-Control`会被更新。

### 协商缓存

**协商缓存**：浏览器发现本地有资源的副本，但是不太确定要不要使用，于是去问问服务器。

当浏览器对某个资源的请求没有命中强缓存（也就是说超出时间了），就会发一个请求到服务器，验证协商缓存是否命中。

协商缓存是利用的是两对Header：

- 第一对：`Last-Modified`、`If-Modified-Since`

- 第二对：`ETag`、`If-None-Match`

ETag（Entity Tag）：被请求变量的实体值”。


**1、`Last-Modified`、`If-Modified-Since`**。过程如下：

（1）浏览器第一次请求一个资源，服务器在返回这个资源的同时，会加上`Last-Modified`这个 response header，这个header表示这该资源在服务器上的最后修改时间：

![](http://img.smyhvae.com/20180311_1715.png)

（2）浏览器再次请求这个资源时，会加上`If-Modified-Since`这个 request header，这个header的值就是上一次返回的`Last-Modified`的值：

![](http://img.smyhvae.com/20180311_1716.png)

（3）服务器收到第二次请求时，会比对浏览器传过来的`If-Modified-Since`和资源在服务器上的最后修改时间`Last-Modified`，判断资源是否有变化。如果没有变化则返回304 Not Modified，但不返回资源内容（此时，服务器不会返回 Last-Modified 这个 response header）；如果有变化，就正常返回资源内容（继续重复整个流程）。这是服务器返回304时的response header：

![](http://img.smyhvae.com/20180311_1720.png)

（4）浏览器如果收到304的响应，就会从缓存中加载资源。

**缺点：**

`Last-Modified`、`If-Modified-Since`一般来说都是非常可靠的，但面临的问题是：

- **服务器上的资源变化了，但是最后的修改时间却没有变化**。


- 如果服务器端在一秒内修改文件两次，但产生的`Last-Modified`却只有一个值。

这一对header就无法解决这种情况。于是，下面这一对header出场了。



**2、`ETag`、`If-None-Match`**。过程如下：

（1）浏览器第一次请求一个资源，服务器在返回这个资源的同时，会加上`ETag`这个 response header，这个header是服务器根据当前请求的资源生成的**唯一标识**。这个唯一标识是一个字符串，只要资源有变化这个串就不同，跟最后修改时间无关，所以也就很好地补充了`Last-Modified`的不足。如下：

![](http://img.smyhvae.com/20180311_1735.png)

（2）浏览器再次请求这个资源时，会加上`If-None-Match`这个 request header，这个header的值就是上一次返回的`ETag`的值：

![](http://img.smyhvae.com/20180311_1737.png)

3）服务器第二次请求时，会对比浏览器传过来的`If-None-Match`和服务器重新生成的一个新的`ETag`，判断资源是否有变化。如果没有变化则返回304 Not Modified，但不返回资源内容（此时，由于ETag重新生成过，response header中还会把这个ETag返回，即使这个ETag并无变化）。如果有变化，就正常返回资源内容（继续重复整个流程）。这是服务器返回304时的response header：

![](http://img.smyhvae.com/20180311_1740.png)

（4）浏览器如果收到304的响应，就会从缓存中加载资源。

提示：如果面试官问你：与浏览器缓存相关的http header有哪些？你能写出来吗？这是一个亮点。

参考链接：

- [浏览器缓存知识小结及应用](https://www.cnblogs.com/lyzg/p/5125934.html)[荐]

## 四、使用CDN

怎么最快地让用户请求资源。一方面是让资源在传输的过程中变小，另外就是CDN。

要注意，浏览器第一次打开页面的时候，浏览器缓存是起不了作任何用的，使用CDN，效果就很明显。

## 五、DNS预解析（dns-prefetch）

通过 DNS 预解析来告诉浏览器未来我们可能从某个特定的 URL 获取资源，当浏览器真正使用到该域中的某个资源时就可以尽快地完成 DNS 解析。

**第一步**：打开或关闭DNS预解析

你可以通过在服务器端发送 X-DNS-Prefetch-Control 报头。或是在文档中使用值为 http-equiv 的meta标签：

```html
	<meta http-equiv="x-dns-prefetch-control" content="on">
```

需要说明的是，在一些高级浏览器中，页面中所有的超链接（`<a>`标签），默认打开了DNS预解析。但是，如果页面中采用的https协议，很多浏览器是默认关闭了超链接的DNS预解析。如果加了上面这行代码，则表明强制打开浏览器的预解析。（如果你能在面试中把这句话说出来，则一定是你出彩的地方）

**第二步**：对指定的域名进行DNS预解析

如果我们将来可能从 smyhvae.com 获取图片或音频资源，那么可以在文档顶部的 <head> 标签中加入以下内容：

```html
	<link rel="dns-prefetch" href="http://www.smyhvae.com/">
```

当我们从该 URL 请求一个资源时，就不再需要等待 DNS 解析的过程。该技术对使用第三方资源特别有用。

参考链接：

- [前端性能优化 - 资源预加载](http://bubkoo.com/2015/11/19/prefetching-preloading-prebrowsing/)[荐]

- [DNS预解析详解](https://www.xuanfengge.com/dns-prefetching-analysis.html)



## 我的公众号

想学习<font color=#0000ff>**代码之外的技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。

扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：

![](http://img.smyhvae.com/2016040102.jpg)

