最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

JavaScript 的成本

  • 2018-01-02
  • 本文字数:3946 字

    阅读完需:约 13 分钟

在建立那些严重依赖于 JavaScript 网站的时候,有时我们会为自己发送的内容付出一些隐形的成本。在本篇文章中,我会介绍一些可以帮助你提升网站在移动设备上加载和运行速度的实用规则

tl;dr:更少的代码 = 更少的解析 / 编译 (parse/compile)+ 更少的传送 + 更少的解压缩

网络

大多数开发人员考虑 JavaScript 成本的时候,考虑的都是下载和执行成本。通过线路发送的 JavaScript 字节越多,所需时间就越长,用户连接就越慢。

即使是在发达国家,这也可能是一个问题,因为用户实际上用的有效网络连接类型可能并不是 3G、4G 或者 Wifi。表面上你可能连的是咖啡店的 Wifi,但实际上连到的是只有 2G 速度的蜂窝热点。

你可以通过以下的几种方式来降低 JavaScript 的网络传输成本:


(减少向用户发送 JavaScript 量的最佳做法。)

解析 / 编译

下载成功后,JavaScript** 绝大部分的时间都消耗在 JS 引擎对下载代码的解析 / 编译 ** 上。在 Chrome DevTools 中,解析和编译是下面性能面板(Performance panel)中黄色“脚本”时间的一部分。

Bottom-Up/Call Tree 允许我们去确切地查看解析 / 编译所用时间:


Chrome DevTools 性能面板下级菜单 >Bottom-U。启动 V8 的 Runtime Call Stats,就能看到不同阶段的时间消耗,比如解析 / 编译所用时间。

但是,这为什么会是个问题?

耗费很长的时间在解析 / 编译代码上,会严重延迟用户与你网站的交互时间。你发送的 JavaScript 越多,在网站实现交互前所用的解析 / 编译的时间就会越长

即使是同样多的字节,浏览器处理 JavaScript 也会比处理等大小的图片和网页字体消耗更高的成本——Tom Dale

相比于 JavaScript,处理等字节的图片所需要的时间成本很高(因为图片仍需要解码!)但是在一般的移动设备上,反而是 JS 更有可能对页面的交互产生负面的影响。


(JavaScript 字节和图像字节耗费的时间成本不同。图像通常不会阻塞主线程,也不会在解码和光栅化的时候阻止接口进行交互。然而 JS 会因为解析、编译和执行的时间消耗阻滞交互性。)

当我们说解析和编译的速度变慢的时候,要注意具体的网络端和设备端的情况,在这里我们针对的是普通手机。普通用户所使用手机的 CPU 和 GPU 速度比较慢,没有 L2/L3 缓存,甚至可能会有内存限制。

网络功能和设备功能并不总是相匹配的。有速度惊人的光纤连接的用户不一定会有最好的 CPU 来解析和评估发送到他们的设备的 JavaScript。反过来也是如此…你可能有糟糕的网络连接,但却有快速的 CPU。 - Kristofer Baxter,LinkedIn

JavaScript Start-up Performance 一文中,我曾提到过在低端和高端硬件上解析~1MB 解压缩过(简单)的 JavaScript 所需要消耗的时间。市面上的普通手机和运行速度最快的手机相比,解析 / 编译代码的所用的时间会有 2-5 倍的差距。


(在不同级别的台式和移动设备上解析 1MB 的 JavaScript 包(经 gzip 压缩,大小约为 250KB )。当分析解析成本时,我们需要考虑的是解压后的数据量,例如〜250KBgzip 压缩过的 JS 解压缩后约为〜1MB 的代码。)

那解析 / 编译真实网站的时间差异又会是怎样呢,比如 CNN.com 网站?

在高端的 iPhone 8 上解析 / 编译 CNN 网站的 JS 大约花费了 4 秒,相比于普通手机(Moto G4)的 13 秒左右。这可以显著地影响用户与 CNN 网站实现完全交互的速度。


