在过去的几年中,由于解析速度的提高和脚本的浏览器编译速度的提高,所谓的“
JavaScript价格 ”发生了重大的积极变化。 现在,在2019年,由JavaScript创建的系统上的负载的主要组成部分是脚本的加载时间及其执行时间。

如果浏览器忙于执行JavaScript代码,则用户与网站的互动可能会暂时中断。 结果,我们可以说与加载和执行脚本相关的瓶颈优化可以对网站性能产生强大的积极影响。
网站优化通用实用指南
以上对Web开发人员意味着什么? 这里的重点是,用于解析(解析,解析)和编译脚本的资源成本没有以前那么严重。 因此,在分析和优化JavaScript包时,开发人员应考虑以下三个建议:
- 尝试减少下载脚本所需的时间。
- 尽量使您的JS包小。 这对于为移动设备设计的站点尤其重要。 使用小捆绑包可以缩短代码加载时间,降低内存使用水平,并减少处理器上的负载。
- 尽量避免将所有项目代码都显示为一大包。 如果束的大小超过大约50-100 Kb,则将其分成小块的单独片段。 由于HTTP / 2多路复用,可以同时处理多个服务器请求和多个响应。 这减少了与满足其他数据加载请求相关的系统负载。
- 如果您正在处理移动项目,请尝试使代码尽可能小。 此建议与移动网络上的低数据速率相关。 此外,争取经济地使用内存。
- 尝试减少执行脚本所需的时间。
- 避免使用冗长的任务 ,这些任务会长时间加载主线程,并增加页面进入用户可以与之交互的状态所需的时间。 在当前环境中,加载脚本后运行的脚本对“ JavaScript价格”做出了重大贡献。
- 不要在页面中嵌入大型代码段。
- 此处应遵循以下规则:如果脚本大小超过1 Kb,请尝试不要将其嵌入页面代码中。 提出此建议的原因之一是1 Kb是一个限制,在此限制之后,外部脚本代码的缓存将开始在Chrome中运行。 另外,请记住,解析和编译嵌入式脚本仍在主线程中运行。
为什么加载和执行脚本如此重要?
为什么在现代条件下优化脚本的加载和执行时间很重要? 在通过慢速网络访问站点的情况下,脚本加载时间非常重要。 尽管4G网络(甚至5G)的传播越来越多,但在许多使用移动Internet连接的情况下,
NetworkInformation.effectiveType属性显示的指标处于3G网络级别甚至更低级别。
执行JS代码所需的时间对于处理器速度较慢的移动设备很重要。 由于移动设备使用不同的CPU和GPU,因此当设备过热以保护它们时,其组件的性能会降低,因此您会发现昂贵和便宜的手机和平板电脑的性能之间存在严重差距。 这极大地影响了JavaScript代码的性能,因为设备执行此类代码的能力受到该设备的处理器能力的限制。
实际上,如果我们分析在Chrome之类的浏览器中加载和准备页面以进行工作所花费的总时间,则大约有30%的时间可用于执行JS代码。 以下是对高性能台式计算机上非常典型的网页(reddit.com)的加载情况的分析。
在加载页面的过程中,大约有10%至30%的时间花费在使用V8执行代码上如果我们谈论移动设备,那么在普通电话(Moto G4)上,使用JS代码执行reddit.com所需的时间要比使用高级设备(像素3)所需的时间长3-4倍。 在性能较弱的设备(阿尔卡特1X的价格不到100美元)上,解决同一问题所需的时间至少是Pixel 3之类的时间的6倍。
在不同类别的移动设备上处理JS代码所需的时间请注意,reddit.com的移动版本和桌面版本不同。 因此,您无法比较移动设备和MacBook Pro的结果。
当您尝试优化JavaScript代码的执行时间时,请注意可能
长时间捕获UI流的
冗长任务 。 这些任务可能会阻止其他极其重要的任务的执行,即使页面的外观看起来完全可以工作了。 长期任务应分解为较小的任务。 通过将代码划分为多个部分并控制这些部分的加载顺序,可以实现以下事实:页面将更快地进入交互状态。 希望这将导致用户与页面交互时的不便减少。
长时间运行的任务将捕获主线程。 他们应该分成碎片V8的改进如何加快解析和编译脚本的速度?
自Chrome 60以来,在V8中解析源JS代码的速度提高了2倍。 同时,解析和编译现在对“ JavaScript价格”的贡献较小。 这要归功于其他Chrome优化工作,这些工作使这些任务并行化。
在V8中,解析和编译主线程中产生的代码的工作量平均减少了40%。 例如,对于Facebook,该指标的改进是46%,对于Pinterest是62%。 YouTube获得的最高结果是81%。 由于将分析和编译移至单独的流,因此可能会产生此类结果。 这是对主流以外相同任务的流解决方案的现有改进的补充。
各种版本的Chrome中的JS解析时间您还可以可视化各种版本的Chrome中进行的V8优化如何影响处理代码所需的处理器时间。 在Chrome 61需要解析Facebook JS代码的同时,Chrome 75现在可以解析Facebook JS代码,此外,还可以解析Twitter代码6次。
Chrome 61需要处理Facebook JS代码时,Chrome 75可以处理Facebook代码和Twitter代码数量的六倍。让我们谈谈如何实现这种改进。 简而言之,可以在工作流中以流模式解析和编译脚本资源。 这意味着:
- V8可以解析和编译JS代码,而不会阻塞主线程。
- 当通用HTML解析器遇到
<script>
时,脚本的流处理开始。 HTML解析器处理阻止页面解析的脚本。 面对异步脚本,他继续工作。 - 在大多数实际场景中,以某些网络连接速度为特征,V8解析代码的速度超过了其加载速度。 结果,V8在脚本的最后一个字节加载后几毫秒内完成了解析和编译代码的任务。
如果您详细讨论所有这些,那么这里的重点如下。 在较旧的Chrome版本中,需要在分析脚本之前完整下载该脚本。 这种方法简单易懂,但是当使用它时,会不合理地使用处理器资源。 版本41和68之间的Chrome浏览器,在脚本开始加载后立即开始以异步模式进行解析,并在单独的线程中执行此任务。
脚本以片段的形式发送到浏览器。 V8具有至少30 Kb的代码后,将开始流数据处理。在Chrome 71中,我们转到了基于任务的系统。 在这里,调度程序可以同时启动多个异步/延迟脚本处理会话。 由于此更改,通过解析主线程创建的负载减少了约20%。 这使在真实站点上获得的TTI / FID分数提高了约2%。
Chrome 71使用基于任务的代码处理系统。 通过这种方法,调度程序可以同时处理多个异步/暂挂脚本。在Chrome 72中,我们将流处理作为解析脚本的主要方式。 现在,即使是常规的同步脚本也可以通过这种方式进行处理(尽管这不适用于内置脚本)。 此外,如果主线程需要解析的代码,我们将停止取消基于任务的解析操作。 这样做的原因是,这导致需要重新执行一些已经完成的工作。
以前的Chrome版本支持流解析和流代码编译。 然后,从网络下载的脚本应首先进入主流,然后将其重定向到流脚本处理系统。
这通常导致流解析器等待已经从网络下载但尚未由主流重定向到流处理的数据。 发生这种情况是由于主线程可能忙于其他一些任务(例如,解析HTML,创建页面布局或执行JS代码)。
现在,我们正在尝试如何在预加载页面时开始解析代码。 以前,这种机制的实现由于需要使用主线程的资源来将任务传输到流解析器而受到阻碍。 可以在
此处找到有关解析“立即”运行的JS代码的详细信息。
这些改进如何影响开发人员工具中可以看到的内容?
除上述之外,可以注意到,以前的开发人员工具中存在一个
问题 。 它包含以下事实:显示有关解析任务的信息,就好像它们完全阻塞了主线程一样。 但是,解析器仅在需要新数据时才执行阻塞主线程的操作。 由于我们已经从使用单个流进行流数据处理的方案转移到了应用流处理任务的方案,这已变得非常明显。 这就是您在Chrome 69中看到的内容。
问题出在开发人员工具中,这是由于显示了有关解析脚本的信息,就像它们完全阻塞了主线程一样在这里,您可以看到“解析脚本”任务需要1.08秒。 但是,事实上,解析JavaScript并不是那么慢! 在大多数时间里,除了等待主线程中的数据外,没有执行任何有用的操作。
在Chrome 76中,您已经可以看到完全不同的图片。
在Chrome 76中,解析分为许多小任务通常,应该注意的是,开发人员工具的“性能”选项卡非常有用,可以查看页面上正在发生的事情的整体情况。 为了获得更多反映V8功能的详细信息,例如解析时间和编译时间,您可以将Chrome跟踪与RCS(运行时调用统计)支持一起使用。 在收到的RCS数据中,您可以找到“解析背景”和“编译背景”指示符。 他们能够报告在主线程之外解析和编译JS代码所花费的时间。 “分析和编译”度量标准指示在主线程中的相关活动上花费了多少时间。
使用Google跟踪分析RCS数据所做的更改如何影响实际站点的工作?
让我们看一些流脚本处理如何影响浏览真实站点的示例。
dReddit
在MacBook Pro上查看reddit.com。 解析和编译在主线程和工作线程中花费的JS代码的时间reddit.com网站上有多个JS捆绑包,每个捆绑包的大小均超过100 Kb。 它们被包装在外部函数中,这导致在主线程中执行大量的
“惰性”编译 。 上图中的关键部分是处理主线程中的脚本所需的时间。 这是由于以下事实:主线程上的大量负载会增加页面切换到交互模式所花费的时间。 在处理reddit.com网站代码时,大部分时间都花在主线程上,而工作/后台线程的资源则被最小化。
您可以通过将一些大捆绑包分成几部分(每个捆绑包约50 Kb)来进行优化,而无需将代码包装在函数中。 这将使脚本的并行处理最大化。 结果,可以在流模式下同时解析和编译包。 在准备工作页面时,这将减少主线程上的负载。
▍脸书
在MacBook Pro上查看facebook.com。 解析和编译在主线程和工作线程中花费的JS代码的时间我们还可以考虑使用facebook.com这样的网站,该网站使用约6 MB的压缩JS代码。 使用大约292个请求加载此代码。 其中有些是异步的,有些是针对预加载数据的,有些则优先级较低。 大多数Facebook脚本尺寸较小且关注范围狭窄。 这可以通过后台/工作流程对并行数据处理产生良好的效果。 事实是,可以通过流式脚本处理同时解析和编译许多小脚本。
请注意,您的网站可能与Facebook网站不同。 您可能没有长时间处于打开状态的应用程序(例如Facebook网站或Gmail界面的状态),并且在与它们一起使用时,使用桌面浏览器下载如此大量的脚本可能是合理的。 但是,尽管如此,我们仍可以给出对任何项目都公平的一般建议。 事实是值得将应用程序代码分成适量的捆绑包,并且仅在需要它们时才需要下载这些捆绑包。
尽管大多数解析和编译JS代码的工作都可以在后台线程中使用流传输工具完成,但是某些操作仍然需要主线程。 当主线程忙于某些事情时,页面无法响应用户交互。 因此,建议注意加载和执行JS代码对UX站点的影响。
请记住,并非现在所有的JavaScript引擎和浏览器都可以流式传输脚本并优化其加载。 但是尽管如此,我们希望上面概述的一般优化原则可以改善使用在任何现有浏览器中查看的网站的用户体验。
JSON解析价格
解析JSON代码比解析JavaScript代码效率更高。 事实是,JSON语法比JavaScript语法简单得多。 可以应用此知识,以提高使用大型配置对象(例如Redux存储库)的Web应用程序的工作准备速度,该对象的结构类似于JSON代码。 结果,您可以将数据表示为JSON对象的字符串,并在运行时解析这些对象,而不是将数据显示为代码中嵌入的对象文字。
第一种方法,使用JS对象,如下所示:
const data = { foo: 42, bar: 1337 };
第二种方法使用JSON字符串,涉及到以下结构的使用:
const data = JSON.parse('{"foo":42,"bar":1337}');
由于您只需要执行一次JSON字符串处理,因此使用
JSON.parse
的方法比使用JavaScript对象文字快得多。 特别是-在“冷”页面加载时。 建议您使用JSON字符串表示以10 Kb开始的对象。 但是,与任何性能技巧一样,不应无情地遵循该技巧。 在应用此技术在生产中呈现数据之前,必须进行测量并评估其对项目的实际影响。
将对象文字用作大量数据的存储带来了另一个威胁。 重点是存在这样的文字可以被处理两次的风险:
- 通过文字的初步解析来执行第一处理过程。
- 第二种方法是在文字的“惰性”解析期间执行的。
您不能摆脱处理对象文字的第一遍。 但是,幸运的是,可以通过将对象文字放在顶层或
PIFE内部来避免第二遍。
在重复访问网站时解析和编译代码又如何呢?
借助V8的缓存代码和字节码功能,可以针对用户多次访问的情况优化站点性能。 首次从服务器请求脚本时,Chrome会下载该脚本并传递V8进行编译。 另外,浏览器将该脚本的文件保存在其磁盘缓存中。 当执行第二个下载相同JS文件的请求时,Chrome从浏览器缓存中获取该文件,然后再次传递V8进行编译。 但是,这一次,编译后的代码将被序列化并作为元数据附加到缓存的脚本文件中。
V8中的代码缓存系统当第三次请求脚本时,Chrome会从缓存中获取文件及其元数据,然后同时传输V8。 V8反序列化元数据,因此可能会跳过编译步骤。 如果在72小时内访问该站点,则会触发代码缓存。 当服务工作者用于缓存脚本时,Chrome还使用贪婪代码缓存策略。 可以在
此处找到有关代码缓存的详细信息。
总结
在2019年,网页的主要性能瓶颈是加载和执行脚本。 为了改善这种情况,请努力使用小尺寸的同步(内置)脚本,这对于组织用户与页面在加载后立即可见的那一部分进行交互是必需的。 建议用于服务页面其他部分的脚本以延迟模式加载。 将大捆切成小块。 这将有助于实施用于处理代码的策略,在该策略中,仅在需要时且仅在需要时才加载代码。 这将最大化V8的功能,旨在并行处理代码。
如果您正在开发移动项目,则应努力确保它们使用尽可能少的JS代码。 该建议源于以下事实:移动设备通常在相当慢的网络中工作。 此外,此类设备可能会在可用RAM和可用处理器资源方面受到限制。 尝试在准备从网络下载的脚本和使用缓存之间花费时间。 这将使在主线程之外执行的代码的解析和编译量最大化。
亲爱的读者们! 您是否在考虑现代浏览器对JS代码处理的特殊性的情况下优化Web项目?
