JavaScript 的时间消耗

随着我们的网站越来越依赖 JavaScript, 我们有时会(无意)用一些不易追踪的方式来传输一些(耗时的)东西. 在这篇文章中, 我会介绍一些能让你的网站在移动设备上快速加载且可交互的方式.

摘要: 更少的代码 = 更少的解析/编译(时间) + 更少的传输(时间) + 更少的解压(时间) 网络

大多数开发者考虑 JavaScript 的时间消耗时, 都会首先考虑到 JavaScript 的下载和执行消耗. 脚本传输的字节越多, 花费的时间越长, 用户连接的就越慢.

network

即使在网络发达的国家, 这也是需要面对的一个问题, 因为用户有效的网络连接类型不一定就是 3G、4G 或者 Wifi. 你可以连接咖啡店的 Wifi, 也可能连接上一个 2G 网络的蜂窝热点.

因而, 开发者需要想办法减少 JavaScript 在网络上的传输时间. 我这提供一些参考的方式:

通过代码分割(Code Splitting), 只传输用户需要的代码. 减少代码体积(对于 ES5 可以使用 Uglify; 对于 ES2015, 可以使用 babel-minify 或 uglify-es) 压缩代码(可以使用 Brotli ~ q11, Zopfli 或 gzip). Brotli 在压缩比上优于 gzip. 这种方式帮助 CertSimple 网站把脚本体积减少了 17%, 并帮助 LinkedIn 节省了 4% 的脚本加载时间. 移除无用的代码. 可以通过 DevTools 查看代码覆盖率情况. 对于代码分离, 可以了解 tree-shaking、Closure Compiler等高级优化方式. 对于公共库则可以使用一些代码优化插件, 如针对 lodash 的代码优化插件 lodash-babel-plugin, 可用于像 Moment.js 一类库的优化插件 ContextReplacementPlugin. 此外, 使用 babel-preset-env & browserlist 可以避免编译现代浏览器已经支持的功能. 部分更高级的开发者可能会细心分析 Webpack bundles 来帮助确定不必要的依赖. 通过缓存来优化网络传输. 通过 max-age 和 Etag 等方式来缓存脚本, 减少字节的传输. Service Worker 缓存技术能使你的应用具备网络弹性, 并且能使用像 V8 code cache 一样的特性. 同时, 也可以了解下通过 文件哈希名 实现长久缓存. cache

解析/编译

脚本下载之后, JavaScript 最消耗时间的地方就是 JS 引擎对代码的解析/编译. 在 Chrome DevTools 的性能面板中, JS 的解析和编译是 Scripting time 中的黄色部分.

parse

从 Bottom-Up/Call Tree 可以看到更精确的解析/编译时间.

parse2

但是, 为什么会这样呢?

parse3

花费很长时间去解析/编译代码会严重延迟用户在网站上的可交互时间. 传输的脚本越多, 在网站可交互之前, 就会花费更多的时间去解析/编译代码.

parse4

和脚本相比, 浏览器也会花费很多时间来处理同等大小的图片(图片仍需要被解码). 但是在大多数移动设备上, JS 更有可能对页面的交互性产生负面影响.

parse5

当我们谈论脚本的解析和编译很慢时, 上下文是很重要的–我们说的是普通的手机设备. 普通用户的手机是配置低配的 CPU 和 GPU, 可能由于手机内存的限制, 也没有 L2/L3 级缓存设置.

在 JavaScript 性能 一文中, 我注意到在低配手机和高配手机上解析约 1M 被解压后的脚本文件所用的时间是不同的. 对于市面上解析最快的手机和普通手机之间, 大约有 2~5x 的时间差异.

phones

那么不同配置的手机访问 CNN.com 又会是怎么样的呢?

与普通手机(Moto G4) 需要花费约 13s 来解析/编译 CNN 网站的 JS 相比, 高配 iPhone 8 仅需要约 4s 时间.这可以显著地影响用户与该站点完全交互的速度.

phones2

这突出了测试普通手机设备(如 Moto G4)的重要性而不仅仅是你口袋里的手机设备. 然而, 上下文关系也很重要: 优化网站用户的硬件设备和网络环境.

phones3

深入分析真实用户访问你的网站所使用的移动设备类型, 这样才可能明白他们真实的 CPU/GPU 等硬件约束.

另一方面, 也需要反思我们是否真的传输了太多的脚本?

通过 HTTP Archive 分析约前 500K 网站在移动设备上传输的脚本大小, 可以发现 50% 的网站需要占据 14s, 用户才可以与网站交互, 但是这些网站仅用 4s 时间来解析和编译 JS.

phones4

在获取和处理 JS 以及其他资源所需的时间中, 用户需要在页面可交互之前等待一段时间, 这一点也不奇怪, 但我们可以在这里做得更好.

移除页面上的非关键脚本不仅能减少传输时间, 也能减少 CPU 的解析/编译时间和潜在的内存开销, 这可提高页面可交互的速度.

执行时间

不仅脚本的解析和编译需要时间, 脚本的执行也需要时间. 长时间的执行时间也会延迟用户与站点的交互速度.

execute

