JavaScript中无分配循环的模式?
假设我们正在编写一个浏览器应用程序,其中平滑动画至关重要。我们知道垃圾收集会阻塞执行足够长的时间,导致明显的冻结,因此我们需要将创建的垃圾量降至最低。为了最小化垃圾,我们需要在主动画循环运行时避免内存分配 但这条执行路径布满了循环:JavaScript中无分配循环的模式?,javascript,memory-management,garbage-collection,Javascript,Memory Management,Garbage Collection,假设我们正在编写一个浏览器应用程序,其中平滑动画至关重要。我们知道垃圾收集会阻塞执行足够长的时间,导致明显的冻结,因此我们需要将创建的垃圾量降至最低。为了最小化垃圾,我们需要在主动画循环运行时避免内存分配 但这条执行路径布满了循环: var i = things.length; while (i--) { /* stuff */ } for (var i = 0, len = things.length; i < len; i++) { /* stuff */ } 优点:实施起来非常简
var i = things.length; while (i--) { /* stuff */ }
for (var i = 0, len = things.length; i < len; i++) { /* stuff */ }
优点:实施起来非常简单缺点:取消对属性的引用会影响性能,这可能意味着代价高昂的胜利。可能会意外误用/clobber属性并导致错误
2.)如果数组长度已知,则不要循环--展开
我们可以保证一个数组有一定数量的元素。如果我们事先知道长度,我们可以在程序中手动解开循环:
doSomethingWithThing(things[0]);
doSomethingWithThing(things[1]);
doSomethingWithThing(things[2]);
优点:高效缺点:在实践中几乎不可能。丑陋?你想改变吗
3.)通过工厂模式利用闭合
编写一个返回“looper”的工厂函数,该函数对集合的元素执行操作(一个la\uuu.each
)。循环器在创建的闭包中保持对索引和长度变量的私有引用。每次调用活套时,活套必须重置i
和length
function buildLooper() {
var i, length;
return function(collection, functionToPerformOnEach) { /* implement me */ };
}
app.each = buildLooper();
app.each(things, doSomethingWithThing);
优点:更实用、更地道缺点:函数调用会增加开销。闭包访问要比对象查找慢
它们的var语句allocate memory可以分配垃圾收集器可能删除的内存,这是我们想要避免的
这有点误传。仅使用var
不会在堆上分配内存。调用函数时,函数中使用的每个变量都会预先在堆栈上分配。当函数完成执行时,将弹出堆栈帧,并立即取消对内存的引用
与垃圾收集相关的内存问题是在堆上分配对象时出现的。这意味着以下任何一项:
- 闭包
- 事件侦听器
- 阵列
- 物体
typeof foo
返回“函数”
或“对象”
(或任何新的ES6typeof
返回值)的任何内容都将在堆上生成对象。可能还有更多我现在想不起来的
堆上的对象的问题是它们可以引用堆上的其他对象。例如:
var x = {};
x.y = {};
delete x;
在上面的示例中,浏览器无法取消分配x
的插槽,因为其中包含的值大小可变。它位于堆上,然后可以指向其他对象(在本例中,是位于x.y
的对象)。另一种可能性是对同一对象有第二个引用:
var x = {};
window.foo = x;
delete x;
浏览器无法从内存中删除x
处的对象,因为还有其他对象指向它
长话短说,不要担心删除变量,因为它们工作得非常好,而且性能完全好。在垃圾收集方面,堆分配是真正的敌人,但即使是这里或那里的一些小堆分配也不会伤害大多数应用。我非常怀疑你的前提。您认为为什么
var
分配GC关心的内存?即使在一个简单而愚蠢的基于堆栈的字节码虚拟机(如CPython)中,局部变量也是frame对象中的本机数组,在函数运行时从未单独分配或添加/删除。由于JavaScript的闭包、with
和eval
,函数的执行上下文(一种帧对象类型)可以在函数返回后继续存在。必须存活的EC将被分配到堆上,但具体何时/如何发生取决于引擎。(像V8这样的智能引擎将进行优化,为本地变量使用堆栈——只有在上下文变量必须比函数调用更有效时才将其复制到堆中)。如果/当这些堆对象变得不可引用/不可访问时,它们将是GC'd。好吧,但这将是一个根本不调用函数的原因(如果这是真的,即使这样,我也会非常怀疑),而不是一个避免var
的原因。如果没有eval
/with
(可以静态确定,也可以在运行时进行假设和检查),在创建EC时只为EC中的所有局部变量分配空间是很简单的,因此单个var
不需要任何分配。顺便说一句@delman——感谢您对前提的质疑。你促使我做更多的研究。在这之后,在我看来,有足够的因素发挥作用,使这些类型的优化的价值具有争议性。或者,如果一个人注意巧妙地设计功能,这可能是一个没有实际意义的问题。FWIW,我见过像googleclosure这样的库使用函数(a,b,returnValue)
模式来避免分配。这是一个很好的答案。非常感谢你花时间对我的问题给出了彻底的回答。
var x = {};
window.foo = x;
delete x;