Fork me on GitHub

m-页面性能

提升页面性能的方法
参考
https://juejin.im/post/5b73ef38f265da281e048e51#heading-22

页面性能优化

一.页面内容

1.减少 http 请求

  • 合并 js 脚本和 css 样式表
  • iconfont 代替小图标
  • 使用 base64 格式的小图片

2.减少 DNS 查询

域名查询:将主机名映射到 IP 地址
DNS 查询完成之前,浏览器无法从主机名下载任何东西。

  • DNS 缓存机制

  • 减少资源存储在不同主机名下的数量,但这样同时减少页面能够并行下载的数量,为了避免 DNS 查找削减响应时间,却增加了因减少并行下载数量增加的响应时间,原则上把组件分散到 2-4 个主机名下,,同时减少 DNS 查询和允许高并发下载的这中方案。

预解析 DNS
DNS 请求虽然占用了很少的带宽,但是延迟很高,DNS 预解析可以很好的降低延迟。

  • 用 meta 告诉浏览器,当前页面要做 DNS 预解析

    1
    <meta http-equiv='x-dns-prefetch-control' content='on'>
  • 在页面 header 中使用 link 标签来强制对 DNS 预解析

    1
    2
    // 强制查询特定主机名
    <link rel='dns-prefetch' href='http://bdimg.share.baidu.com'>

    注:dns-prefetch 需慎用,多页面重复 DNS 预解析会增加重复 DNS 查询次数。

需要注意的是,虽然使用 DNS Prefetch 能够加快页面的解析速度,但是也不能滥用,因为有开发者指出 禁用 DNS 预读取能节省每月 100 亿的 DNS 查询 。

如果需要禁止隐式的 DNS Prefetch,可以使用以下的标签:

1
<meta http-equiv="x-dns-prefetch-control" content="off">

3. 避免重定向

  • 最浪费的重定向经常发生、而且很容易被忽略:URL 末尾应该添加/但未添加。
  • 网站域名变更。

4. 延迟加载

非页面初始加载的必须资源延迟加载

  • 非首屏使用的数据、样式、脚本、图片等
  • 用户交互时才会显示的内容

5. 预加载

利用浏览器空闲时间请求将来要使用的资源,以便用户访问下一页面更快响应。

  • 无条件预先加载
  • 有条件预先加载:根据用户预先判断用户取消,加载相关资源
  • 有【阴谋】的预先加载:页面即将上线新版预先加载新版内容

6. 减少 DOM 元素数量

  • 避免使用表格布局
  • 通过伪元素实现功能
  • 为了布局使用的无实际意义div,能否使用方法处理

    表格布局的缺点:

    • 标签多,增加文件大小
    • 不一维护,无法适应响应式布局
    • 默认的表格布局算法产生大量的重绘

7. 划分内容到不同的域名

浏览器一般限制每个域的并行线程(6 个或更少),使用不同的域名可以最大化线程,注意保持在 2-4 个域名内,避免 DNS 查询损耗

8.减少 iframe 的使用

优点:

  • 用来加载速度较慢的第三方资源,广告
  • 作为安全沙箱
  • 并行下载脚本

缺点:

  • 加载代价高昂
  • 阻塞页面 load 事件触发

    iframe 完全加载后,父页面才会触发 load 事件,Safari,Chrome 中通过 JS 动态设置 iframe src 避免这个问题

  • 缺乏语义

9. 避免 404 错误

无效的 404 响应,浪费服务器资源

二、服务器

1. 使用 CDN

用户与服务器的物理距离对响应事件也有影响。
静态内容分发网络:一组分散在不同地理位置的 web 服务器,用来给用户更高效的发送内容。

2.HTTP 缓存,添加 Expires 或 Catch-Control 响应头

强缓存表示在缓存期间不需要请求,state code 为 200。

