Javascriptの循環参照はエグイ
JavascriptのObject型は簡単に循環参照を作れてしまう。
こういう奴
var data = { a : 'a', b : {c : 'c'} }; data['d'] = data; // 循環参照
こいつは便利な反面、汎用的な処理をしたい時にハマる!
普通にハッシュを再帰で舐める
function crawl_object ( data ) { for ( i in data ){ if ( typeof(data[i]) === 'object') { crawl_object ( data[i] ); // 再帰 }else{ do_anything(i,data[i]); } } }
nodeで実行するとこんな感じ
RangeError: Maximum call stack size exceeded
無限再帰でスタックが尽きた・・・
良く有りそうな処理なのに手段が用意されて無いのは悲しいな。。
仕方ない作ろう
function checked_crawl_object ( data , callback , done ) { // チェックリスト・評価したオブジェクトを入れていく if ( done === undefined ) done = []; for ( d in done ) { if ( done[d] === data ) { // 循環参照・発見! callback(i,data[i],true); // ※コールバック return; } } done.push(data); // チェックリスト更新 // オブジェクトを舐める for ( i in data ){ if ( typeof(data[i]) === 'object') { // オブジェクト型の場合 if ( callback(i,data[i]) ) { // ※コールバック・オブジェクトの中を更に追う場合はtrueをくださいな! checked_crawl_object ( data[i],callback,done ); // 再帰処理 } }else{ // プリミティブ型の場合 callback(i,data[i]); // ※コールバック } } }
使い方はこんな感じ
var data = { a : 'a', b : {c : 'c'} }; checked_crawl_object(data,function(key,value,cyclic){ if ( typeof(data[i]) === 'object') { // 何かしらの処理 return true; // 中を更に追って貰う場合はtrue } // プリミティブ型の処理 });
追記:色々追加したりバグってたりしたんで修正版(nodejs形式)
function crawl_object( data , callback , igns , path , done ) { function is_callback( type ){ for ( var i in igns ){ if ( igns[i] === type ) { return false; } } return true; } if ( igns === undefined ) igns = []; if ( done === undefined ) done = []; if ( path === undefined ) path = []; if ( done.length === 0 ){ callback('',data,path,0); // root object done.push(data); } for ( var i in data ){ path.push(i); var type = typeof(data[i]); if ( type === 'object') { function check_done(){ for ( var no in done ) { if ( done[no] === data[i] ) { if ( is_callback(type)){ callback(i,data[i],path,no,true); } return false; } } return true; } if ( ! check_done() ) { path.pop(i); continue; } done.push(data[i]); var objid = done.length; if ( is_callback(type)){ if ( callback(i,data[i],path,objid) ) { this.crawl_object ( data[i],callback,igns,path ,done ); } }else{ this.crawl_object ( data[i],callback,igns,path ,done ); } }else{ if ( is_callback(type)){ callback(i,data[i],path,undefined); } } path.pop(i); } }
コールバックはこんな感じで用意しておく
function crawl_callback( key , value , path, objid , cyclic ) { if ( value === undefined ) { // undefined型は関数が無いので最初に排除 } else if (value === null ) { // nullはオブジェクト型なのでcyclicの後ろで判定すると面倒 } else if ( cyclic ) { // 既にコールバックした奴と同じオブジェクト。objidで特定できる。 } else if ( objid === undefined ) { // string , function , その他 非object型 var type = typeof(value); if ( value.toString !== undefined ) { }else{ } } else { // 始めて現れたオブジェクトobjidで管理できる } return true; // このvalue(オブジェクト型)の中も追え!=> 他の型やcyclicの場合は無視される }
こんな感じで呼ぶ
// function型は要らんよ crawl_object( target , crawl_callback , ['function'] );