我所知道的 Web 性能优化策略

未完,原文地址: github.com/rccoder/blog/issues/34

前言

本文核心分为两部分,第一部分讲述普通浏览器中能干的事情,第二部分则讲述在自建容器的背景下更能干的事情。内容会比较粗略,如果你对细节比较感兴趣,欢迎留言。

一、止步于浏览器

1.1 DNS Prefetch

通常情况下,一个 html bundle 里面一般会有 script 等标签去加载其他的资源。浏览器在加载完 html 之后,就会去加载 script 等标签里面的内容,大多情况下,这种标签里 uri 的 host 和当前页面的往往是不相同的,那就会涉及到 DNS 解析的问题,会有一定程度的损耗。

在 HTML 里面加入 DNS Prefetch 则会让浏览器提前进行 DNS 的解析并且缓存到系统中,这样就可以提升网页的加载效率了。

<link rel="dns-prefetch" href="//img.alicdn.com">

1.2 域名收敛

在像 v2ex 这样的社区论坛中,往往会有很多用户外链图片,不同的图片有不同的域名。这个时候 DNS Prefetch 会显得很无力,如果说在图片上传之后做一定的 转化 或者 同步,把域名收敛到同一个域名中,这样就能弥补相关的缺憾了。

aaa.com/jjj.png ->  mycdn.com/aaa/jjj.png
bbb.com/jjj.png ->  mycdn.com/bbb/jjj.png

1.3 加载合适的图片

同一个图片在 在不同的设备、不同的网络条件、不同的渲染面积下,统一加载一样的尺寸难免是 “奢侈” 的,利用 CDN 裁剪 + 前端嗅探 去加载不同体积、不同压缩比率、不同格式的图片,能在节省不少流量的同时提升很大的性能。

目前主流 CDN / OSS 都是支持通过 URL 后缀进行图片裁剪的,比如: 阿里云

http://image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg?x-oss-process=image/crop,x_100,y_50

在前端嗅探上,一般我们可以嗅探 是否支持 webp、客户端分辨率、当前网络状况,去全局设置不同的图片加载 URL。

base64、

1.4 不要展示 “绝对Loading / 占位”

当前时代,大部分网页的数据都是动态下发的,甚至千人前面,为了减少用户等待的焦灼感,往往会设置一个 Loading 动画或者 骨骼图占位。但当请求响应足够快的时候,会发现这种 Loading 或 占位 却会给人相反的感觉 —— 瞬间的闪动。

针对这种情况,在使用 Loading 或骨骼图占位的,可以做一定的优化,比如请求发起后 200ms 以上还未返回数据才展示占位图。在 React Suspense 中,为了这种效果官方甚至加了一个 API。

1.5 资源 combo

在 HTTP 请求中,请求创建往往因为 TCP 三次握手等会有一个非常大的开销。涉想在 HTTP 1.0 时代,如果页面上有 50 个 Script 标签,会有 50 次的请求创建,在资源不是那么大的情况下,请求创建的耗时往往会远大于资源真正的下载时间。

在服务端做资源 Combo,然后再往 CDN 回源,就能很大程序上减少这种开销。

xx.com/a.js
xx.com/b.js
xx.com/c.js
xx.com/d.js

xx.com/combo?a.js,b.js,c.js,c.js

在 HTTP 2.0 时代资源要不要 Combo + 多少个资源(多大的体积)Combo 到一起 是个有意思的话题,这里不做讨论。

1.6 在线 Shim

前端因为浏览器问题一直存在店铺这种东西,有很多的特性往往在新的浏览器版本里面已经支持,但奈何老的不支持需要做垫片。如果对代码统一加垫片则又会让新浏览器很尴尬(我要这新特性有何用?),使用在线的 Shim 应该是一个不错的 Shim。

比如著名的 https://polyfill.io/v3/url-builder/, 就是一个这样的服务。在支持 Object.assign 中访问 https://polyfill.io/v3/polyfill.min.js?features=Object.assign , 会得到:

/* Disable minification (remove `.min` from URL path) for more info */

而在不支持的浏览器中访问,会得到:

