原文:http://laurens.vd.oever.nl/weblog/items2005/closures/
著者:laurens at vd dot oever dot nl
翻訳:usj12262 2005-10-30 完訳

Javascript closures can be a powerful programming
technique. Unfortunately in Internet Explorer they
are a common source of memory leaks.
Therefore I propose a method to create closures
that don't leak memory.

Javascriptのクロージャは強力なプログラミングテクニックである。
残念なことに、これはInternet Explorerではメモリリークの主な発生源になっている。
そこでメモリリークしないクロージャを作成する方法を提示する。

Some might argue that this fixes one leak with
another since all closure context objects are
stored in an array. Though this array will be freed
on reload, it will stay in memory as long as the
user stays on the page.

1つのリークを別のリークで置き換えただけだ、という人が
いるかもしれない。すべてのクロージャの文脈にある
オブジェクトが配列に保存されるからだ。
この配列はリロード時には解放されるが、ユーザーがページに
とどまっている間はずっとメモリに居つづける。

A simulation of a highly dynamic webpage shows
that this isn't a big problem in practise.
This shows that an html element takes about 1KB
and even an application like Xopus doesn't create
more than 10000 elements in a single session.
And even if it would, it would only take about
10MB which I think is acceptable.

高度にダイナミックなウェブページのシミュレーションが示すように、
上に挙げたことは実際的には大きな問題にならない。
これは、1つのHTML要素が使用するのは約1KBであり、
Xopusのようなアプリケーションでさえ1度のセッションで
10000以上の要素は生成しないことを示している。
そして、もしそれだけの数を生成したとしても、
約10MBにしかならず、それは許容できると考える。

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

There are a lot of solutions for this problem,
of which most focus on event attaching.
But this problem can also occur when Javascript
objects are set as a property on an HTML element.

多くの解決方法があり、そのほとんどがイベントの関連付けに
着目している。しかし、JavaScriptのオブジェクトがHTML要素の
属性として定義されている場合もこの問題は起きる。

Update: new version with less prerequisites

The above mentioned closure function will only work
if the original function does not have a (indirect)
reference to the object to which the closure is
attached. So this will still leak:

上で言及されたclosure関数は、closureに付加された
オブジェクトへの(間接)参照を元の関数が持たない場合
に限り動作する。そのため以下のコードでは依然として
リークが発生する。

function attach()
{
function clickHandler()
{
alert("Clicked: " + this.innerHTML);
}

var element = document.getElementById("my-element");
element.attachEvent("onclick", clickHandler.closure(element));
}

This is caused by the fact that the created closure
function still has a reference to it's original
function (me). A new version of the closure
function fixes that problem:

これは作成したclosure関数が元の関数(me)を参照
しているからである。 新しいバージョンのclosure関数では
この問題を解決している:

Function.prototype.closure = function(obj)
{
// オブジェクトの保存場所を初期化
if (!window.__objs)
window.__objs = [];

// クロージャの保存場所を初期化
if (!this.__closureFuncs)
this.__closureFuncs = [];

// オブジェクトがidをもち、オブジェクト保存所に保存されていることを確認
var objId = obj.__closureObjId;
if (!objId)
__objs[objId = obj.__closureObjId = __objs.length] = obj;

// このオブジェクト/関数のペアに対してクロージャを作成ずみかチェック
var closureFunc = this.__closureFuncs[objId];
if (closureFunc)
return closureFunc;

// 参照をクリアし、オブジェクトがスコープから外れた状態にする
obj = null;

// クロージャを作成・キャッシュに保存し、結果を返す
var me = this;
return this.__closureFuncs[objId] = function()
{
return me.apply(__objs[objId], arguments);
};
};

Problem

First start with a short explanation of the problem
I tried to fix. Here is an example of a simple event
handler (IE only for clarity):

はじめに、修正しようとしている問題の簡単な説明から始める。
シンプルなイベントハンドラの例を以下に示す(簡単のため、IEのみ動作)。

function attach()
{
var element = document.getElementById("my-element");
element.attachEvent("onclick", function(){ alert("Clicked: " + element.innerHTML); });
}

This seems harmless enough, but the function
(closure) is created in a scope which contains
element. Since we attach the function to element,
a circular reference is created and IE no longer
can garbage collect element. This can easily be
demonstrated by adding a large string to element.

まずいところはなさそうだが、elementを含むスコープで
関数(クロージャ)が生成されている。
関数をelementに関連付けたことで循環参照が発生し、
IEはもはやelementをガベージコレクトできない。
このことはelementに長い文字列を付加することで簡単に示せる。

Solution

So we need a function that can access an HTML
element without creating an inline closure that leaks
memory.

そこで、メモリリークを引き起こすインラインクロージャを
作成することなくHTML要素にアクセスできる関数が必要となる。

The following code adds a closure method to each
function. closure wraps the original function in such
a way that this is set to the given object.

以下のコードは各関数にclosureメソッドを付加している。
closureは、与えられたオブジェクトに元の関数がセット
されるように元の関数をラッピングしている。

Function.prototype.closure = function(obj)
{
// オブジェクトの保存場所を初期化
if (!window.__objs)
{
window.__objs = [];
window.__funs = [];
}

// 比較しやすくするのため
var fun = this;

// オブジェクトがidをもち、オブジェクト保存所に保存されていることを確認
var objId = obj.__objId;
if (!objId)
__objs[objId = obj.__objId = __objs.length] = obj;

// 関数がidをもち、関数保存所に保存されていることを確認
var funId = fun.__funId;
if (!funId)
__funs[funId = fun.__funId = __funs.length] = fun;

// クロージャの保存場所を初期化
if (!obj.__closures)
obj.__closures = [];

// このオブジェクト/関数のペアに対してクロージャを作成ずみかチェック
var closure = obj.__closures[funId];
if (closure)
return closure;

// 参照をクリアし、オブジェクトがスコープから外れた状態にする
obj = null;
fun = null;

// クロージャを作成・キャッシュに保存し、結果を返す
return __objs[objId].__closures[funId] = function ()
{
return __funs[funId].apply(__objs[objId], arguments);
};
};

So now we can do:

よって以下のように:

function attach()
{
var element = document.getElementById("my-element");
element.attachEvent("onclick", clickHandler.closure(element));
}

function clickHandler()
{
alert("Clicked: " + this.innerHTML);
}

We can now use the common pattern of creating
event handlers inline:

これで、インラインでイベントハンドラを作成する
一般的なパターンが使えるようになった。

function attach()
{
var element = document.getElementById("my-element");
element.attachEvent("onclick", function()
{
alert("Clicked: " + this.innerHTML);
}.closure(element));
}

So now we have truly leak free closures.

これで本当にリークフリーなクロージャができた。

In addition we can also easily remove an object
from the global array. The following code allows
the garbage collector to free an object if there are
no other references to it:

さらに、グローバル配列からオブジェクトを削除することも
簡単にできる。以下のコードにより、ガベージコレクタは
参照されていないオブジェクトを解放できる。

window.__objs[obj.__objId] = null;

Which doesn't leak. And can also be used to run
any function in a given context:

リークしないコードが書ける。しかも与えられた文脈で
任意の関数を実行できる。

function myObject()
{
this.status = "waiting";

setTimeout(this.delayedCode.closure(this), 1000);
}

myObject.prototype =
{
delayedCode: function()
{
this.status = "done waiting";
}
};

var o = new myObject();