(苹果公司的 A11 仿生芯片和更普通的 Android 硬件中的 Snapdragon 617 的解析时间上的性能比较

这就突出了在普通硬件(比如 Moto G4)上测试的重要性,而不仅仅是在自己恰好有的手机上测试。基于 _ 自己 _ 客户原有的设备和网络条件来进行优化是很重要的。

分析可以使你更加深入了解自己真实客户访问网站所用的移动设备的级别和这些设备 CPU/GPU 的局限性。

我们真的发送了太多的 JavaScript 了吗?呃…真有可能:)

用 HTTP Archive(qian500K 站点)来分析移动设备上 JavaScript 的状态时,我们可以看到,50%的站点需要 14 秒才能取得交互。这些网站光是用来解析和编译 JS 的时间就长达 4 秒。

考虑到获取和处理 JS 和其他资源所耗费的时间,也就不奇怪用户可能需要在页面可用之前等待一段时间了。我们绝对可以在这个方面做的更好。

从网页中删除不必要的 JavaScript 可以减少传输时间、CPU 密集型解析和编译以及潜在的内存消耗,同时也有助于加快网页的交互速度。

执行时间

不光是解析和编译会有时间成本。执行 JavaScript(在解析 / 编译之后运行代码)也是需要在主线程上进行的操作之一。长的执行时间也会延迟用户与你网站的交互时间。

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

为了解决这个问题,可以将 JavaScript 脚本分为几个小块来执行,以避免锁定主线程。探索一下是否可以减少脚本执行过程中进行中工作量的可能性。

减少 JavaScript 交付成本的模式

当你试图降低 JavaScript 解析 / 编译和网络传输所用时间时,类似于基于路由分块和 PRPL 这些模式也会有用。

PRPL 是一种通过激进的(aggresive)代码分割和缓存来优化交互性的模式:

为了能将PRPL 的影响以视觉化方式表现出来。

我们用V8 引擎中的Runtime Call Stats 分析了流行移动网站和progressive Web Apps(PWA)的加载时间。正如我们所看到的,解析部分(用橙色表示)是很多网站页面加载时产生显著时间消耗的部分。

Wego 网站就使用了 PRPL 来保持较低的路由解析时间,让页面交互得以快速的进行。以上的很多站点都试图采用代码分割和性能预算来降低 JS 的消耗。

JavaScript 的其他消耗

JavaScript 还可以通过其他方式来影响页面性能:

  • 存储。页面可能会因为垃圾回收(GC,garbage colleciton),页面可能会出现画面中断卡顿(junk)和暂停。因为当一个浏览器回收内存的时候,JS 的执行也会被中止,所以经常回收垃圾的浏览器会比我们想象中的更频繁地中止 JS 的执行。在这种情况下,可以通过避免内存溢出和频繁内存回收来保持页面的流畅。
  • 在运行时,长时间的运行 JavaScript 会阻塞主线程,导致页面没有响应。这种情况下,可以将脚本的工作量分成多个小的板块(具体可用 requestAnimationFrame() 或者 requestIdleCallback() 进行任务调度)来执行,以此减少页面响应的问题。

Progressive Bootstrapping

很多网站将优化内容可视性作为保证交互性所需代价的一部分。为了在 JavaScript 有大体积包体时改善首屏性能,开发人员有时会先用服务器端渲染帮助客户提前看到页面内容,然后再在 JavaScript 最终执行完成后“升级”附加上事件处理程序。

但是值得注意的是,这样做也是有代价的。你 1)通常会发送一个更大的 HTML 响应来增加交互性,2)在一段时间内,用户会处在一半的页面交互体验缺失的奇怪状况下,直到 JavaScript 处理完成。

Progressive Bootstrapping 或许会是一个更好的处理方式。浏览器请求一个最少化的功能页面(仅由当前路由所需要的 HTML/JS/CSS 组成)。当有更多的资源请求的时候,应用程序则可以懒加载(lazy-load)和解锁更多的功能。


Progressive Bootstrapping visual by Paul Lewis

仅加载可视区域内的代码是其中的关键。PRPL 和 Progressive Bootstrapping 模式均可以用来实现这一点。

结论

