elem.unbind();超重要

状況

jQueryバリバリのウェブサイトを作っていたら、ページを開くたびに表示速度が遅くなる、という問題が起きた。いろいろ調べてみると、IE6ではイベントを実装するとメモリリークを起こすらしく、あーこれはもう仕方ないなーと思っていたんですよ。

原因

んで、ちょっと時間ができたので、この時のJSの組み方が汚かったということもあり、再コーディングを試みることにして、ついでに具体的にどの部分でメモリリークが発生しているのかを確認してみたところ、どうもロールオーバー時に画像を切り替えるために使っている elem.hover(func, func); を書くか否かで明らかな差が出まして。もちろん循環参照を避けるために、func部分は無名関数にはしてませんよ。とりま、jQueryのhover();ってメモリリーク起こすんだー、と。あーでもそれがライブラリの仕様じゃ、これはもう仕方ないなーと思っていたんですよ。

解決策1

とはいえ、放置してたら再コーディングしている意味が無いので、別案を試そうと。例えばjQueryを使わず、ネイティブのJSで組むとか。しかしこれがやたら面倒くさい。というか、IE6が面倒くさい。まず、IEはaddEventListener();ではなくattachEvent();を使わなければいけない。つまり条件式でIEとそれ以外で分けなければいけない。しかも循環参照を避けるために、無名関数を使わないようにすると、thisに対象の画像のDOMなりなんなりが入ってくれないのですよ。んでarguments[0]を使って、Eventを拾ってきた所、どうもIEではEvent.currentTargetが使えないのでやっぱりthisにあたる何かが拾えないと。

//Blog記述上、それっぽく書き直してるので、このままでは動かないかもねー
if(isIe()){ // isIe();はIE判定用の自作関数
  elem.attachEvent("onmouseover", onmouseoverfunc);
}else{
  elem.addEventListener("mousemover", onmouseoverfunc);
}

var onmouseoverfunc = function(){
  alert(this.src); // output: ie - null, fx - http://.....jpg
  var _event = arguments[0];
  alert(_event.currentTarget); // 確かエラー(覚えてない)
}

いろいろやれる手はあるようだけれでも、IE6の面倒くさい部分を解決するためにIE6のために面倒くさいことをするのが嫌になったので、この方法を諦める。

解決策2

やっぱり放置かなーと思ってたなか、Event Listener: イベントリスナ [JavaScript / DOM]を読んでて、「revemoEventListener();」とか「detachEvent();」とかについて書いてあるのを見て、「そういえばASでも使わないイベント消しておかないと重くなるしなーEvent.ENTERFRAMEとか…JSでもちゃんと消すべきかー。本来ならページ遷移したときに、全部無かったことになるもんなんだけど、IE6はメモリリークしてそれができないみたいだし」と思いたち、jQueryで設定したイベントの削除方法について調べた所、「それ、elem.unbind();でできるよ」という情報を見つけたので試してみることに。しかし、ロールオーバーアクションという性質上、イベントの削除可能タイミングが「別のページへ遷移した時」となるので、windowがunloadイベントを発行した時点でunbindするようにする。

//Blog記述上、それっぽく書き直してるので、このままでは動かないかもねー
$(function(jQuery){
  var hoverimgarr = [];
  $("img").each(hoverfunc);
  var hoverfunc = function(){
    $(this).hover(hoveron, hoveroff);
    hoverimgarr.push($(this));
  }
  var hoveron = function(){...}
  var hoveroff = function(){...}
  var unloader = function(){
    for(var i = 0; i < hoverimgarr.length; i++){
      $(hoverimgarr[i]).unbind("mouseenter").unbind("mouseleave").unbind("mouseover").unbind("mouseout");
      //多分hover();はmouseenterとmouseleaveとを使っているので、後ろ2つはいらない気がするけど、
      //圧縮前のソースコード落として確認してないので適当
    }
  }
  $(window).unload(unloader);  
});
参考

確認

さて、実際これでどうかとIE6にて確認してみたところ、極端な使用メモリ量の増加は見られず、また、時として前回見た際の使用メモリ量を下回ることもあったので、おそらくメモリリークは抑えられていると思われる。unloadにunbind();つけるのを面倒臭がって避けてたけど、やっぱ重要なんだなー