在Web开发中,速度可能是使大家在竞争对手面前占优势的关键因素。而在较快的网络上每浪费一毫秒,放到较慢的网络上,这一缺陷就会被无限放大,造成致命缺陷。
所以在本文中,我们将研究13种实用方法,无论您是使用Node.js还是使用客户端JavaScript编写服务器端代码,都可以提高JavaScript代码的速度。
只要有可能,我都将链接到使用https://jsperf.com创建的基准测试测试。如果你想自己测试这些技巧,请确保单击那些链接!
1少做
最快的代码是永不运行的代码。
删除不必要的功能
轻松优化已编写的代码很容易,但是通常最大的性能提升来自于退后一步并询问我们的代码是否首先需要存在。
在继续进行个别优化之前,请问自己自己程序是否需要做它正在做的所有事情。该功能,组件或功能是否必要?如果不是,请将其删除。这一步对于提高代码速度非常重要,但是却很容易被忽略!
避免不必要的步骤
在一个较小规模的项目中,你可以问问自己功能采取的每一个步骤是否对于最终结果的表现来说都是必要的。举个例子,为了得到最终结果,您的数据是否跳过了不必要的循环?
下面是示例,可能被简化了,但是它代表了一些在较大的代码库中很难发现的内容:
在这个简单的示例中,运行一些代码比不运行任何代码慢很多,性能有很大的差异。
总体上很少有人会犯上述错误,但是在更长,更复杂的代码中,程序员们也比较容易去添加一些不必要的步骤来获得所需的最终结果,而这些不必要的步骤对于性能上的提高并没有多大作用,所以尽量避免他们。
2经常少做
如果你无法删除代码,那就问自己是否可以减少执行频率,代码如此强大的原因之一是,它可以使我们轻松地重复操作,也因此我们很容易执行不必要亲自重复进行的任务。以下是一些需要注意的特殊案例。
尽早摆脱loops
找出不需要在循环中完成每个迭代的情况。例如,如果要搜索特定值并找到该值,则不需要后续迭代。你应该使用break语句来中断循环的执行:
或者,如果只需要对循环中的某些元素执行操作,则可以使用continue语句跳过对其他元素执行的操作。Continue终止当前迭代中语句的执行,并立即转到下一个语句:
还值得记住的是,可以使用标签突破嵌套循环。这些允许你将break或Continue语句与特定循环相关联:
尽可能预计算一次
使用以下函数,我们将在应用程序中多次调用该函数:
这段代码的问题是,每当我们调用whichsideOfTheForce时,我们都会创建一个新对象。每次函数调用时,都不必要将内存重新分配给我们的明暗阵列。
给定明暗值是静态的,更好的解决方案是一次声明这些变量,然后在调用whichsideofTheForce时引用它们。尽管我们可以通过在全局范围内定义变量来做到这一点,但这将允许它们在我们的函数外部被篡改。更好的解决方案是使用闭包,这意味着返回一个函数:
现在,实例化展示明暗阵列操作,嵌套函数也是如此:
每次运行doSomething时,都会从头开始创建嵌套函数doSomethingElse。同样,闭包提供了解决方案。如果我们返回一个函数,doSomethingElse仍然是私有的,但只会创建一次:
使用指令码来最小化操作次数
如果仔细考虑函数中动作的顺序,通常可以提高代码速度。假设我们有一个项目价格数组,以美分为单位计值,我们需要一个函数来对这些项目求和最后以美元为单位返回结果:
这个函数必须做两件事-将美分转换为美元和对元素求和,两者的操作的顺序很重要。我们可以采取以下操作,使用如下函数先将美分转换为美元:
但是,在下面这种方法中,我们对数组中的每个项执行除法操作。通过将我们的行为按相反的顺序排列,我们就只需要执行一次除法:
总结以上,采取这些措施的关键在于确定好最佳顺序。
学习代码的时间复杂度O(n)
学习代码的时间复杂度是帮助大家理解为什么某些函数可以比其他函数运行速度更快、占用内存更少(尤其是在规模上)问题的最佳途径之一。
同时,也可以用来展示说明“为什么二进制搜索是最有效的搜索算法之一以及为什么快速排序往往是最有效的排序数据的方法”这两个问题,这种展示说明让人一目了然。而实际上,也为大家提供了一种途径,去更好地理解和应用本文中讨论的几种关于速度优化的方法。
这是一个比较深入的主题,所以如果大家有兴趣了解更多,可以阅读以下我写过的两篇文章:一篇文章是我对于Big-O符号的探讨,另一篇是在谷歌时间和空间上的复杂背景下,我针对其面试问题探讨出的四种解决方案。
3做得更快
前两节的内容“少做”和“经常少做”对于代码速度提升有更加显着的作用,而在本节中,我们将继续研究使您的代码更快的几种方法,但是这些方法更多地与优化现有代码有关,而不同于之前减少代码或使其运行次数减少的途径。
当然即便是这些优化也要减小代码的大小或使其对编译器更友好,从而减小编译器代码的大小。但是从表面上看,你只是在更改代码而不是删除代码,这也是以下内容记录在“Do It Faster”下的原因!
首选内置方法
对于那些有编译器和低级语言经验的人来说,这一点似乎是显而易见的。但一般来说,如果JavaScript有一个内置的方法,就使用它。编译器代码的设计针对方法或对象类型进行了性能优化。
另外,基础语言是C++。除非你的用例非常具体,否则您自己的JavaScript实现优于现有内置方法的可能性非常低!
为了测试这一点,让我们为Array.prototype.map方法创建我们自己的JavaScript实现:
现在,让我们创建一个由1到100之间的100个随机整数组成的数组:
即使我们想执行一个简单的操作,例如将数组中的每个整数乘以2,我们也会看到性能差异:
从以上测试结果可以看到使用新的JavaScript映射功能比使用Array.prototype.map慢大约65%。
为job使用最佳对象
同样,最佳性能还来自为手头工作选择最合适的内置对象。JavaScript的内置对象远远不止数字,字符串,函数,对象等基本类型。而如果将这些不常见的内置对象在正确的上下文中使用,则可以提供明显的性能优势。
在其他文章中,我写了关于如何使用Sets可以比使用Arrays更快、以及如何使用Maps可以比使用常规Objects更快的方法。其中说明了一点,set和map在定期添加和删除条目的上下文中,它们可以提供显着的性能优势。
所以如果想要代码提速,了解好内置的对象类型,并始终尝试使用最满足你需要的对象不失为一个好办法。
不要忘记内存管理
作为一种高级语言,JavaScript会为你处理许多底层细节。其中的细节之一为你进行内存管理。
此内存管理通过使用一种称为垃圾回收的系统来进行内存的释放,而这种系统尽可能自行判断进行内存的清理,而不需要开发者的明确指令。
但尽管JavaScript中的内存管理是自动的,但这并不意味着它是完美的。您可以采取其他步骤来管理内存并减少内存泄漏的可能性。
例如sets 和Maps,它们具有“弱”变体,称为Weaksetsand和WeakMaps。拥有对对象的“弱”引用,而诸如此类的还有很多,通过确保未引用的值得到垃圾回收,因此可以达到防止内存泄漏的目的。
同时你还可以使用ES2017中引入的javaScript的TypedArray对象来更好地控制内存分配。例如,Int8Array可以采用-128到127之间的值,并且大小仅为一个字节。
但是,值得注意的是,使用TypedArrays的对代码性能提升的作用可能很小:比较常规数组和Uint32Array会显示写入性能略有改善,但读取性能却几乎没有改善(这两项测试均归ChrisKhoo所有)。
对底层编程语言有基本的了解可以帮助你编写JavaScript代码时更快更好。而我将在我的文章《 JavaScript开发人员可以从C++中学到什么》中详细介绍这一点。
尽可能使用单态形式
如果我们设置consta =2,则变量a可被视为多态的(可以更改)。相反,如果我们直接使用2,则可以认为是单态的(其值是固定的)。当然,如果我们需要多次使用变量,那设置变量非常有用。
但是,如果我们只使用一次变量,那么完全避免设置变量会稍快一些,也就是单态形式。因此可采取简单的乘法功能:
如果我们运行multiple(2,3),它将比运行速度快约1%:
这是一个很小的胜利,但是在一个大型代码库中,许多类似的小小的胜利可以叠加起来对于代码性能优化的作用就非常大了。
同样,在函数中使用参数可提供灵活性,但同时会降低性能。虽然参数是编程的组成部分,但是我们在不需要它的情况下可以不用,这样可以获得性能上的优势。而乘积函数的更快版本参考下面:
综上,使用这种方法在性能提升上作用比例很小(在我的测试中约为2%),但是如果可以在大型代码库中多次进行这种提升,性能上的作用也会更大,此时可以参考使用一下。
但是需要注意的是,通常只有当一个值必须是动态之时才引入参数,而当变量要被多次使用时才引入变量。
避免使用“删除”关键字
基准测试1:https://jsperf.com/removing-variables-from-an-object/1
基准测试2:https://jsperf.com/delete-vs-map-prototype-delete
delete关键字用于从对象中删除条目。大家可能会觉得这对于你的应用程序是必要的,但实际上如果可以避免使用它,那尽量避免使用。
在后台,“delete”关键字移除了V8Javascript引擎中隐藏类模式的优点,使其成为通用的慢对象,所以你可以猜想它的执行速度会更慢!
而也许将不需要的属性设置为undefined可能就足够了,当然这得根据大家个人的使用需求。
我在网上看到了一些建议,比如有人建议使用类似以下的功能,以此创建没有特定属性的原始对象的副本,这样可能会更快:
但是,在我的测试中,上面的函数(和其他几个函数)证明比delete关键字还要慢。另外,与删除obj.a或obj.a= undefined相比,此类函数的可读性较低。
另外根据测试,我建议大家是否可以考虑使用Map代替object,如Map.prototypedelete,这要比delete语句运行起来快得多。
4以后再做
如果前三类优化方式大家做起来有难度的话,那么可以尝试接下来介绍的第四类优化。
这一类优化可以使你的代码感觉更快,但-即使它们的运行时间几乎是完全一样的相同。这涉及到以这样一种方式重组代码:不太完整或要求更高的任务不会阻塞最重要的内容。
使用异步代码防止线程阻塞
默认情况下,JavaScript是单线程的,一次一步同步运行代码。(实际上,浏览器代码可能正在运行多线程来捕获事件和触发器处理程序,但就编写JavaScript代码而言,它是单线程的)。
这对于大多数JavaScript代码都适用,但是,如果我们运行的项目可能需要很长时间,那此时我们不希望阻止或延迟更重要代码的执行。
解决方案是使用异步代码。
对于某些内置方法,例如fetch()或XMLHttpRequest(),这是强制性的,但也值得注意的是,可以将任何同步函数都设为异步——
如果您有耗时的(同步)操作,如果您有一个耗时(同步)的操作,例如对大型数组中的每个项执行操作,这段代码可以设置为异步,这样就不会阻塞其他代码的执行。如果你对异步JavaScript还不熟悉,请看我的文章《AGuide to JavaScript Promises》。
此外,许多模块(如Node.js的文件系统)具有某些功能的异步和同步变体,例如fs。writeFile()
使用代码拆分
如果您在客户端使用JavaScript,那么你的首要任务应该是确保视觉效果尽快出现。一个关键的基准是“firstcontentful paint”,用于当浏览器呈现来自DOM的第一位内容时衡量从导航到“时间”的时间。
改善此问题的最佳方法之一是通过JavaScript代码拆分。
与其将JavaScript代码打包为一大包,不如考虑将其拆分为较小的块,以便预先需要最少的JavaScript代码。代码拆分的方式会因你使用的是React,Angular,Vue还是VanillaJavascript而异。
一种相关的策略是Tree-Shaking,这是消除死代码的一种形式,专门用于从代码库中删除未使用或不必要的依赖项。要查找有关此内容的更多信息,我建议阅读Google的这篇文章。(另外记得缩小你的生产代码!)
5结论
确保你对代码进行了实际上的有效优化,最好方法是对其进行测试