この問題は公式に認識されており,v0.3.0で解決したとあるのですが,コードを読む限り正しく実装されていません.
このことについての質問があったため,とりあえず回避コード(パッチ)を作ってみました.なお,詳しい動作検証は行っていませんので,導入は自己責任でお願いいたします.
※ライセンスはSnap.svgと同等としますので,ご自由にお使いください.
問題の本質
SnapメソッドやSnap.elメソッド等で得たElementオブジェクトは変数「hub」において連想配列形式(key-value)としてキャッシュされています.
この仕組みにより,Element.data,Element.click等の整合性が保たれるのですが,Elementオブジェクトをhubキャッシュから削除する仕組みが備わっていません.
v0.3.0ではこの削除処理をタイマーを用いて実装していますが,次の点に於いて更に根深い問題を追加してしまいました.
- DOMにアタッチされていないSVGElementに対応するElementオブジェクトを削除しているため,処理の都合上一時的にDOMから切り離したElementオブジェクトがタイミングによっては無効化されてしまう.
- 再帰的な処理を行っていないため,トップレベルのSVG要素(Paperオブジェクト)が廃棄されても,その子Elementオブジェクトが開放されるとは限らない(未検証)
キャッシュの開放機構を追加する
そこで筆者はv0.3.0でのコードをあえてコメントアウトし,新たにSnap.releaseメソッドを追加しました.
// Hub garbage collector every 10s
でソースコードを検索し,処理をコメントアウトする.- 下記のコードを追加する.
// comment out by DEFGHI1977 2014.07.04
// Hub garbage collector every 10s
/*setInterval(function () {
for (var key in hub) if (hub[has](key)) {
var el = hub[key],
node = el.node;
if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
delete hub[key];
}
}
}, 1e4);*/
Snap.release = function(node){
var key = node.snap;
if(key){
var el = hub[key];
if(el){
delete el.node;
delete hub[key];
}
delete node.snap;
var children = node.childNodes;
for(var i = 0, len = children.length; i<len; i++){
Snap.release(children[i]);
}
}else if(node.node){
Snap.release(node.node);
}
};
NOTE:要するに変数hubがElementオブジェクトへの強参照を持っているため, オブジェクトの開放が上手く行っていないのです. つまりECMAScript6で導入されるWeakMapを使って既存のロジックを弱参照に書き換える方法もあります. (この場合WeakMapをサポートしないIE9/10の扱いが問題となります.)使い方
キャッシュから開放したいオブジェクトをSnap.releaseメソッドに渡します.使用例)
var paper = Snap(200,200);
var circle = paper.circle(100,100,80);
//circleオブジェクトをDOMから切り離し,キャッシュから開放する.
Snap.release(circle.remove());
Snap.releaseメソッドはSVGDOMオブジェクトとSnap.svgオブジェクト間の関連付けを再帰的に開放し,関連するElement(Paper)オブジェクトをキャッシュから削除します.
キャッシュの開放責任をプログラマ側に委ねる事になりますが,余計な処理を行なっていない分制御が容易になります.
0 件のコメント:
コメントを投稿