中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

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'] );