传输脚本的大小对低端网络至关重要,而解析时间对于 CPU 有局限性的设备很重要。降低传输脚本的大小和减少解析消耗时间是有必要的。

有团队发现采用严格的性能预算可以成功降低他们 JavaScript 的传输和解析 / 编译的时间消耗。具体可参照 Alex Russell 的“ Can You Afford It?: Real-world Web Performance Budgets ” 一文中关于移动设备预算的指导。


(考虑一下在我们所做的架构决策下,JS 有多大的空间可以让我们的应用程序具有逻辑)

如果你正在建一个用于移动设备上的站点,请尽可能的在代表性硬件上开发,保持较低的 JavaScript 解析 / 编译的时间成本,并采用性能预算来确保团队对自身 JavaScript 的成本关注。

更多相关学习


(点击观看视频,作者 2017 年在 Chrome Dev Summit 上的演讲,其中涵盖了本文内容,其后就 Pinterest 和 Tinder 这样的制作网站进行了性能案例研究

作者感谢了来自 Nolan Lawson,Kristofer Baxter 和 Jeremy Wagner 的反馈。

原文链接: The Cost Of JavaScript

感谢徐川对本文的审校。

公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2018-01-02 17:313553

评论

发布
暂无评论
发现更多内容

为什么正在使用的Java版本跟环境变量的版本不一致

阡陌r

Java 踩坑

程序员陪娃漫画系列——魔方

孙苏勇

程序员 生活 陪伴 漫画

即将步入职场,忐忑而又期待的新人菜鸟

菜农阿飞

成长 新人

如何优雅滴在手机上跑Python代码

王坤祥

Python 移动应用 手机编程

从数据闭环谈微服务拆分

松花皮蛋me

微服务

MySQL中order by语句的实现原理以及优化手段

天堂

Java MySQL 性能优化

KubeFATE: 用云原生技术赋能联邦学习(一)

亨利笔记

人工智能 学习 FATE KUBEFATE

韦小宝真的幸福吗 | Random Forest

张利东

Python 学习

浅析 Cocoapods-Packager 实现

Edmond

ruby ios CocoaPods binary packager

经济大萧条对我的启示

Neco.W

创业 自我管理 职场 自我提升

高仿瑞幸小程序 00 准备工作

曾伟@喵先森

小程序 微信小程序 大前端 瑞幸

Day 47|Week 07-5 曾国藩家书|问学篇-学问何处何时都可做

熊小北同学

神经网络的激活函数为什么要使用非线性函数

王坤祥

神经网络 激活函数

把成功过成自己的生活

子铭

成功学 生活状态

消息队列Kafka - acks参数

Java收录阁

kafka

关于5G RCS的产品猜想

机器鸟

Netty 源码解析(四): Netty 的 ChannelPipeline

猿灯塔

Java并发编程系列——锁

孙苏勇

Java Java并发 并发编程 多线程

使用 jsDelivr 免费加速 GitHub Pages 博客的静态资源

mzlogin

CDN Jekyll GitHub Pages 个人博客

怎样算是一个好的开发者?

水滴

开发者

DDD 实践手册(2. 实现分层架构)

Joshua

设计模式 领域驱动设计 DDD 系统架构 分层架构

MySQL的死锁系列- 锁的类型以及加锁原理

程序员历小冰

MySQL

死磕Java并发编程(7):读写锁 ReentrantReadWriteLock 源码解析

Seven七哥

Java并发 读写锁 ReentrantReadWriteLock

游戏夜读 | 2020周记(3.27-4.3)

game1night

HashMap 的 7 种遍历方式与性能分析

Bruce Duan

Java 性能 hashmap 遍历

南丁格尔科普

小匚

多云的一点思考

HU

InfoQ写作平台首秀,来个自我介绍

nuhcoad

个人感想

阿里巴巴Java开发手册泰山版解读

Bruce Duan

说出来就不灵啦

伯薇

糊涂 活在当下 享受状态 生活状态 观察者

学习来应对创业的未知

Neco.W

创业 重新理解创业

JavaScript的成本_JavaScript_Addy Osmani_InfoQ精选文章