强缓存
  • Expires:时间(基本已经淘汰)
    缓存过期的时间
    Expires: Wed, 22 Oct 2018 08:41:00 GMT
    Expires是 HTTP/1 的产物,表示资源会在Wed, 22 Oct 2018 08:41:00 GMT后过期,需要再次请求。并且Expires受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
  • Catch-Control
    具体可以看请求、响应头
    Cache-control: max-age=30
    • private:仅浏览器可以缓存
    • public:浏览器和代理服务器都可以缓存
    • max-age=xxx,过期时间(单位秒)
    • s-maxage=30,覆盖 max-age,作用一样,只在代理服务器中生效
    • no-catch:资源被缓存,但是立即失效,下次会发起请求验证资源是否过期
    • no-store:不强缓存,也不协商缓存

强缓存步骤:

  1. 第一次请求文件时,缓存中没有信息,直接请求服务器
  2. 服务器返回请求的文件,并且 http response header 中 cache-control 为 max-age=xxx
  3. 再次请求该文件时,判断是否过期,没有过期直接读取本地缓存,否则进行协商缓存
协商缓存

由服务确定缓存资源是否可用
触发条件:

  • Catch-Control的值为no-catch
  • Catch-Controlmax-age过期

  • Last-Modified/If-Modify-Since:本地文件最后修改日期

    • 第一次请求,服务器返回 header 会加上Last-Modified,标识该资源的最后修改时间,例如Last-Modified: Thu,31 Dec 2037 23:59:59 GMT
    • 再次请求该资源,请求头会包含If-Modify-Since,该值就是上次返回的Last-Modified。服务器收到If-Modify-Since,根据资源的最后修改时间判断是否能使用缓存。
    • 如果可以使用缓存,返回 304,不会返回资源的内容,并且不会返回 Last-Modified
    • 如果有更新,返回 200,浏览器获取服务器最新资源,和Last-Modified

    Last-Modified的弊端

    • 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
    • 因为Last-Modified只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
  • ETag/If-None-Match
    返回一个校验码,任意资源改动了,会导致 ETag 变化
    服务器根据浏览器 request 的 If-None-Match 值来判断是否命中缓存。
    Last-Modified不一样的是,当服务器返回 304 Not Modified 响应,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。

问题:每次验证还是要向服务器发送请求验证,失去了缓存的意义。
所以,HTML 使用协商缓存,CSS&JS&图片使用强缓存

web 服务器配置(Node)

ETag 计算

强缓存

1
res.setHeader('Catch-Control','public,max-age=xxx')

协商缓存

1
2
3
res.setHeader('Catch-Control','public,max-age=0')
res.setHeader('Last-Modified',xxx)
res.setHeader('Etag',xxx)

3. 启用压缩

注意,图片和 PDF 文件不要使用 gzip,他们本身已经压缩过,再次压缩浪费资源,还会增加文件体积
HTTP/1.1,web 客户端支持压缩的 Accept-Encoding 请求头

1
2
3
4
5
Accept-Encoding: gzip

Accept-Encoding: gzip, compress, br

Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
  • gzip 文本压缩,视频压缩不好
  • compress
  • deflate
  • br
  • identity:指代自身,未经压缩
  • *
  • ;q=权重

如果 web 服务器看到这个请求头,它就会用客户端列出的一种方式来压缩响应。web 服务器通过 Content-Encoding 响应头来通知客户端。

1
Content-Encoding: gzip
  • 服务端开启 gzip 压缩
    node、express 做服务器,中间件compression开启 gzip 压缩
1
2
3
4
5
// server.js
var express = require('express');
var compress = require('compression');
var app = express();
app.use(compress());

4.避免图片 src 为空

三、Cookie

Cookie 会在 HTTP 头在浏览器和服务器之间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。

  • 去除不必要的 Cookie
  • 压缩 Cookie 大小
  • 设置 Cookie 的 domain 级别,如无必要不要影响到 sub-domain
  • 设置合适的过期时间

