1. ホーム
  2. javascript

[解決済み] javascriptでディープクローンを作る方法

2022-03-02 20:11:40

質問

JavaScriptのオブジェクトをディープクローンする方法は?

のようなフレームワークをベースにした様々な関数があることは知っています。 JSON.parse(JSON.stringify(o))$.extend(true, {}, o) が、そんなフレームワークは使いたくありません。

ディープクローンを作成するための最もエレガントで効率的な方法は何ですか。

配列のクローンのようなエッジケースを気にしています。プロトタイプの連鎖を断ち切らないこと、自己参照を扱うこと。

DOMオブジェクトのコピーなどのサポートについては、以下の理由から気にしないことにしています。 .cloneNode はそのために存在するのです。

主にディープクローンを使いたいので node.js V8エンジンのES5の機能を使うのは許容範囲内です。

[編集]をクリックします。

オブジェクトをプロトタイプで継承してコピーを作成するのと クローン です。前者はプロトタイプの連鎖を混乱させます。

[さらに編集]。

回答を読んで、オブジェクト全体のクローン作成は非常に危険で難しいゲームであるという、腹立たしい発見に至りました。例えば、次のようなクロージャベースのオブジェクトを考えてみましょう。

var o = (function() {
     var magic = 42;

     var magicContainer = function() {
          this.get = function() { return magic; };
          this.set = function(i) { magic = i; };
     }

      return new magicContainer;
}());

var n = clone(o); // how to implement clone to support closures

オブジェクトのクローンを作成し、クローン作成時に同じ状態を持ち、かつ o JSパーサーをJSで書かずに。

このような機能は、もう実世界では必要ないはずです。これは単なる学術的な興味に過ぎない。

どのように解決するのか?

何をクローン化したいかは、本当に人それぞれです。これは本当にJSONオブジェクトなのか、それともJavaScriptの任意のオブジェクトなのか。もし、どんなものでもいいからクローンを作りたいのであれば、ちょっと困ったことになるかもしれません。どのようなトラブルですか?以下に説明しますが、まず、オブジェクト リテラル、任意のプリミティブ、配列、DOM ノードのクローンを作成するコード例を示します。

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testing that this is DOM
            if (item.nodeType && typeof item.cloneNode == "function") {
                result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                if (item instanceof Date) {
                    result = new Date(item);
                } else {
                    // it is an object literal
                    result = {};
                    for (var i in item) {
                        result[i] = clone( item[i] );
                    }
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}

var copy = clone({
    one : {
        'one-one' : new String("hello"),
        'one-two' : [
            "one", "two", true, "four"
        ]
    },
    two : document.createElement("div"),
    three : [
        {
            name : "three-one",
            number : new Number("100"),
            obj : new function() {
                this.name = "Object test";
            }   
        }
    ]
})

それでは、リアルなオブジェクトのクローンを作成する際に起こりうる問題について説明します。今話しているのは、次のような方法で作成したオブジェクトのことです。

var User = function(){}
var newuser = new User();

もちろん、オブジェクトのクローンを作ることはできます。すべてのオブジェクトはコンストラクタのプロパティを公開しており、それを使ってオブジェクトのクローンを作ることができますが、常にうまくいくとは限りません。また、単純に for in しかし、それは同じ方向に進みます - トラブルです。コードの中にもクローン機能は含まれているのですが、それは if( false ) ステートメントを使用します。

では、なぜクローンを作るのが面倒なのでしょうか?まず第一に、すべてのオブジェクトやインスタンスは、何らかの状態を持っている可能性があります。例えば、オブジェクトがプライベート変数を持っていないとは限りません。

状態がないと仮定すると、それはそれでいいんです。そうすると、まだ別の問題があります。コンストラクタ経由でクローンを作成すると、別の問題が発生します。それは、引数依存性です。このオブジェクトを作成した人が、ある種のオブジェクトを作成しなかったかどうかを確認することはできません。

new User({
   bike : someBikeInstance
});

someBikeInstance はおそらく何らかのコンテキストで作成されたもので、そのコンテキストは clone メソッドでは未知です。

では、どうすればいいのか?まだ、次のことができます。 for in しかし、そのようなオブジェクトのクローンを作らず、このオブジェクトの参照だけを渡すというのはどうでしょう?

もうひとつの解決策は、クローンする必要のあるすべてのオブジェクトは、この部分を自分で実装し、適切なAPIメソッド(cloneObjectなど)を提供しなければならないという規約を設定することです。例えば cloneNode はDOMのためにやっています。

あなたが決めてください。