假设我们正在编写一个浏览器应用程序,其中流畅的动画至关重要。我们知道垃圾收集会阻塞执行足够长的时间以导致可察觉的冻结,因此我们需要尽量减少我们创建的垃圾量。为了尽量减少垃圾,我们需要在主动画循环运行时避免内存分配。
但是该执行路径到处都是循环:
var i = things.length; while (i--) { /* stuff */ }
for (var i = 0, len = things.length; i < len; i++) { /* stuff */ }
他们的var
语句allocate memory可以分配垃圾收集器可能删除的内存,这是我们想要避免的。
那么,在 JavaScript 中编写循环结构以避免每个循环结构分配内存的好策略是什么?我正在寻找一个通用的解决方案,列出了利弊。
以下是我提出的三个想法:
1.)声明索引和长度的“顶级”变量;到处重复使用它们
我们可以在顶部声明app.i
和app.length
,并一次又一次地重用它们:
app.i = things.length; while (app.i--) { /* stuff */ }
for (app.i = 0; app.i < app.length; app.i++) { /* stuff */ }
优点:很容易实现。缺点:取消引用属性对性能的影响可能意味着代价高昂的胜利。可能会意外滥用/破坏属性并导致错误。
2.)如果数组长度已知,不要循环——展开
我们可以保证一个数组有一定数量的元素。如果我们事先知道长度是多少,我们可以在程序中手动展开循环:
doSomethingWithThing(things[0]);
doSomethingWithThing(things[1]);
doSomethingWithThing(things[2]);
优点:高效。缺点:在实践中几乎不可能。丑陋的?改变烦心?
3.)通过工厂模式利用闭包
编写一个返回“looper”的工厂函数,该函数对集合(a la _.each
)的元素执行操作。Looper 在创建的闭包中保留对索引和长度变量的私有引用。循环器必须重置i
,并且length
每次调用它。
function buildLooper() {
var i, length;
return function(collection, functionToPerformOnEach) { /* implement me */ };
}
app.each = buildLooper();
app.each(things, doSomethingWithThing);
优点:更实用,更惯用?缺点:函数调用会增加开销。闭包访问已被证明比对象查找慢。