LoginSignup
17
15

More than 5 years have passed since last update.

自己参照のオブジェクトや配列をディープコピーする

Posted at

対象

var original = {a: 1};
original.b = original; // 自己参照

既存の方法

いくつか既存の方法を試してみましたが、すべて思い通りにはなりませんでした。
うかつに実行すると例外が発生してしまいます

JSON.parse

var cloned = JSON.parse(JSON.stringify(original));

TypeError: Converting circular structure to JSONが発生

jQuery

var cloned = $.extend(true, {}, original);

RangeError: Maximum call stack size exceededが発生

Coffeescript cookbook

コードはここで

RangeError: Maximum call stack size exceededが発生

underscore

var cloned = _.clone(original);
console.log(cloned.b === original.b); //(true) シャローコピー

今回作成したもの

だれかが既に解決していたら、ただ見つけられなかっただけです。
とりあえず作ってみたので公開します

var clone = (function() {
  function cp (val) {
    if (val === null ||
        val === void 0 ||
        typeof val === 'string' ||
        typeof val === 'number' ||
        typeof val === 'boolean' ||
        typeof val === 'function') {
      return val;
    }

    var constructor = Object.getPrototypeOf(val).constructor;

    if (constructor === RegExp) {
      var att = String(val).slice(val.source.length + 2);
      return new RegExp(val.source, att);
    }

    if (constructor === Date) {
      return new Date(val.getTime());
    }

    var self = this;

    // 自己参照対策
    var idx = self.obj1.indexOf(val);
    if (~idx) {
      return self.obj2[idx];
    }

    var rtn = constructor === Array ? [] : {};
    this.obj1.push(val);
    this.obj2.push(rtn);

    if (constructor === Array) {
      val.forEach(function(x) {
        rtn.push(self.cp(x));
      });

    } else {
      Object.keys(val).forEach(function(x){
        rtn[x] = self.cp(val[x]);
      });

    }
    return rtn;
  }

  function clone (val) {
    var self = {
      cp: cp,
      obj1: [], // 自己参照対策
      obj2: []
    };
    return self.cp(val);
  }

  return clone;
})();

検証

var cloned = clone(original);
console.log(cloned === original); // false
console.log(cloned.b === original.b); // false
console.log(original.b === original); // true
console.log(cloned.b === cloned); // true
console.log(cloned.b.b.b.b.b.a === original.b.b.a); // true

参照はそれぞれ自分自身への循環参照でディープコピーされています。

さいごに

対応しているのは、プリミティブといくつかの基本的なオブジェクトだけ。
DOMとかは対象外です。
関数だけは参照渡しになっているのでそこだけディープコピーじゃないけど、まあいいかと。

最初のプリミティブらへんの解決はかんたんに、
val === null || typeof val !== 'object'
とかでもいいかもしれません

17
15
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15