JavaScript オブジェクトを正しく複製するにはどうすればよいですか? 質問する

JavaScript オブジェクトを正しく複製するにはどうすればよいですか? 質問する

オブジェクトがあります。 を変更しても が変更されないように、xオブジェクトとしてコピーしたいと思います。組み込みの JavaScript オブジェクトから派生したオブジェクトをコピーすると、不要なプロパティが追加されることがわかりました。これは、自分でリテラル構築したオブジェクトの 1 つをコピーしているので、問題にはなりません。yyx

JavaScript オブジェクトを正しく複製するにはどうすればよいですか?

ベストアンサー1

2022年アップデート

新しいJS標準があります構造化クローニング多くのブラウザで動作します(使ってもいいですか)。

const clone = structuredClone(object);

古い回答

JavaScript で任意のオブジェクトに対してこれを行うのは、単純でも簡単でもない。オブジェクトのプロトタイプから、プロトタイプに残して新しいインスタンスにコピーすべきではない属性を誤って取得するという問題に遭遇するだろう。たとえば、いくつかの回答で示されているように、cloneにメソッドを追加するObject.prototype場合、その属性を明示的にスキップする必要がある。しかし、 に他の追加メソッドが追加されていObject.prototypeたり、知らない他の中間プロトタイプがあったりしたらどうなるだろうか。その場合、コピーすべきでない属性をコピーするため、予期しない非ローカル属性を で検出する必要がある。hasOwnProperty方法。

列挙できない属性に加えて、隠しプロパティを持つオブジェクトをコピーしようとすると、より困難な問題に遭遇します。たとえば、 はprototype関数の隠しプロパティです。また、オブジェクトのプロトタイプは属性 で参照されます__proto__が、これも隠し属性であるため、ソース オブジェクトの属性を反復処理する for/in ループではコピーされません。これは__proto__Firefox の JavaScript インタープリターに固有のものであり、他のブラウザーでは異なる可能性がありますが、概要は理解できます。すべてが列挙可能というわけではありません。名前がわかっていれば隠し属性をコピーできますが、自動的に検出する方法はわかりません。

エレガントなソリューションを求める上でのもう 1 つの障害は、プロトタイプ継承を正しく設定するという問題です。ソース オブジェクトのプロトタイプが である場合Object、 で新しい汎用オブジェクトを作成するだけで{}機能しますが、ソースのプロトタイプが の子孫である場合は、フィルターを使用してスキップしたプロトタイプの追加メンバー、またはプロトタイプにはあったが最初から列挙可能ではなかったメンバーがObject失われます。1 つの解決策は、ソース オブジェクトのプロパティを呼び出して最初のコピー オブジェクトを取得し、属性をコピーすることですが、それでも列挙不可能な属性は取得されません。たとえば、hasOwnPropertyconstructorDateオブジェクトはデータを隠しメンバーとして保存します。

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

の日付文字列は、 の日付文字列d1より 5 秒遅れます。一方を他方と同じにd2する方法は、 メソッドを呼び出すことですが、これはクラスに固有のものです。この問題に対する万能の一般的な解決策はないと思いますが、間違っていたら嬉しいです!DatesetTimeDate

一般的なディープ コピーを実装しなければならなかったとき、単純なObjectArrayDateStringNumber、 のみをコピーする必要があると仮定して妥協することになりましたBoolean。最後の 3 つの型は不変なので、浅いコピーを実行しても変更を心配する必要はありません。さらに、Objectまたは に含まれる要素Arrayも、そのリストの 6 つの単純な型のいずれかであると仮定しました。これは、次のようなコードで実現できます。

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

上記の関数は、オブジェクトと配列内のデータがツリー構造を形成している限り、私が言及した 6 つの単純な型に対して適切に機能します。つまり、オブジェクト内の同じデータへの参照が複数存在しないということです。例:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

あらゆる JavaScript オブジェクトを処理できるわけではありませんが、どんなオブジェクトでも問題なく動作すると想定しない限り、多くの目的には十分かもしれません。

おすすめ記事