资源大小
- 压缩
- Gzip
- br(Brotli)
- JS Uglify,minify
- 使用 Tree Shaking 排除没有使用的导入模块
- webpack-bundle-analyzer
- webpack-libs-optimization
- 图像优化
- WebP
- 字体,视频优化
- JS 分包
- 大于 30KB 的 async/defer 文件可以触发 V8 Script Streaming
- 有利于 HTTP2 的多路复用
加载时序
- PRPL 模式
- Push (or preload) the most important resources(首屏加载前)
- Render the initial route as soon as possible(首屏加载)
- Pre-cache remaining assets(为下一个场景做准备)
- Lazy load other routes and non-critical assets(不是关键的资源延迟加载)
- 活用 async script,尽量异步加载非首次渲染需要的资源
- 活用 defer script,提前 first paint 的时间点
- 缓存策略
- 优化入口文件大小
- TCP 的 first roundtrip 只能发 10 个 TCP packets(大概是14KB)
- 使用
<link rel="preload" />
,<link rel="dns-prefetch" >
- preload-webpack-plugin
- 首屏渲染需要外部 JS,CSS,应该尽可能的放到 HTML 文档上方(Header)以便尽早发出请求
- CSS 会阻塞 JS,所以 CSS 应该放在更前面
- 这里 介绍了一些到 2018 年为止 CSS 应该怎么放置的策略
- 避免
@import css
,因为这些都不能并行下载 - 首屏不需要的 CSS 可以放到 BODY 里
- 避免
渲染逻辑
- 避免不必要的重排,重绘
- 慎重使用会产生重排,重绘的方法,具体参见 CSS Triggers
- 避免强制同步布局(修改后马上查询), 浏览器本不需要在每次查询的时候就马上就去重排的
- 避免布局抖动(循环内反复获取和修改)
- 可以使用 FastDOM 批处理 DOM 的读取和写入
- 可以单独创建新的合成器层(但是不要创建太多了, 耗内存)
will-change
可以做到, 并提前警示浏览器即将出现更改transform: translateZ(0)
在旧浏览器里也可以做到
- Flexbox 要好于浮动布局
- 避免复杂的 CSS 选择器
:nth-last-child
这种要慎用
JS 逻辑
- 对于大型任务
- 特别大的,没有 DOM 操作的,可以考虑 Web Worker
- 活用几个异步的回调函数将大型任务分割
- requestAnimationFrame 保证 JavaScript 在帧开始时(Safari 为帧结束)运行,这个对于实现动画效果很有帮助(cocos 的每一帧都是在 RAF 回调内的)
- requestIdleCallback 在浏览器空闲的时候执行(貌似 safari 还不支持)
- 慎用微优化(忽略 JS 方法间的性能差距,因为大部分时候他们微乎其微)
- 活用防抖动和节流阀
-
防抖:
Debounce
触发事件后 n 秒内函数只会执行一次, 如果 n 秒内事件再次被触发则重新计时- 用于搜索联想词,用户的频繁点赞操作,resize(只执行最后一次就可以了)
- debounce.js · GitHub
const debounce = function(fn, idle) { let last; return function() { // 每次触发事件时都取消之前的延时调用方法 clearTimeout(last); last = setTimeout(() => { fn.apply(this, arguments); }, idle) }; }
- 节流:
throttle
在 n 秒内只会执行一次,若果有多次则忽略后面的- 类似 RAF
- 可以用于 loadmore 的实现
const throttle = function(fn, delay) { let timer = null; return function() { // 每次触发事件时都判断当前是否有等待执行的延时函数, 如果有则不执行 if(!timer) { timer = setTimeout(() => { fn.apply(this, arguments); timer = null; }, delay); } }; }
-
工程角度
- 为不同的环境配置不同的策略
navigator.connection.effectiveType
可以更准确的检测当前网络环境,Chrome 62 开始支持
汇总建议
浏览器加载过程
缓存策略
检查 Cache-Control 的时间或者是 Expires 的时间戳,如果满足,则使用本地的缓存 根据上一次服务端发送的 ETag 发送 If-None-Match 给服务端或者是根据上一次的 Last-modified 发送 If-Modified-since 给服务端,服务端再根据这个判断是否要返回 304 给客户端,如果客户端收到 304,则使用上一次的缓存信息 同时也可以用 Service Worker 进行离线化操作
传输
如果是 http 检查 HSTS Preload 列表, 决定是否需要从 http 转换到 https 把 URL 通过 DNS 解析到服务器的真正IP 可以在 script 标签里设置 dns-prefetch 来缓存 DNS 的解析结果,这样下次真的要请求的时候就不需要再进行 DNS 解析了 通过 TCP 三次握手建立连接 建立连接成功后要考虑 TCP 为了防止网络拥塞采取了慢启动策略,所以若果可以的话, 第一个 HTML 文件建议小于 14KB(10 个 TCP packets)
渲染
解析 HTML 文档,构建 DOM 树,然后下载 CSS 资源,构建 CSSOM 将 DOM 和 CSSOM 合并后进行排版,绘制(layout,paint,紫色和绿色) 这里需要关注的是 HTML 内 script 的 async 和 defer 标签,async 代表可以一步加载,但是加载完后会立即开始执行,而 defer 会等到 HTML 解析后再执行
2019 前端性能优化年度总结 by Vitaly Friedman Web performance made easy (Google I/O ‘18)