17 JavaScript / node.js性能编码技巧,使应用程序更快

尽管目前JavaScript在社区的开发人员数量相对于地球上任何其他语言都是最高的,社区成员之间存在很多误解,浅薄的知识和不好的假设。
在这篇文章中,我们提出了一个提示列表,这可以让你的JavaScript应用程序更快。
本文不关于dev-ops,也不讨论缩小文件或设置redis或使用docker和kubernetes来提高应用程序性能。本文是关于使用JavaScript进行编码以使性能更好。
我主要讨论关于JavaScript的问题,但只有很少的观点仅与node.js有关,或者很少有观点只针对客户端JavaScript。但是,由于现在大部分JavaScript开发人员都是完整的,所以我认为您可以轻松理解这些内容。
高性能js
没有全局变量
这是非常简单的技巧之一。很多人已经熟悉这一点,但很少有人知道原因,为什么。
你可以想象你的应用程序中的每个范围都像一棵巨大的树中的一个节点。每当你尝试使用一个变量时,JavaScript首先在非常直接的节点(这是函数自己的作用域)中搜索它。如果找不到,它将移动到父节点以搜索相同的节点。如果在那里找不到,它将移动该节点的父节点,并且此过程将继续,直到它到达根节点或全局范围。
所以,无论何时你调用一个全局变量,它都会遍历整个范围树(或AST)来查找它,这是非常昂贵的。
第二点是,全局变量不会被垃圾收集器清除。所以不要不断地添加越来越多的全局变量
只有同类阵列
除非绝对必要,否则不得在同一阵列中推送不同类型的元素(不同的数据类型)。
的原因已经在本文中深入地描述了。但是,简而言之,当你使用异构数组时,JavaScript(或者我会说v8)不能使用一个合适的数组,并将它解构成一个字典,这使得在同类数组方面搜索成本非常高昂。
将通用代码放入函数中
在说别的之前,让我告诉你一些关于即时编译器的信息。
这些编译器在v8和其他JavaScript引擎中用于优化热码。现在什么是热门代码?热码是一个不断被使用的功能或对象。如果这些对象的签名没有改变,V8会存储该对象函数的编译后二进制版本。这给你一个巨大的性能提升。
现在,当您将通用代码放入函数中并且不要更改合约(参数或参数及其类型)时,V8会将其编译并优化。所以下面的编程原则肯定会给你提升性能。
避免使用删除
我是一名来自C / C ++背景的人,我被教导使用malloc等,并尽可能释放内存。
当我转移到JavaScript并发现delete删除属性和变量的操作时,我开始使用这个原因,尽管我很聪明。
但是当我开始了解JIT或JavaScript引擎中的及时编译器时,我意识到使用delete关键字时我犯的重大错误。
那么,关于将通用代码放入函数的最后一点已经谈到了JIT编译器。这里的原因也是一样的。
当您使用delete删除对象的属性; 像这样的东西delete obj.prop,它会在内部更改该对象的隐藏类。由于这个原因,编译的代码变得无效,V8创建对象分开处理。这当然是一个性能问题。
所以不要使用,delete如果它不是绝对必要的,你知道你到底在做什么。您可以将该属性设置为null,而不会更改隐藏的类或编译的代码。
关闭和计时器 – 一个致命的组合
想象一下如下的例子。

有一个简单的对象foo,它有一个函数bar,它不过是一个setTimeout的定时器。
在调用bar之后,我将foo设置为null。理想情况下,现在这个前面的对象foo已经没有更多的参考,它应该被垃圾收集。但不幸的是,计时器每次都保持参考自我。这永远不会垃圾收集该对象,并会导致内存泄漏。
所以在这种情况下要谨慎。
为类似的对象创建类
JIT编译的另一个例子就是在这里。
无论何时创建具有相同签名的对象(即具有相同的属性集),都要尝试为其创建类或构造函数。
通过这种方式,您可以为您正在使用的所有实例创建单个隐藏类,这将再次给予JIT优化代码并更快执行JavaScript的机会。
所以不是这样的:

做这个:

forEach vs()
您可能听说使用内置功能总是更好。现在的问题是,在迭代数组时应该使用for循环或forEach
那么,与超小时间无关,你必须更喜欢每个。这总是一个很好的做法。但是,如果元素未定义,forEach也会避免索引,这使得它更加智能。下面的例子可以说清楚。

在上面的例子中,文本I am for将被打印1000次; 但是I am forEach只会打印两次。
避免… …
特别是在克隆对象的情况下,我见过使用for...in循环的人。不幸的for...in是,它的设计方式永远无法实现。所以尽量避免使用它。您可以检查其他方式来克隆JavaScript中的对象。
数组文字比推送更好
如果您使用数组文本,v8将总是比使用空数组更好地理解结构,然后再推入其中的元素。

