JavaScript でオブジェクトのディープ クローンを作成する最も効率的な方法は何ですか?

StackOverflow https://stackoverflow.com/questions/122102

  •  02-07-2019
  •  | 
  •  

質問

JavaScript オブジェクトのクローンを作成する最も効率的な方法は何ですか?私は見た obj = eval(uneval(o)); 使用されていますが、 これは非標準であり、Firefox でのみサポートされています.

私は次のようなことをしました obj = JSON.parse(JSON.stringify(o)); しかし、効率には疑問があります。

また、再帰コピー関数にはさまざまな欠陥があることも見てきました。
正規の解決策が存在しないことに驚きました。

役に立ちましたか?

解決

2019 年 6 月のメモ: これはもともと別の回答に対する回答であり、この質問に対する適切な回答ではありませんでした。なぜそれが正解として選ばれたのかわかりません。しかし、賛成票は雪だるま式に増加し、この質問に対するダントツの #1 の回答であるため、解決策を Wiki の回答として要約します。

ネイティブディープクローン作成

これは「構造化クローン」と呼ばれるもので、Node 11 以降で実験的に動作し、ブラウザにも導入されることが期待されています。見る この答え 詳細については。

データ損失を伴う高速クローン作成 - JSON.parse/stringify

使用しない場合 Date、関数、 undefined, Infinity, 、RegExp、Map、Set、Blob、FileList、ImageData、sparse Array、Typed Array、またはオブジェクト内のその他の複雑な型の場合、オブジェクトのディープ クローンを作成するための非常に簡単なワンライナーは次のとおりです。

JSON.parse(JSON.stringify(object))

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

見る コーバンの答え ベンチマーク用。

ライブラリを使用した信頼性の高いクローン作成

オブジェクトの複製は簡単ではないため (複合型、循環参照、関数など)、ほとんどの主要なライブラリはオブジェクトを複製するための関数を提供しています。 車輪の再発明はしないでください - すでにライブラリを使用している場合は、オブジェクトの複製機能があるかどうかを確認してください。例えば、

  • ロダッシュ - cloneDeep;経由で個別にインポートできます lodash.clonedeep モジュールであり、ディープ クローン作成機能を提供するライブラリをまだ使用していない場合は、おそらく最良の選択です。
  • 角度 - angular.copy
  • jQuery - jQuery.extend(true, { }, oldObject); .clone() DOM 要素のみをクローンします

ES6

完全を期すために、ES6 は 2 つのシャロー コピー メカニズムを提供していることに注意してください。 Object.assign() そしてその スプレッド演算子.

他のヒント

このベンチマークをチェックしてください: http://jsben.ch/#/bWfk9

速度が主な懸念事項であった以前のテストで、私が発見したのは

JSON.parse(JSON.stringify(obj))

オブジェクトのディープ クローンを作成する最も遅い方法です (これは、 jQuery.extenddeep フラグは 10 ~ 20% で true に設定されます)。

jQuery.extend は、 deep フラグが設定されています false (浅いクローン)。これは、型検証のための追加ロジックが含まれており、未定義のプロパティなどをコピーしないため、良いオプションですが、速度も少し遅くなります。

クローンを作成しようとしているオブジェクトの構造がわかっている場合、または深くネストされた配列を回避できる場合は、単純なコードを作成できます。 for (var i in obj) hasOwnProperty をチェックしながらオブジェクトのクローンを作成するループを実行すると、jQuery よりもはるかに高速になります。

最後に、ホット ループで既知のオブジェクト構造のクローンを作成しようとしている場合は、クローン プロシージャをインライン化し、手動でオブジェクトを構築するだけで、はるかに優れたパフォーマンスを得ることができます。

JavaScript トレース エンジンは最適化が苦手 for..in ループして hasOwnProperty をチェックすると、速度も低下します。速度が絶対的に必要な場合は、手動でクローンを作成します。

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

の使用に注意してください JSON.parse(JSON.stringify(obj)) メソッドオン Date オブジェクト - JSON.stringify(new Date()) ISO 形式で日付の文字列表現を返します。 JSON.parse() しません に変換し直す Date 物体。 詳細については、この回答を参照してください.

さらに、少なくとも Chrome 65 では、ネイティブ クローン作成は使用できないことに注意してください。によると この JSPerf, 、新しい関数を作成してネイティブ クローンを実行するのはほぼ完了です。 800倍 全体的に信じられないほど高速な JSON.stringify を使用するよりも遅くなります。

ES6のアップデート

Javascript ES6 を使用している場合は、クローン作成またはシャロー コピーにこのネイティブ メソッドを試してください。

