当我们建立的网站越来越多依赖 Javascript 时, 我们有时候会为了一些我们不容易看到的地方付出代价在这边文章中, 我会讲述一些规范, 如果你想让你的网站在移动设备上更快加载和响应的话, 这些规范可能会对你有帮助
tl;dr: less code = less parse/compile + less transfer + less to decompress
tl;dr 是 too long; don't read 的缩写
网络
当大多数开发者想到 Javascript 的开销时, 可能会联想到下载和执行开销传送的数据量越大, 那用户连接的速度就越缓慢
即使在一些大国家, 如果用户的有效网络不是 3G, 4G 或者 WiFi 的话(用户可能在咖啡厅用着 wifi, 但是实际网速是 2G), 那么这就会成为一个问题
你可以通过以下几种方式来减少网络传输的开销:
只传输用户需要的. 代码分离 (Code-spliting) 能起到一定作用.
混淆(丑化) (ES5 有 Uglify, ES2015 有 babel-minify 或者 uglify-es)
强力压缩 (使用 Brotli ~q11, Zopfli 或者 gzip). 相较于 gzip,Brotli 在压缩比上更胜一筹. 它使 CertSimple 在 JS 的体积上节省了 17%, 使 LinkedIn 在加载时间上节省了 4%.
删除无用代码. 与 DevTools code coverage 类似. 对于剥离的代码, 见 tree-shaking, Closure Compiler 的高级优化和其它类似的插件, 像 lodash-babel-plugin 或者 webpack 的 ContextReplacementPlugin. 使用 babel-preset-env 和 browserlist 以避免转换已经存在于现代浏览器的一些特性. 资深开发者可能已经发现 analysis of their Webpack bundles 可以帮助去除那些不必要的依赖.
缓存 优化脚本有效时间以及 ETag 来避免传输没有变化的数据. Service Worker 缓存能帮助实现弹性网络, 并且使你更早使用一些特性, 像 V8s code cache. 同时也可以通过 filename hashing 学习持久化缓存.
解析 / 编译
一旦脚本下载完成了之后, JS 最大的开销之一就是 JS 引擎的解析 / 编译代码在 Chrome 开发者工具里的性能模块, 解析和编译代码用黄色标识.
通过 Bottom-Up/Call Tree(调用树), 可以看实际的解析 / 编译用时:
但是, 为什么这个很重要呢?
花费大量时间在解析 / 编译代码上会延迟用户与网站的交互, 破坏用户体验在网站呈现之前, JS 量越大, 花在解析 / 编译上的时间就越长
对于大小相同的 JS 和图片或者网页字体, JS 需要浏览器花费的时间最多 Tom Dale
与 Javascript 相比, 处理相同大小的图片所需要的开销明显小很多
当我们讨论解析和编译的速度之慢时, 上下文是很重要的我们的讨论是基于平均水平的移动设备平均水平的用户使用的移动设备可能是 CPU/GPU 很慢的没有 L2/L3 缓存的或者甚至内存很有限的
网络和设备不总是匹配的一个用户可能有很好的网络条件, 但是只有一部烂手机相反, 一个用户可能有一部神机, 却碰上了龟速网络 Kristofer Baxter, LinkedIn
在 JavaScript Start-up Performance 文中, 我提到了分别在低端和高端机型中 1MB 原始 JS 代码的解析时间. 它们之间的差距达到了 2-5 倍.
那实际的网站如何呢, 比如 CNN.com?
在高端机 iPhone 8 上, 解析 / 编译 JS 代码只需大约 4s, 而在平均水平的手机 Moto G4 上却要花上将近 13s 这很明显得影响了用户能够多快看到界面
这就要求我们要更加注重一些平均水平的设备的测试, 而不仅仅是自己口袋里的高端机上下文是很重要的: 一定要针对你的用户的设备和网络进行优化
可以通过 mobile device classes 来看看真实用户的分析情况
我们真的是传输了太多的 JS 代码吗? 呃有可能 :)
使用 HTTP Archive (top ~500K sites)来分析 JS 在移动设备上的使用情况 JavaScript on mobile, 我们就能发现 50% 左右的网站需要 14s 以上才能真正让用户用上这些网站光花在解析 / 编译 JS 上的时间就多达 4s.
介于以上这些情况, 怪不得用户在还没有看到页面之前就离开了我们当然可以在这点上做得更好
删除一些非必要 JS 代码能有效减少转换时间 CPU 的解析 / 编译时间以及内存占用同样也能是用户更快得与网站交互
执行时间
当然, 编译和解析只是 JS 开销的一部分执行 JS 代码也是主线程上必须要做的, 如果执行时间冗长, 也会直接影响用户体验
一旦脚本执行时间超过 50ms, 后果不堪设想 Alex Russell
为了减少执行时间, 你可以将 JS 代码分离成一块块的, 以免阻塞主线程
设计模式
有时候一些设计模式能够帮助你, 比如基于路由的代码分块 (route-based chunking) 或者 PRPL.
如下图所示, PRPL 就是一个利用代码分离和缓存方式来优化交互体验的模式:
让我们来看看这个影响.
我们使用 V8 的 Runtime Call Stats 分析了一些主流网站以及 PWA 的加载时间可以看到, 解析时间在整个加载时间中占了可观的部分:
Wego, 是使用了 PRPL 的一个站点, 让每个路由都保持很少的解析时间, 使得用户能更快得与网站交互上图中的很多网站也是采用了代码分离和性能预算 (performance budget) 来尝试降低 JS 开销
其它开销
JS 也会在其它方面影响页面性能:
内存页面可能会因为垃圾回收而导致频繁的闪烁或暂停当浏览器回收内存的时候, JS 执行就会暂停这就导致了当浏览器频繁回收垃圾时, JS 执行的暂停频率就会比我们想象中的更多避免内存泄漏和频繁的垃圾回收能够是页面更稳定
在运行时, 如果 JS 运行时间过长就会阻塞主进程, 导致页面无法交互把这些任务分成一小块一小块 (可以采用 requestAnimationFrame() 或者 requestIdleCallback() 或者 scheduling) 能够最小化其带来的影响
渐进式 Bootstrapping
为了让网站更快呈现在用户面前, 许多网站会使用服务器端渲染来实现, 然后在页面返回后通过绑定事件来升级它
当心 -- 这种方式也有它的开销一方面传输回来的 html 比较大, 另一方面用户必须等到 JS 处理完毕才能真正与页面进行交互
渐进式 Bootstrapping 可能是一种更好的方法先发送一部分功能性页面 (只是当前路由需要的 HTML/JS/CSS) 回来当更多的资源传输回来时, 页面就会进行懒加载并且解锁更多的功能
加载当前页面的代码实在是非常好的方法 PRPL 和渐进式 Bootstrapping 就是能帮助实现这点的模式
来源: https://juejin.im/post/5a7d0d586fb9a0633f0e0eab