JavaScript 的时间消耗

英文:Addy Osmani   译文:dwqs

https://github.com/dwqs/blog/issues/59


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

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

网络

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

即使在网络发达的国家, 这也是需要面对的一个问题, 因为用户有效的网络连接类型不一定就是 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 一样的特性. 同时, 也可以了解下通过 文件哈希名 实现长久缓存.

解析/编译

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

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

但是, 为什么会这样呢?

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

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

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

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

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

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

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

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

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

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

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

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

执行时间

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

如果脚本的执行时间超过 50ms, 那么可交互时间的延迟将是脚本下载、编译和执行脚本所花费时间的总和. — Alex Russell

为减少脚本的执行时间, 可以将脚本分成小块来执行, 以避免锁住主线程. 可以考虑是否能减少脚本在执行过程中需要完成的工作量, 如果工作量很多, 就将脚本分成小块来分解工作量, 以提高页面可交互的速度.

降低 JavaScript 交付成本的模式

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

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

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

其它消耗

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

  • 内存. 由于 GC(garbage collection), 页面可能会频繁的出现闪现或者卡顿. 当浏览器回收内存时, JS 的执行会被暂停, 所以 JS 被暂停执行的频率和浏览器回收内存的频率是正相关的, 因此需要避免内存泄漏和频繁的内存回收导致的 JS 执行暂停, 保持页面的流畅度.

  • 在运行期间, 长时间的脚本执行会阻塞主线程而导致页面没有响应. 将脚本的工作量分成多个小块来执行(使用 requestAnimationFrame() 或 requestIdleCallback() 进行任务调度)可以最小化响应性问题.


Progressive Bootstrapping(逐步引导)

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

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

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


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

参考

  • The Cost Of JavaScript

  • JavaScript Start-up Performance

  • Solving the web performance crisis

  • Can you afford it? Real-world performance budgets

  • Performance Futures



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明
### 回答1: JavaScript 函数节流是一种优化技术,旨在通过限制函数的执行频率来提高性能。通常,当用户在界面上进行操作时,会触发大量的函数调用,比如拖动滑块、滚动屏幕等。如果每次调用都执行函数的全部代码,可能会造成性能问题。因此,我们可以使用函数节流来限制函数的执行频率,从而提高性能。 下面是一个简单的函数节流实现: ``` function throttle(func, wait) { let timeout; return function() { const context = this; const args = arguments; if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args); }, wait); } } } ``` 使用方法: ``` const throttledFunction = throttle(function() { // 这里是函数的代码 }, 1000); ``` 在上面的代码中,我们定义了一个名为 throttle 的函数,它接收两个参数:要节流的函数和等待时间。throttle 函数返回一个新的函数,这个新函数在被调用时会延迟执行原函数,直到等待时间过去后才真正执行。 使用函数节流可以有效减少函数调用的次数,从而提高性能。但是要注意,函数节流会导致函数的响应时间变长,所以在使用时要根据具 ### 回答2: JavaScript 函数节流是一种控制函数执行频率的方法。通过节流,我们可以限制函数的调用频率,从而减少函数的执行次数,提高性能。 函数节流的原理是创建一个计时器,在指定的时间间隔内只允许函数执行一次,如果在时间间隔内多次触发函数执行,只有一次会被执行,其余的会被忽略。 下面是一个简单的实现函数节流的例子: ```javascript function throttle(func, delay) { let timer = null; return function () { if (!timer) { timer = setTimeout(() => { func.apply(this, arguments); timer = null; }, delay); } }; } ``` 在上述代码中,`throttle` 函数接收两个参数:`func` 表示需要节流的函数,`delay` 表示时间间隔,单位是毫秒。 `throttle` 函数内部定义了一个`timer`变量,用于保存计时器的标识。在函数被调用时,先判断`timer`是否为`null`,如果为`null`,则创建一个计时器,并在指定的时间间隔后触发函数执行。执行函数时,将使用 `apply` 方法将传入的参数原封不动地传递给函数。 如果在时间间隔内多次触发函数执行,即使函数被调用,也不会创建新的计时器,保证函数的执行频率不超过设定的时间间隔。 通过使用函数节流,我们可以有效地控制函数的执行频率,提高页面的性能和用户体验。比如在监听滚动事件、鼠标移动事件等场景下,可以使用函数节流避免频繁调用函数,减少不必要的计算和资源消耗。 ### 回答3: JavaScript 函数节流是一种控制函数执行频率的技术,用于限制函数在一定时间内被触发的次数。 节流的实现原理是通过设置一个时间间隔,在这个时间间隔内只能触发一次函数执行,如果在时间间隔内再次触发函数执行,则需要等待时间间隔结束后才能再次执行。这样可以有效地控制函数的执行频率,避免函数被频繁调用造成性能损耗。 在实际应用中,可以使用定时器来实现函数节流。具体的步骤如下: 1. 定义一个变量来存储函数最后一次执行的时间戳。 2. 在函数执行之初,获取当前的时间戳,并与上一次执行的时间戳进行比较。 3. 如果时间间隔大于设定的阈值,则执行函数,并更新最后一次执行的时间戳。 4. 如果时间间隔小于设定的阈值,则不执行函数,等待下次触发函数执行。 5. 使用定时器循环检测是否满足执行条件。 通过函数节流可以有效地降低函数的执行频率,减少不必要的计算和资源消耗。在监听滚动事件、窗口大小改变等频繁触发函数的场景下,使用函数节流可以提升页面性能和用户体验。 需要注意的是,在设置时间间隔时,要根据实际需求和函数的执行时间来合理设定,以避免函数不能及时响应用户操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值