Leak Free Javascript Closures

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.

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):

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.

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.

Solution

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

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.

Function.prototype.closure = function(obj)
{
  // Init object storage.
  if (!window.__objs)
    window.__objs = [];

  // Init closure storage.
  if (!this.__closureFuncs)
    this.__closureFuncs = [];

  // Make sure the object has an id and is stored in the object store.
  var objId = obj.__closureObjId;
  if (!objId)
    __objs[objId = obj.__closureObjId = __objs.length] = obj;

  // See if we previously created a closure for this object/function pair.
  var closureFunc = this.__closureFuncs[objId];
  if (closureFunc)
    return closureFunc;

  // Clear reference to keep the object out of the closure scope.
  obj = null;

  // Create the closure, store in cache and return result.
  var me = this;
  return this.__closureFuncs[objId] = function()
  {
    return me.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);
}
 

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();
 

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.

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.

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:

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:

Function.prototype.closure = function(obj)
{
  // Init object storage.
  if (!window.__objs)
  {
    window.__objs = [];
    window.__funs = [];
  }

  // For symmetry and clarity.
  var fun = this;

  // Make sure the object has an id and is stored in the object store.
  var objId = obj.__objId;
  if (!objId)
    __objs[objId = obj.__objId = __objs.length] = obj;

  // Make sure the function has an id and is stored in the function store.
  var funId = fun.__funId;
  if (!funId)
    __funs[funId = fun.__funId = __funs.length] = fun;

  // Init closure storage.
  if (!obj.__closures)
    obj.__closures = [];

  // See if we previously created a closure for this object/function pair.
  var closure = obj.__closures[funId];
  if (closure)
    return closure;

  // Clear references to keep them out of the closure scope.
  obj = null;
  fun = null;

  // Create the closure, store in cache and return result.
  return __objs[objId].__closures[funId] = function ()
  {
    return __funs[funId].apply(__objs[objId], arguments);
  };
};
 

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;

 

 

[转自:http://laurens.vd.oever.nl/weblog/items2005/closures/]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值