现在你可以说谁选择了第二个?每个人都只用第一种方法。
不,不完全是。因为有时候,我们不知不觉地像第二个那样编码。举个例子,假设你用一些特定的规则修改数组中的每一个元素,并想创建一个新的数组。
通常开发人员首先创建一个空数组,然后使用forEach或其他东西在前一个数组上迭代,然后在操作推送新数组中的修改后的元素之后。但更好的方法是使用Array.map()方法。
网络工作者和共享缓冲区
JavaScript是单线程的。同一个线程用于事件循环。因此,您在node.js中进行新的请求处理并在浏览器中进行dom渲染,所有事情都以非并行方式进行。
因此,无论何时您需要花费更多计算时间的任务,您都应该将其委派给一些网络工作者。在node.js的情况下,没有内置的worker,但是你可以使用npm模块或者为它创建一个新的进程。
与员工合作的一个常见问题可能是如何与他们同步(完成后不发布消息)。那么,SharedArrayBuffer可以是一个方便的方式。
从2018年1月5日起,默认情况下,SharedArrayBuffer被禁用; 但是这个(或者这种)已经在ECMA提案的第4阶段。
setImmediate over setTimeout(fn,0)
这仅适用于node.js开发人员。许多开发人员不使用setImmediate或process.nextTick,并setTimeout(fn, 0)使其程序的一部分异步。
那么,我们关于setImmediate与setTimeout(fn,0)的一些实验说,setImmediate可以比setTimeout(fn,0)快达200倍(是,不是百分比)。
因此,比setTimeout更频繁地使用setImmediate; 但要谨慎使用process.nextTick,除非你明白它是如何工作的。
避免静态文件托管
建议不要从您的node.js服务器提供静态文件。
为什么?因为节点不是为此而创建的。Node可以最好地服务于你的tcp / http请求,使它完全异步工作。
如果你正在忙于读取静态文件(该操作由libUV的线程池处理),则性能会下降,导致libUV具有预定义数量的线程(默认为4),最多只能扩展128个线程。如果文件读取任务多于指定的线程池大小,则该过程将开始变为同步。
Promise.all(异步等待)
ES6的原生承诺确实让我们的生活更轻松。但是,像祝福一样出现的是异步的等待。现在我们可以编写看起来同步但代码异步的代码。
但是,有时开发人员变得太舒服,不知不觉就会产生不必要的同步代码。其中一个例子如下。

这是一种非常常见的情况,即多个异步调用开发人员一个接一个地等待。但是,如果您使用某些行将它们全部提供Promise.all(),则可以同时解决这些问题。
删除事件监听器
我们经常使用事件监听器,特别是在客户端。通常由于各种原因,我们可能会删除事件侦听器所附的元素。
但是一件值得注意的事情是,如果事件监听器附加到的元素被销毁,它们不会被删除。重复这可能导致内存泄漏。
因此,您应该知道元素何时被销毁,并且还会随着它们移除事件侦听器。
使用本地而不是客户端库
在革命性的node.js出现之前,JavaScript仅用于客户端编程。
不幸的是,开发人员不知道哪个用户的浏览器正在使用什么版本的ECMAScript。这就是为什么通常在客户端使用的JavaScript库为不同版本编写polyfill的原因,并且它们会变得更重一些。
但是当您使用node.js时,您始终知道服务器正在运行的确切版本。因此,代替使用客户端库来承诺,使用彩色控制台或类似的东西,总是最好使用本地代码。
二进制模块
如果您正在为您的应用程序开发大型模块,并希望将性能提升到下一个级别; 你应该编译并将它们转换为二进制模块。
LinkedIn是使用node.js作为后端的公司之一,他们遵循这种做法。
避免O(n),尝试O(1)
我已经看到开发者不必要地以O(n)的复杂度而不是O(1)来进行。
在JavaScript中,对象是松散类型的,您可以将任何东西(可以转换为字符串)附加为键或属性。
现在,假设你有一个N个学生的名单,其中包含他们的卷号(这是唯一的)和totalMarks。如果有人在文本字段中输入了卷号,则会在页面中显示该特定学生的总标记。
常见的表示方法是创建一个数组并将所有学生对象推送到那里,并且当有人输入一个数字时,您将迭代学生数组并找到该特定学生并显示标记。
通过这种方式,你的复杂度为O(n)。但是,如果您使用的是对象而不是数组,并将卷号设置为属性,并将相应的学生对象设置为其值,则对于您而言,事情将变为O(1),因为每当有人输入卷号时,都必须显示studentsObj[rollNumber]对象。
这并不意味着你总是应该使用一个对象而不是数组。这完全取决于情况,问题陈述和要求。
结论
增加服务器的配置,扩展服务,分发服务是一些众所周知的过程,可以提高应用程序的性能。
但是,如果你的代码导致内存泄漏或顺序处理,所有这些dev-ops步骤都无法保存你的服务器变慢或甚至崩溃。
因此,在编码(不考虑语言)的同时,您必须了解所有良好实践,边缘案例和绩效点。

Back to Top