Web/14-前端面试/10-01.页面性能优化.md
2019-04-13 17:28:47 +08:00

278 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

> 本文最初发表于[博客园](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`
ETagEntity 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)