JavaScript:Event Handlers:在哪里声明变量-本地或闭包(相对于开销)



我发现自己在编写包含事件处理程序的各种函数。最好在父函数(闭包)的根处声明处理程序函数所需的变量,特别是如果它们是jQuery选择、多个处理程序所需的常量,或者是每次触发事件时我不想重复的一些预计算。一个简单的例子:

var touchDrag = function() {
var x, y, i;
var $mySelection = $('.selection');
$('#some-elem').on( 'touchmove', function(e) {
x = something;
y = something;
i++;
$mySelection.doSomething();
// more code..
});
}  

然而,我经常看到在处理程序函数(local)内部声明的处理程序变量。问了几位编码人员,一些争论随之而来,但没有明确的答案。

我知道保持可变范围尽可能小是一种很好的做法。然而,对于频繁触发事件(如.scroll()touchmove),在我看来,每次触发事件时重新声明它们会有很大的开销(而不是只分配一次每个var)?

通常情况下,应在需要的最小范围内定义变量。因此,如果一个变量只在touchmove事件的实际处理过程中需要,并且它没有将状态从一个事件传递到下一个事件,那么我通常会在实际的touchmove事件处理程序中声明它,这样它的范围就尽可能小,并且在不使用时会被垃圾收集。

当然,也有例外情况可能会从更高范围的声明中受益,例如:

  1. 预计算与其每次都计算一次,不如只计算一次并放在手边。若这些都是用户触发的事件,那个么一个小的预计算的性能和用户时间几乎并没有实际关系。

  2. 从一次事件到下一次事件保持状态。这需要在更高级别声明变量,以便它可以从一个事件持续到下一个事件。

  3. 与其他代码共享如果变量中的值由同一上下文中的其他处理程序共享,那么显然您必须在足够高的级别上声明它,以便所有想要访问它的人都可以使用它。一个常见的例子可能是一个计时器,一个事件想要启动,另一个事件可能想要停止。

以下是避免在更高范围内声明变量的一些原因,这会延长变量的使用寿命:

  1. 内存泄漏您可能会无意中造成一些内存泄漏。如果您正在缓存一个DOM元素,然后在代码的其他地方您或其他人决定替换该DOM元素,那么现在您在JS代码中有了对该DOM元素的引用,这将防止它被垃圾收集。

  2. 陈旧的价值观以上相同的情况可能会导致缓存变量中的值错误。如果您根据需要获取或计算值,而不是长时间保存值,那么过时值的风险总是较小的。

  3. 代码清晰性和健壮性如果你有一组在更高范围内声明的变量,然后是一堆函数,每个函数都使用其中的一些变量,那么谁在使用什么就不太清楚了。如果这些函数实际上是可以相互调用的函数,那么一个函数有各种可能性来破坏另一个函数正在使用的变量。从逻辑的极端来看,这正是局部变量比全局变量更可取的原因。虽然一个级别更高的范围没有全局变量那么糟糕,但它仍然存在一些相同的问题。


此外,为了回应您的一个担忧,声明和初始化局部变量并不是一个"大开销",除非计算初始值的工作是一项耗时的任务。但在您的示例中,当函数启动时,简单地将var x移动到使用它的函数中并不会显著降低性能,事实上,它甚至可能在函数执行期间提高性能,因为访问本地变量比访问更高范围的变量更快(在检查更高范围之前,首先检查本地命名空间中的变量)。

至于您现在添加到问题中的$mySelection变量,我会在需要它的最小范围内声明它,直到/除非您有任何数据/信息表明它的初始化性能实际上会给您带来问题。一般来说,在现代CPU上,简单的选择器搜索操作非常快。

与几乎所有的性能问题一样,在没有任何证据表明性能问题实际上是一个问题之前就试图过早地解决它,这很少是对时间的有效利用,也很少是偏离最佳编码实践的理由。如果有疑问,请尽可能保持代码的简单性和自包含性。

尽管重用尽可能多的代码是件好事,但在某些情况下,重用变量可能会导致意想不到的问题

  • 共享变量可能会发生副作用。例如,在一些有很多事件的大型应用程序中,在它们之间使用共享变量可能会导致一些意外行为,因为事件是异步的,没有人能够预测事件何时完成

  • (在我看来)使用共享变量很难读取和调试,因为我不知道是否有其他东西在使用它(这会在重构代码时导致一些问题)

最新更新