Object.assign({}, obj);

オブジェクト内に変数のみがあり関数がないと仮定すると、次のように使用できます。

var newObject = JSON.parse(JSON.stringify(oldObject));

構造化されたクローン作成

HTML 標準には以下が含まれます 内部構造化されたクローン作成/シリアル化アルゴリズム オブジェクトのディープ クローンを作成できます。まだ特定の組み込み型に限定されていますが、JSON でサポートされているいくつかの型に加えて、Date、RegExp、Maps、Set、Blob、FileList、ImageData、sparse Array、Typed Array もサポートされており、おそらく将来的にはさらに多くの型もサポートされます。 。また、クローン データ内の参照を保持するため、JSON のエラーの原因となる循環的および再帰的な構造をサポートできるようになります。

Node.js でのサポート:実験的です 🙂

v8 現在 Node.js のモジュール (Node 11 時点) 構造化シリアル化 API を直接公開します, ただし、この機能はまだ「実験的」としてマークされており、将来のバージョンでは変更または削除される可能性があります。互換性のあるバージョンを使用している場合、オブジェクトのクローン作成は次のように簡単です。

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

ブラウザでの直接サポート:もしかしたら最終的には?😐

ブラウザは現在、構造化クローン作成アルゴリズム用の直接インターフェイスを提供していませんが、グローバルな structuredClone() 関数についてはで議論されています GitHub の whatwg/html#793. 。現在提案されているように、ほとんどの目的でこれを使用するのは次のように簡単です。

const clone = structuredClone(original);

これが出荷されない限り、ブラウザの構造化クローン実装は間接的にのみ公開されます。

非同期の回避策:使える。😕

既存の API を使用して構造化クローンを作成するオーバーヘッドの低い方法は、API の 1 つのポートを介してデータをポストすることです。 メッセージチャネル. 。他のポートは message 接続されたイベントの構造化されたクローンを含むイベント .data. 。残念ながら、これらのイベントのリッスンは必然的に非同期であり、同期による代替手段はあまり実用的ではありません。

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

使用例:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

同期の回避策:ひどい!🤢

構造化クローンを同期的に作成するための適切なオプションはありません。代わりに、非実用的なハックをいくつか紹介します。

history.pushState() そして history.replaceState() どちらも最初の引数の構造化クローンを作成し、その値を history.state. 。これを使用して、次のように任意のオブジェクトの構造化されたクローンを作成できます。

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

使用例:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

同期的ではありますが、これは非常に遅くなる可能性があります。ブラウザ履歴の操作に関連するすべてのオーバーヘッドが発生します。このメソッドを繰り返し呼び出すと、Chrome が一時的に応答しなくなる可能性があります。

Notification コンストラクタ 関連するデータの構造化されたクローンを作成します。また、ユーザーにブラウザ通知を表示しようとしますが、通知の許可を要求しない限り、これは通知なしで失敗します。他の目的で許可をお持ちの場合は、作成した通知をすぐに閉じます。

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

使用例:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();

組み込みのものがない場合は、次のことを試すことができます。

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

1 行のコードでオブジェクトをクローン (ディープ クローンではなく) 作成する効率的な方法

アン Object.assign このメソッドは ECMAScript 2015 (ES6) 標準の一部であり、必要なことを正確に実行します。

var clone = Object.assign({}, obj);

Object.assign() メソッドは、列挙可能なすべての独自プロパティの値を 1 つ以上のソース オブジェクトからターゲット オブジェクトにコピーするために使用されます。

続きを読む...

ポリフィル 古いブラウザをサポートするには:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

コード:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

テスト:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

これが私が使用しているものです:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

パフォーマンス別のディープコピー:最高から最低までのランク付け

  • 再割り当て "= (文字列配列、数値配列 - のみ)
  • スライス (文字列配列、数値配列のみ)
  • 連結 (文字列配列、数値配列のみ)
  • カスタム機能:for ループまたは再帰的コピー
  • jQueryの$.extend
  • JSON.parse (文字列配列、数値配列、オブジェクト配列 - のみ)
  • アンダースコア.jsの _.clone (文字列配列、数値配列 - のみ)
  • Lo-Dash の _.cloneDeep

文字列または数値の配列をディープ コピーします (1 レベル - 参照ポインターなし)。

配列に数値と文字列が含まれる場合 - .slice()、.concat()、.splice() などの関数、代入演算子 "=、Underscore.js のクローン関数。配列要素のディープコピーを作成します。

再割り当てのパフォーマンスが最も速い場合:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

また、.slice() は .concat() よりもパフォーマンスが優れています。http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