(function(undefined) {if (!(// In IE8, defineProperty could only act on DOM elements, so full support
// for the feature requires the ability to set a property on an arbitrary object
'defineProperty' in Object && (function() {
	try {
		var a = {};
		Object.defineProperty(a, 'test', {value:42});
		return true;
	} catch(e) {
		return false
	}
}()))) {!function(e){
...

在国内也有这样的服务,比如: https://polyfill.alicdn.com/polyfill.min.js?features=Object.assign ,如果你觉得不够信赖,可以自建然后部署在 CDN 上。

内联 CSS

GZIP & BBR

GZIP 压缩使用 Deflate 能有效压缩文本资源的大小,在现代浏览器中,对 GZIP 的支持也是非常良好。值的注意的是,GZIP 的压缩并不是压的越小越好,太小会产生压缩性能的问题。

传统 TCP 使用的是基于丢包的拥塞控制算法,但并不是所有的丢包都是网络堵塞所导致的,为此 Google 开发了 BBR 拥塞控制算法,能有效提升服务器的吞吐量,如果服务器支持的话,可以开启 BBR 来加快网络传输。

性能测量

window.performace 能展示绝大多数检测 Web 性能的指标,在业务代码中埋点收集 window.performance 的值,可以为网页性能短板做很好的测量与统计。

Service Worker

Service Worker 本质上充当Web应用程序与浏览器之间的代理服务器,利用 Service Worker 可以极致的控制每个请求,进而可以对 Web APP 在浏览器上做离线处理。

传说中的 PWA 就是对这个东西的一个极致应用。

分离静态资源

在绝大多数情况下,访问静态资源的时候并不需要知道 Cookie 信息,为静态资源使用一个新的域名能有效避免用不着的 Cookie 上传,能减少一部分无用流量的传输。

使用 requestAnimationFrame 实现 60 FPS 动画

在绘制动画的时候,优先使用 requestAnimationFrame,会充分利用分利用显示器的刷新机制,实现 60 FPS 的感觉。

节流 & 防抖

在 Web 中,像 Scroll 这种事件,在界面操作中触发的频率是非常之高的。涉想这样的一种场景:用户往下滑动网页,当滑动距离超过 1000px 的时候,右下角展现一个 回到顶部 的按钮。想当然的操作就是监听 Scroll 事件,当值大于 1000px 的时候展示 按钮,但因为 Scroll 的高频率触发,尤其在移动端这样做就能感觉到比较明显的性能问题了,如果我们对其加个操作 —— 1s 内检测函数只触发 1 次 或者在用户停下来的时候再去检测位置。这样页面整体就会流畅很多了,相对应的两个操作就是 节流 和 防抖。

在 Web 开发过程中,对于这种高频次触发的事情,合理的进程节流和防抖能在很大程度上增加页面流产度。

WebWorker

WebWorker 为 JavaScript 在浏览器中多线程调用提供了能力,可以让主线程创建 Worker 线程,针对一些密集计算或者需要时间比较高的场景,是非常有效的。比如:网页版邮箱附件上传等。

使用新版本 JavaScript

一般情况下,V8 等 JavaScript 的 Runtime 都会对新特性有优化,在新浏览器上使用 Babel 转化后的代码难免会有一定的浪费与可惜。在新浏览器上使用新预发,老浏览器上使用老语法,才是比较好姿势。

实现上一种思路就是在线 Shim;第二种思路是正对先加载 seed 再加载 bundle 代码,可以在加载 bundle 之前做一个知否支持新版本 ES 的判断(比如是否支持 async 函数),然后再加载相应的 Bundle。

善用 LocalStorage

在一些场景下,每次用户进入时数据的变化不会太大,比如不怎么更新的个人博客页面。这个时候就可以使用 LocalStorage 去做 HTML 的缓存,页面进入的时候直接从 Storage 中获取缓存,然后 append 到页面上,等接口数据回来之后,再 Diff 做更新。

在新版本浏览器中,可以用 indexedDB 等代替 LocalStorage。

组件级缓存

在 SPA 网站中,加载 bundle 大致上可以分为两份:

  1. 所有的组件代码和业务代码打到一起,和业务代码一起输出
  2. 组件代码在组件内部各自打包,业务代码打包的时候 external 掉组件代码,最后 combo 到一起输出。

针对第二种情况,可以利用 LocalStorage 等单独缓存组件代码(带上版本号),在端侧实现一个 Combo 的机制(有 Cache 取 Cache,没 Cache Fetch),这样一来,就能让一个网站的多个页面享受同份缓存,让之第一次也能非常快速的访问。

二、外探于自建容器

WKWebview

资源 Cache

DNS 这层

数据预加载

Webviw 内核内置

阉割版 WebView

Weex/React Native

Flutter

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章