静态资源一般无需使用 Cookie,可以把它们放在二级域名或专门域名的无 Cookie 服务器上,降低 Cookie 传送造成的流量浪费,提高响应速度。

四、CSS

1.把样式表放在

2.不要使用 CSS 表达式

3.使用代替@import

4.不要使用 filter

AlphaImageLoader 为 IE5.5-IE8 专有的技术
不是 CSS3 filter

五、JavaScript

1.把脚本放在页面底部

浏览器下载脚本,会阻塞其他资源并行下载,即使来自不同域名的资源。
最好将脚本放在底部,以提高页面加载速度。
非核心代码异步加载

  • JS 异步加载的方式

    1. 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
    2. 给 js 脚本添加 defer 属性,让脚本加载与文档解析同步进行,然后解析完成再执行这个脚本,如果设置多个 defer 属性的脚本,按照规范是顺序执行,但是某些浏览可能不是。
    3. 给 js 脚本添加 async 属性,也是让脚本异步加载,不阻塞页面渲染,但是脚本完成加载后会立即执行,如果此时页面文档没有解析同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
    4. 动态脚本加载。对文档加载进行事件监听,文档加载完成后再动态创建 script 标签引入 js 脚本。

2.使用外部 JS 和 CSS

外部 JavaScript 和 CSS 文件可以被浏览器缓存,在不同页面间重用,也能降低页面大小。
当然,实际中也需要考虑代码的重用程度。如果仅仅是某个页面使用到的代码,可以考虑内嵌在页面中,减少 HTTP 请求数。另外,可以在首页加载完成以后,预先加载子页面的资源。

3.压缩 JS 和 CSS

压缩代码可以移除非功能性的字符(注释、空格、空行等),减少文件大小,提高载入速度。
可以使用 Gulp、Webpack 等流行构建工具

4.移除重复脚本

5.减少 DOM 操作

6.使用高效的事件处理

六、图片

1.优化图片

尝试把 GIF 格式转换成 PNG 格式,看看是否节省空间。在所有的 PNG 图片上运行 pngcrush(或者其它 PNG 优化工具)。

2.不要在 HTML 中缩放图片

不要用大图片,然后用宽高缩小图片。
设置图片的宽高。避免产生重绘。

3. icon 使用字体图标代替

4.小图片使用内联图片(base64)

七、移动端

1.保证所有组件都小于 25k

2.打包内容为分段文档

总结

性能优化考虑的方面:

  • 资源本身大小的压缩优化(想办法减少资源的体积)
  • 网络请求的全过程(从 url 地址栏输入发送请求开始到返回响应包的每个环节)
  • 浏览器渲染的全过程(拿到资源后浏览器渲染的每个环节)

更快速的网络通信

减少 HTTP 请求次数

  • 资源合并
  • 雪碧图
  • 缓存
  • base64

数据压缩

  • gzip
  • 代码文件压缩
    • 代码注释,空格,
  • 静态资源
    • 字体图标,去除元数据,图片格式 webp
  • 头与报文
    • 去掉不必要的头
    • 减少 cookie 数据

HTTP2
资源合并和域名分片不要做

  • 头部压缩

头部臃肿
专门的 HPACK 压缩算法 - 索引表 - 霍夫曼编码

  • 二进制帧
    文本字符分隔数据流- 解析慢容易出错
    • 帧长度
    • 帧类型
    • 帧标识
  • 链路复用

更高效的数据处理

  1. 语义化标签
  2. 多使用伪元素,减少 JS 对 DOM 的查找遍历
  3. 能用 HTML、CSS 实现的效果不是要 JS
  4. 逻辑与展示解耦,避免不必要的 JS 引擎启动
  5. 减少作用域查询和闭包,闭包==,是要块级作用域

框架 白屏,解决方案
服务端渲染 SSR

  • next
  • nuxt

SSG 静态站点生成方案
Gatsby
Gridsome

-------------本文结束感谢阅读-------------