JavaScript中无分配循环的模式?

Gla*_*eep 5 javascript garbage-collection memory-management

假设我们正在编写一个平滑动画至关重要的浏览器应用程序.我们知道垃圾收集可以阻止执行足够长的时间以引起可察觉的冻结,因此我们需要最小化我们创建的垃圾量.为了最大限度地减少垃圾,我们需要在主动画循环运行时避免内存分配.

但是执行路径遍布循环:

var i = things.length; while (i--) { /* stuff */ }

for (var i = 0, len = things.length; i < len; i++) { /* stuff */ }
Run Code Online (Sandbox Code Playgroud)

并且他们的var语句分配内存可以分配垃圾收集器可能删除的内存,我们要​​避免.

那么,在JavaScript中编写循环结构以避免每个都分配内存的好策略是什么?我正在寻找一个通用的解决方案,列出了利弊.


以下是我提出的三个想法:

1.)为索引和长度声明"顶级"变量; 到处重用它们

我们可以声明app.iapp.length在顶部,并一次又一次地重复使用它们:

app.i = things.length; while (app.i--) { /* stuff */ }

for (app.i = 0; app.i < app.length; app.i++) { /* stuff */ }
Run Code Online (Sandbox Code Playgroud)

优点:简单到实现.缺点:取消引用属性所带来的性能可能意味着Pyrrhic的胜利.可能会意外误用/破坏属性并导致错误.

2.)如果已知数组长度,请不要循环 - 展开

我们可以保证数组具有一定数量的元素.如果我们事先知道长度是多少,我们可以在程序中手动解开循环:

doSomethingWithThing(things[0]);
doSomethingWithThing(things[1]);
doSomethingWithThing(things[2]);
Run Code Online (Sandbox Code Playgroud)

优点:高效.缺点:在实践中很少可能.丑陋?烦人改变?

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);
Run Code Online (Sandbox Code Playgroud)

优点:更实用,更惯用?缺点:函数调用增加了开销.闭包访问显示比对象查找慢.

mat*_*sta 1

他们的var语句分配内存可以分配垃圾收集器可能删除的内存,这是我们想要避免的。

这有点误导。简单地使用var不会在堆上分配内存。当调用函数时,函数中使用的每个变量都预先在堆栈上分配。当函数完成执行时,堆栈帧将被弹出,并且内存将立即取消引用。

当您在堆上分配对象时,与垃圾收集相关的内存问题就会成为问题。这意味着以下任何一项:

  • 闭包
  • 事件监听器
  • 数组
  • 对象

在大多数情况下,任何typeof foo返回"function"or "object"(或任何新的 ES6typeof返回值)的内容都会在堆上生成一个对象。可能还有更多我现在想不起来的。

堆上的对象的特点是它们可以引用堆上的其他对象。例如:

var x = {};
x.y = {};
delete x;
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,浏览器根本无法取消分配 的槽x,因为其中包含的值的大小是可变的。它位于堆上,然后可以指向其他对象(在本例中为 处的对象x.y)。另一种可能性是同一个对象有第二个引用:

var x = {};
window.foo = x;
delete x;
Run Code Online (Sandbox Code Playgroud)

浏览器根本无法x从内存中删除该对象,因为仍有其他东西指向该对象。

长话短说,不用担心删除变量,因为它们工作得很好并且完全高性能。当涉及到垃圾收集时,堆分配是真正的敌人,但即使是一些小的堆分配也不会损害大多数应用程序。