オブジェクトの配列 (2 つ以上のレベル - 参照ポインター) をディープ コピーします。

var arr1 = [{object:'a'}, {object:'b'}];

カスタム関数を作成します ($.extend() や JSON.parse よりもパフォーマンスが高速です)。

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

サードパーティのユーティリティ関数を使用します。

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

jQuery の $.extend のパフォーマンスが優れている場合:

var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});

あります ライブラリ(「クローン」と呼ばれます), 、これは非常にうまく機能します。これは、私が知る限り、任意のオブジェクトの最も完全な再帰的クローン作成/コピーを提供します。また、他の回答ではまだカバーされていない循環参照もサポートしています。

あなたはできる npmで見つけてください, 、 あまりにも。Node.jsだけでなくブラウザでも利用できます。

これを使用する方法の例を次に示します。

でインストールします

npm install clone

またはパッケージ化してください エンダー.

ender build clone [...]

ソース コードを手動でダウンロードすることもできます。

その後、ソースコードでそれを使用できるようになります。

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(免責事項:私はこのライブラリの著者です。)

これが古い投稿であることは承知していますが、次につまずく人にとっては役立つかもしれないと思いました。

オブジェクトを何かに割り当てない限り、オブジェクトはメモリ内に参照を保持しません。したがって、他のオブジェクト間で共有するオブジェクトを作成するには、次のようなファクトリを作成する必要があります。

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

Cloning JS ではオブジェクトは常に懸念事項でしたが、それはすべて ES6 より前のことでした。以下に JavaScript でオブジェクトをコピーするさまざまな方法をリストします。以下のオブジェクトがあり、そのディープ コピーが必要だと想像してください。

var obj = {a:1, b:2, c:3, d:4};

原点を変更せずにこのオブジェクトをコピーする方法はいくつかあります。

1) ES5+、簡単な関数を使用してコピーを実行します。

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2) ES5+、JSON.parse と JSON.stringify を使用します。

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJ:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs と Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

これらがお役に立てば幸いです...

使用している場合は、 アンダースコア.js 図書館には クローン 方法。

var newObject = _.clone(oldObject);

JavaScript でオブジェクトをディープコピーする (これが最も簡単で最良だと思います)

1.JSON.parse(JSON.stringify(object)); の使用

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.作成したメソッドの利用

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3.Lo-Dash の _.cloneDeep の使用 リンク ロダッシュ

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4.Object.assign() メソッドの使用

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

しかし、間違っている場合

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Underscore.js _.cloneの使用 リンク アンダースコア.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

しかし、間違っている場合

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

参考媒体.com

JSBEN.CH パフォーマンス ベンチマーク プレイグラウンド 1~3 http://jsben.ch/KVQLd Performance Deep copying objects in JavaScript

上記の ConroyP の回答のバージョンは、コンストラクターに必須のパラメーターがある場合でも機能します。

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

この機能は私の端末でも利用できます シンプル 図書館。

編集:

これは、より堅牢なバージョンです (Justin McCandless のおかげで、循環参照もサポートされるようになりました)。

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

次の例では、同じオブジェクトの 2 つのインスタンスを作成します。見つけたので現在使用中です。シンプルで使いやすいです。

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

Lodashには素晴らしいものがあります _.cloneDeep(値) 方法:

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

Crockford は、この関数の使用を提案しています (そして私はそれを好みます)。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var newObject = object(oldObject);

簡潔で期待どおりに動作し、ライブラリは必要ありません。


編集:

これはポリフィルです Object.create, これも使えます。

var newObject = Object.create(oldObject);

注記: これの一部を使用すると、一部の反復で問題が発生する可能性があります。 hasOwnProperty. 。なぜなら、 create 継承する新しい空のオブジェクトを作成します oldObject. 。ただし、オブジェクトのクローンを作成する場合には依然として便利で実用的です。

たとえば、 oldObject.a = 5;

newObject.a; // is 5

しかし:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

浅いコピーのワンライナー (ECMAScript 第 5 版):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

