javascript概述
在这篇文章中,有很多内容可以涵盖广阔而变化莫测的景观。 它也是涵盖每个人最喜欢的主题:本月的JS框架™。
我们将尝试坚持“工具而不是规则”的口头禅,并将JS流行语降至最低。 由于我们无法在2000字的文章中涵盖与JS性能相关的所有内容,因此请确保您已阅读参考资料,然后进行自己的研究。
但是,在深入探讨具体问题之前,让我们通过回答以下问题来更广泛地了解该问题:什么是高性能JavaScript,以及它如何适应更广泛的Web性能指标?
搭建舞台
首先,让我们排除以下问题:如果仅在台式机设备上进行测试,则您将排除超过50%的用户。
这种趋势只会继续增长,因为新兴市场首选的网络门户是价格低于100美元的Android设备。 桌面作为访问Internet的主要设备的时代已经过去,接下来的十亿互联网用户将主要通过移动设备访问您的站点。
在Chrome DevTools的设备模式下进行的测试不能替代在真实设备上进行的测试。 使用CPU和网络限制会有所帮助,但这是完全不同的野兽。 在真实设备上测试。
即使您是在真实的移动设备上进行测试,也可能会在品牌上使用这种价格仅为600美元的旗舰手机进行测试。 问题是,这不是您的用户拥有的设备。 中值设备类似于Moto G1,即具有1GB以下RAM以及非常弱的CPU和GPU的设备。
让我们看看在解析平均JS bundle时它是如何堆叠的。
Addy Osmani: 平均JS花费在JS解析和评估上的时间 。
哎哟。 虽然此图像仅涵盖JS的解析和编译时间(稍后会详细介绍),而不涉及一般性能,但它具有很强的相关性,可以视为一般JS性能的指标。
用布鲁斯·劳森(Bruce Lawson)的话来说,“ 这是万维网,而不是西方的富裕网络 ”。 因此,您的网络性能目标是使设备的运行速度比MacBook或iPhone 慢25倍 。 让它沉入一点。 但情况变得更糟。 让我们看看我们真正的目标是什么。
什么是Performance JS代码?
现在我们知道目标平台是什么,我们可以回答下一个问题:什么是高性能JS代码?
尽管没有定义性能代码的绝对分类,但我们确实有一个以用户为中心的性能模型,可以用作参考: RAIL模型 。
Sam Saccone: 绩效规划:PRPL
响应
如果您的应用在100毫秒内响应用户的操作,则用户会将响应视为立即响应。 这适用于可点击的元素,但不适用于滚动或拖动。
动画化
在60Hz显示器上,我们希望在动画和滚动时以每秒60帧的恒定速度为目标。 这导致每帧约16ms。 在这16毫秒的预算中,实际上,您需要8到10毫秒来完成所有工作,其余的工作则由浏览器内部和其他差异占用。
空闲工作
如果您有一项昂贵且连续运行的任务,请确保将其切成较小的块,以使主线程对用户输入做出React。 您不应将用户输入延迟超过50毫秒。
加载
您应该在1000毫秒内定位页面加载。 一切都结束了,您的用户开始抽搐。 这是在移动设备上实现的一个非常困难的目标,因为它与页面是交互式的有关,而不仅仅是页面在屏幕上可滚动。 实际上,它甚至更少:
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
在实践中,争取5s的互动时间标记。 这就是Chrome浏览器在Lighthouse审核中使用的功能 。
现在我们知道了指标, 让我们看一些统计数据 :
- 如果移动网站加载时间超过三秒钟,则53%的访问将被放弃
- 2人中有1人希望页面加载时间少于2秒
- 77%的移动网站在3G网络上加载所需的时间超过10秒
- 19秒是3G网络上移动网站的平均加载时间。
- 应用程序在台式机上(通过电缆)在8秒内变为交互式,在移动设备上(3G上的Moto G4)在16秒内变为交互式
- 以中位数计,开发人员为其页面发送了410KB的压缩JS。
感到足够沮丧吗? 好。 让我们开始工作并修复网络。 ✊
情境就是一切
您可能已经注意到,主要瓶颈是加载网站所需的时间。 具体来说,就是JavaScript的下载,解析,编译和执行时间。 没有其他办法,只能加载更少JavaScript并更智能地加载。
但是,除了启动网站之外,您的代码还能做些实际工作吗? 那里必须有一些性能提升,对吗?
在深入优化代码之前,请考虑您要构建的内容。 您正在构建框架还是VDOM库? 您的代码是否需要每秒执行数千次操作? 您是否正在处理时间紧迫的库来处理用户输入和/或动画? 如果没有,您可能希望将时间和精力转移到更有意义的地方。
并不是说编写高效的代码无关紧要,但是这通常不会对宏大的事物产生影响,甚至不会产生任何影响,尤其是在谈论微优化时。 因此,在通过比较JSperf.com的结果进入有关.map
, .forEach
和for
循环的Stack Overflow参数之前,请确保不仅查看树木,而且查看树木。 50k ops / s听起来可能比纸上的1k ops / s好50倍,但在大多数情况下并没有什么不同。
解析,编译和执行
从根本上讲,大多数性能不佳的JS的问题不是运行代码本身,而是在代码开始执行之前必须执行的所有步骤。
我们在这里谈论抽象级别。 您计算机中的CPU运行机器代码。 您在计算机上运行的大多数代码都是已编译的二进制格式。 (考虑到如今所有的Electron应用程序 ,我说的是代码而不是程序 。)这意味着,除了所有的OS级抽象之外,它还可以在您的硬件上本地运行,无需任何准备工作。
JavaScript未预编译。 它(作为一个相对较慢的网络)以可读代码的形式到达您的浏览器中,无论出于何种目的,它都是JS程序的“操作系统”。
该代码首先需要解析-即,将其读取并转换为可用于编制索引的计算机可索引结构。 然后将其编译为字节码,最后编译为机器码,然后由设备/浏览器执行。
还要提到的另一个非常重要的事情是JavaScript是单线程的,并且在浏览器的主线程上运行。 这意味着一次只能运行一个进程。 如果您的DevTools性能时间表充满黄色峰值,并且CPU以100%的速度运行,那么您将出现长帧/掉帧,混乱滚动以及所有其他讨厌的东西。
保罗·刘易斯: 当一切都很重要时,什么都不是! 。
因此,在您的JS开始工作之前,需要完成所有这些工作。 在Chrome的V8引擎中,解析和编译最多需要执行JS的总时间的50%。
艾迪·奥斯曼(Addy Osmani): JavaScript启动性能 。
您应该从本节中删除两件事:
- 尽管不一定是线性的,但JS解析时间与包的大小成比例。 您运送的JS越少越好。
- 您使用的每个JS框架(React,Vue,Angular,Preact…)都是另一种抽象级别(除非它是像Svelte这样的预编译框架)。 由于不直接与浏览器对话,这不仅会增加捆绑包的大小,而且还会降低代码的速度。
有多种方法可以减轻这种情况,例如,使用服务工作者在后台和另一个线程上执行作业,使用asm.js编写更易于编译为机器指令的代码,但这是另一个主题。
但是,您可以做的是避免对所有内容使用JS动画框架,并仔细阅读触发绘画和布局的内容 。 仅在绝对无法使用常规CSS过渡和动画实现动画的情况下,才使用库。
即使它们可能使用CSS过渡,复合属性和requestAnimationFrame()
,它们仍在主线程中的JS中运行。 他们基本上只是每16ms用内联样式锤击您的DOM,因为他们没有太多其他事情可以做。 您需要确保所有JS在每帧8ms内完成执行,以保持动画流畅。
另一方面,CSS动画和过渡正在主线程上运行-如果在GPU上实现得当,则不会导致重做/重排。
考虑到大多数动画在加载或用户交互过程中都在运行,因此可以为您的Web应用程序提供急需的呼吸空间。
Web Animations API是即将推出的功能集,它使您可以在主线程之外执行高效的JS动画,但是目前,请坚持使用CSS过渡和FLIP之类的技术。
捆绑包大小无所不能
今天,一切都与捆绑包有关。 在</body>
标记之前,Bower和数十个<script>
标记的时代已经一去不复返了。
现在,这一切都与npm install
有关–可以在NPM上找到任何闪亮的新玩具,将它们与Webpack捆绑在一个巨大的单个1MB JS文件中,并在限制其数据计划的同时重击用户的浏览器。
尝试运送较少的JS。 您可能不需要项目的整个Lodash库 。 您是否绝对需要使用JS框架? 如果是,是否考虑使用React以外的其他东西,例如Preact或HyperHTML ,其大小小于React的1/20? 该滚动到顶部的动画是否需要TweenMax ? npm和框架中隔离组件的便利性有一个缺点:开发人员对问题的第一React是投入更多的JS。 当您只有一把锤子时,一切看起来都像钉子。
完成修剪杂草并减少JS运输后,请尝试更智能地进行运输。 在需要时运送需要的物品。
Webpack 3具有称为代码拆分和动态导入的 惊人功能。 无需将所有JS模块捆绑为一个整体的app.js
捆绑包,它可以使用import()
语法自动拆分代码并异步加载。
您也无需使用框架,组件和客户端路由来获得其好处。 假设您有一个复杂的代码段可以为您的.mega-widget
,该代码可以在任意数量的页面上显示。 您只需在主JS文件中编写以下内容:
if (document.querySelector('.mega-widget')) {
import('./mega-widget');
}
如果您的应用在页面上找到小部件,它将动态加载所需的支持代码。 否则,一切都很好。
此外,Webpack需要自己的运行时才能运行,并将其注入到它生成的所有.js文件中。 如果使用commonChunks
插件,则可以使用以下代码将运行时提取到其自己的块中 :
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
}),
它将所有其他块中的运行时剥离到其自己的文件中,在本例中为runtime.js
。 只要确保在主JS捆绑包之前加载它即可。 例如:
<script src="runtime.js">
<script src="main-bundle.js">
然后是转译代码和polyfill的主题。 如果您正在编写现代(ES6 +)JavaScript,则可能是使用Babel将其转换为ES5兼容代码。 由于所有的冗长性,转码不仅增加了文件大小,而且还增加了复杂性,并且与本机ES6 +代码相比,转码通常会降低性能 。
除此之外,您可能正在使用babel-polyfill
whatwg-fetch
软件包和whatwg-fetch
来修补旧版浏览器中缺少的功能。 然后,如果您使用async/await
编写代码,则还可以使用包含regenerator-runtime
所需的regenerator-runtime
器来对其进行转换……
关键是,您将近100 KB的JS包添加到JS包中,这不仅具有巨大的文件大小,而且还具有巨大的解析和执行成本,以支持较旧的浏览器。
不过,惩罚使用现代浏览器的人毫无意义。 Philip Walton在本文中介绍了我使用的一种方法,即创建两个单独的包并有条件地加载它们。 Babel使用babel-preset-env
轻松实现了这一点。 例如,您有一个捆绑包用于支持IE 11,而另一个捆绑包不具有针对现代浏览器最新版本的polyfills。
一种肮脏但有效的方法是将以下内容放入内联脚本中:
(function() {
try {
new Function('async () => {}')();
} catch (error) {
// create script tag pointing to legacy-bundle.js;
return;
}
// create script tag pointing to modern-bundle.js;;
})();
如果浏览器无法评估async
功能,则我们假定它是旧的浏览器,并且只提供了polyfilled捆绑包。 否则,用户将获得简洁而现代的变体。
结论
我们希望您从本文中学到的是JS价格昂贵,应谨慎使用。
确保在真实网络条件下在低端设备上测试网站的性能。 您的网站应加载速度快,并应尽快进行交互。 这意味着可以减少JS的发送,并通过任何必要的方式更快地发送。 您的代码应始终最小化,分成更小的,可管理的捆绑包,并在可能的情况下异步加载。 在服务器端,请确保已启用HTTP / 2,以实现更快的并行传输和gzip / Brotli压缩,以大大减少JS的传输大小。
话虽如此,我想结束以下推文:
因此,我需要花很多时间才能达到这一点。 但是认真的人,有时间扔掉您的框架,看看浏览器有多快。
— Alex Russell(@slightlylate) 2016年9月15日
翻译自: https://www.sitepoint.com/javascript-performance-optimization-tips-an-overview/
javascript概述