如果脚本的执行时间超过 50ms, 那么可交互时间的延迟将是脚本下载、编译和执行脚本所花费时间的总和. – Alex Russell 为减少脚本的执行时间, 可以将脚本分成小块来执行, 以避免锁住主线程. 可以考虑是否能减少脚本在执行过程中需要完成的工作量, 如果工作量很多, 就将脚本分成小块来分解工作量, 以提高页面可交互的速度.

降低 JavaScript 交付成本的模式

当你尝试着降低 JavaScript 的解析/编译和网络传输时间时, 也可以试试基于路由的代码分割或 PRPL 模式来降低 JavaScript 的交付成本.

PRPL 是一种通过代码分割和缓存来优化页面交互的模式:

PRPL

通过 V8’s Runtime Call Stats, 我们可以分析一些受欢迎移动站以及 PWA 应用的加载时间. 从下图可以看出, 脚本解析所需要的时间(橙色部分)是页面加载中最耗时的一部分:

Call Stats

其它消耗

除上述方式外, JavaScript 还能通过如下方式影响页面性能:

内存. 由于 GC(garbage collection), 页面可能会频繁的出现闪现或者卡顿. 当浏览器回收内存时, JS 的执行会被暂停, 所以 JS 被暂停执行的频率和浏览器回收内存的频率是正相关的, 因此需要避免内存泄漏和频繁的内存回收导致的 JS 执行暂停, 保持页面的流畅度. 在运行期间, 长时间的脚本执行会阻塞主线程而导致页面没有响应. 将脚本的工作量分成多个小块来执行(使用 requestAnimationFrame() 或 requestIdleCallback() 进行任务调度)可以最小化响应性问题. Progressive Bootstrapping(逐步引导)

因为优化交互性的成本比较高, 许多网站会考虑去优化内容的可见性. 当 JavaScript Bundles 很大时, 为了减少白屏时间(First paint time), 一些开发者会采用服务端渲染的方式, 当 JS 处理完成之后再将其 “升级” 为事件处理.

但这种方式也是有时间消耗的: 1) 通常会发送一个很大的 HTML 文件作为响应, 2) 在 JavaScript 完成处理之前, 页面可能只有一部分是可交互的.

因而逐步引导可能是一个更好的方式. 浏览请请求一个最小化的功能页面(仅由当前路由需要的 HTML/JS/CSS 组成), 当有更多资源请求时, 应用可以进行资源懒加载, 然后逐步解锁更多功能.

pwa

Loading code proportionate to what’s in view is the holy grail. PRPL and Progressive Bootstrapping are patterns that can help accomplish this.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏京东技术

老板们不知道的秘密:开启自动化测试,让我们一起以逸待劳

我认为所有的UI自动化测试都分成基本的三个步骤:定位元素,操作元素和执行断言。大家在做UI自动化不同的主要是方案的选型,封装优化的方式不同。目前移动App的更新...

2653
来自专栏地方网络工作室的专栏

打造前端MAC工作站(三)使用brew利用命令行安装软件

打造前端MAC工作站(三)使用brew利用命令行安装软件 前情回顾 打造前端MAC工作站(一)简单系统配置 打造前端MAC工作站(二)安装软件的两种方法 前...

20810
来自专栏企鹅号快讯

如何将数据库检索的结果导出?

最近很多同学询问不同的数据库的文献如何导出……老师表示很是不解,这是个很简单的小问题,上课时候也讲过,演示过,可是却是提问频率最高的问题之一。于是,今天就来大家...

3915
来自专栏Java架构师学习

你想了解的分布式--从ACID到CAP/BASE

本文先介绍传统关系数据库中事务的ACID特性,再介绍分布式系统中的经典理论——CAP定理和BASE理论。 事务 事务的定义: 事务(Transaction)是由...

2958
来自专栏程序人生

谈谈用户权限系统

登录这事之于一个需要识别用户身份的产品,就仿佛cs101之于computer science。感谢各种语言里各种优秀的登录模块(比如nodejs的passpor...

3004
来自专栏最高权限比特流

漫谈计算机组成原理(二)系统总线

从引言中,我们基本上能够了解到总线在计算机系统中的作用。但是上面的说法只是一个概论,我们需要给出更加详细的总线的作用:总线是计算机中各个部件的信息传输线。在计算...

3403
来自专栏java 成神之路

iostat 命令详解

3819
来自专栏深度学习那些事儿

深度学习-在ubuntu16.04安装CUDA9.1-总结(问题完全解决方案)

深度学习大火,为了赶上AI的班车,许多研究生本科生们都在搞深度学习。然而深度学习环境搭建必不可少,这篇文章是我多次为实验室搭建环境所积累起来的经验总结,希望所有...

5885
来自专栏魏艾斯博客www.vpsss.net

WordPress 安装插件失败怎么解决?

在 wordpress 后台安装插件的时候,经常会遇到各种原因导致上传失败,比如下图这样的提示:安装失败。遇到这种情况很多新手不知道应该怎么解决了,下面魏艾斯博...

6492
来自专栏北京马哥教育

Web APP编程模型和IO策略

现代大型高性能网站诸如淘宝,京东,微博,FB,知乎等等,网站架构涉及很多知识。像业务分层,软件分割模块化,分布式部署,集群服务器,负载均衡等技术可以帮助架构师将...

3617

扫码关注云+社区

领取腾讯云代金券