そして、浅いコピーのワンライナー (ECMAScript 第 6 版, 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

見てなかっただけで AngularJS と言及し、人々が知りたいと思うかもしれないと思いました...

angular.copy オブジェクトと配列をディープコピーする方法も提供します。

配列のようなオブジェクトに最適なディープ クローン オペレーターはまだないようです。以下のコードが示すように、John Resig の jQuery クローナーは非数値プロパティを持つ配列を配列ではないオブジェクトに変換し、RegDwight の JSON クローナーは非数値プロパティを削除します。次のテストは、複数のブラウザーでのこれらの点を示しています。

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

目的が「単純な古い JavaScript オブジェクト」のクローンを作成することであるかどうかに応じて、適切な答えが 2 つあります。

また、ソース オブジェクトへのプロトタイプ参照を持たない完全なクローンを作成することが目的であると仮定します。完全なクローンに興味がない場合は、他の回答 (Crockford のパターン) で提供されている Object.clone() ルーチンの多くを使用できます。

単純な古い JavaScript オブジェクトの場合、最新のランタイムでオブジェクトのクローンを作成する実証済みの優れた方法は次のとおりです。

var clone = JSON.parse(JSON.stringify(obj));

ソース オブジェクトは純粋な JSON オブジェクトである必要があることに注意してください。つまり、そのネストされたプロパティはすべてスカラー (ブール値、文字列、配列、オブジェクトなど) でなければなりません。RegExp や Date などの関数や特別なオブジェクトは複製されません。

効率的ですか?はい、そうです。あらゆる種類のクローン作成方法を試しましたが、これが最も効果的です。きっと忍者ならもっと早い方法を思いつくだろう。しかし、私たちは限界利益について話しているのではないかと思います。

このアプローチはシンプルで実装が簡単です。これを便利な関数にラップし、どうしてもゲインを絞り出す必要がある場合は、後で使用してください。

さて、非プレーン JavaScript オブジェクトについては、本当に単純な答えはありません。実際には、JavaScript 関数と内部オブジェクトの状態の動的な性質により、そのようなことはあり得ません。内部に関数を含む JSON 構造のディープ クローンを作成するには、それらの関数とその内部コンテキストを再作成する必要があります。そして、JavaScript にはそれを行うための標準化された方法がありません。

これを行う正しい方法は、繰り返しになりますが、コード内で宣言して再利用する便利なメソッドを使用することです。この便利なメソッドを使用すると、独自のオブジェクトをある程度理解できるため、新しいオブジェクト内でグラフを適切に再作成できるようになります。

私たちは独自に作成しましたが、私が見た中で最良の一般的なアプローチはここでカバーされています。

http://davidwalsh.name/javascript-clone

これは正しい考えです。著者 (David Walsh) は、一般化された関数の複製をコメントアウトしました。これは、ユースケースに応じて選択できます。

主な考え方は、関数 (いわばプロトタイプ クラス) のインスタンス化を型ごとに特別に処理する必要があるということです。ここで、彼は RegExp と Date の例をいくつか示しました。

このコードは短いだけでなく、非常に読みやすいです。拡張するのはかなり簡単です。

これは効率的ですか?はい、そうです。目的が真のディープ コピー クローンを作成することであるとすると、ソース オブジェクト グラフのメンバーをたどる必要があります。このアプローチを使用すると、どの子メンバーを処理するか、およびカスタム型を手動で処理する方法を正確に調整できます。

それでは、どうぞ。2 つのアプローチ。私の見解では、どちらも効率的です。

これは一般に最も効率的な解決策ではありませんが、私が必要なことは行います。以下の簡単なテストケース...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

循環配列テスト...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

機能テスト...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

AngularJS

まあ、angularを使用している場合は、これも行うことができます

var newObject = angular.copy(oldObject);

私は最も多くの票を集めた回答に同意しません ここ. 。あ 再帰的ディープ クローンはるかに高速 よりも JSON.parse(JSON.stringify(obj)) というアプローチが挙げられました。

クイックリファレンス用の関数は次のとおりです。

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

利用したい人にとっては、 JSON.parse(JSON.stringify(obj)) バージョンは異なりますが、Date オブジェクトを失うことなく、 の第 2 引数 parse 方法 文字列を日付に変換するには:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}

これは、任意の JavaScript オブジェクトのクローンを作成できる包括的な clone() メソッドです。ほぼすべてのケースに対応します。

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if (!src && typeof src != "object") {
        // Any non-object (Boolean, String, Number), null, undefined, NaN
        return src;
    }

    // Honor native/custom clone methods
    if (src.clone && toString.call(src.clone) == "[object Function]") {
        return src.clone(deep);
    }

    // DOM elements
    if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
        return src.cloneNode(deep);
    }

    // Date
    if (toString.call(src) == "[object Date]") {
        return new Date(src.getTime());
    }

    // RegExp
    if (toString.call(src) == "[object RegExp]") {
        return new RegExp(src);
    }

    // Function
    if (toString.call(src) == "[object Function]") {

        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });
    }

    var ret, index;
    //Array
    if (toString.call(src) == "[object Array]") {
        //[].slice(0) would soft clone
        ret = src.slice();
        if (deep) {
            index = ret.length;
            while (index--) {
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }
    return ret;
};
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top