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中编写循环结构以避免每个都分配内存的好策略是什么?我正在寻找一个通用的解决方案,列出了利弊.
以下是我提出的三个想法:
我们可以声明app.i并app.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的胜利.可能会意外误用/破坏属性并导致错误.
我们可以保证数组具有一定数量的元素.如果我们事先知道长度是多少,我们可以在程序中手动解开循环:
doSomethingWithThing(things[0]);
doSomethingWithThing(things[1]);
doSomethingWithThing(things[2]);
Run Code Online (Sandbox Code Playgroud)
优点:高效.缺点:在实践中很少可能.丑陋?烦人改变?
编写一个返回'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)
优点:更实用,更惯用?缺点:函数调用增加了开销.闭包访问显示比对象查找慢.
他们的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从内存中删除该对象,因为仍有其他东西指向该对象。
长话短说,不用担心删除变量,因为它们工作得很好并且完全高性能。当涉及到垃圾收集时,堆分配是真正的敌人,但即使是一些小的堆分配也不会损害大多数应用程序。