JavaScript オブジェクトを複製する最も効率的な方法は何ですか? obj = eval(uneval(o));
が使用されているのを見てきましたが、これは非標準であり、Firefox でのみサポートされています。
私は次のようなことをしましたobj = JSON.parse(JSON.stringify(o));
が、効率に疑問があります。
また、さまざまな欠陥のある再帰コピー関数も見てきました。
標準的な解決策が存在しないことに驚いています。
67 に答える
ネイティブディープクローニング
現在、「構造化クローン」と呼ばれるJS標準があります。これは、ノード11以降で実験的に機能し、ブラウザーに組み込まれ、既存のシステム用のポリフィルを備えています。
structuredClone(value)
必要に応じて、最初にポリフィルをロードします。
import structuredClone from '@ungap/structured-clone';
詳細については、この回答を参照してください。
古い答え
データ損失のある高速クローン作成-JSON.parse/stringify
Date
オブジェクト内でs、functions undefined
、、、 RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、sparse Arrays、Typed Arrays、またはその他の複雑な型を使用しない場合Infinity
、オブジェクトをディープクローンするための非常に単純なワンライナーは次のとおりです。
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()
ベンチマークについては、 Corbanの回答を参照してください。
ライブラリを使用した信頼性の高いクローン作成
オブジェクトのクローン作成は簡単ではないため(複雑なタイプ、循環参照、関数など)、ほとんどの主要なライブラリはオブジェクトのクローンを作成するための関数を提供します。車輪の再発明をしないでください。すでにライブラリを使用している場合は、オブジェクトのクローン作成機能があるかどうかを確認してください。例えば、
- lodash-
cloneDeep
; lodash.clonedeepモジュールを介して個別にインポートでき、ディープクローン機能を提供するライブラリをまだ使用していない場合はおそらく最良の選択です。 - AngularJS-
angular.copy
- jQuery-
jQuery.extend(true, { }, oldObject)
;.clone()
DOM要素のみを複製します - ただのライブラリ-
just-clone
; 1つのことだけを行うゼロ依存npmモジュールのライブラリの一部。あらゆる機会に罪悪感のないユーティリティ。
ES6(浅いコピー)
完全を期すために、ES6には2つの浅いコピーメカニズムObject.assign()
とスプレッド構文が用意されていることに注意してください。これは、列挙可能なすべての独自のプロパティの値を1つのオブジェクトから別のオブジェクトにコピーします。例えば:
var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1}; // Spread Syntax
このベンチマークをチェックしてください: http://jsben.ch/#/bWfk9
以前のテストでは、速度が主な関心事であることがわかりました
JSON.parse(JSON.stringify(obj))
オブジェクトをディープ クローンする最も遅い方法です (フラグが trueに設定されたjQuery.extenddeep
よりも10 ~ 20% 遅くなります)。
フラグが(shallow clone)deep
に設定されている場合、jQuery.extend は非常に高速です。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 によると、新しい関数を作成してネイティブ クローンを実行すると、 JSON.stringify を使用するよりも800倍近く遅くなり、全体的に非常に高速です。
Javascript ES6 を使用している場合は、クローン作成または浅いコピーにこのネイティブ メソッドを試してください。
Object.assign({}, obj);
構造化されたクローニング
2022アップデート:グローバルstructuredClone
機能はFirefox 94、Node 17、Deno1.14ですでに利用可能です
HTML標準には、オブジェクトのディープクローンを作成できる内部構造化クローン作成/シリアル化アルゴリズムが含まれています。それでも特定の組み込み型に制限されていますが、JSONでサポートされるいくつかの型に加えて、Dates、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、sparse Arrays、Typed Arrays、およびおそらく将来的にはさらに多くの型もサポートします。また、複製されたデータ内の参照を保持し、JSONのエラーの原因となる循環的および再帰的な構造をサポートできるようにします。
Node.jsでのサポート:
structuredClone
グローバル関数はノード17.0によって提供されます。
const clone = structuredClone(original);
以前のバージョン:v8
Node.jsのモジュール(ノード11以降)は構造化シリアル化APIを直接公開していますが、この機能は引き続き「実験的」としてマークされており、将来のバージョンで変更または削除される可能性があります。互換性のあるバージョンを使用している場合、オブジェクトのクローン作成は次のように簡単です。
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
ブラウザでの直接サポート:Firefox94で利用可能
structuredClone
グローバル関数はまもなくすべての主要なブラウザーで提供されます(GitHubのwhatwg / html#793で以前に説明されています)。それは次のように見えます/次のようになります:
const clone = structuredClone(original);
これが出荷されるまで、ブラウザの構造化クローンの実装は間接的にのみ公開されます。
非同期の回避策:使用可能。
既存のAPIを使用して構造化クローンを作成するオーバーヘッドの少ない方法は、MessageChannelsの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();
オブジェクトにプロパティのみがあり、関数がないと仮定すると、次を使用できます。
var newObject = JSON.parse(JSON.stringify(oldObject));
組み込みのものがない場合は、次を試すことができます。
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;
}
これは私が使用しているものです:
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;
}
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リンクを使用するlodash
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リンクUnderscore.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.)
JSBEN.CH パフォーマンス ベンチマーク プレイグラウンド 1~3 http://jsben.ch/KVQLd
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でパッケージ化します。
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);
使用している場合は、Underscore.jsライブラリにcloneメソッドがあります。
var newObject = _.clone(oldObject);
上記の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;
}
この関数は私のsimpleooライブラリでも利用できます。
編集:
これがより堅牢なバージョンです(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));
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
Lodash には_.cloneDeep(value)メソッドがあります。
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;
}
浅いコピーのワンライナー ( 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
配列のようなオブジェクトに対する理想的なディープ クローン オペレータはまだないようです。以下のコードが示すように、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)
これは一般的に最も効率的なソリューションではありませんが、必要なことは実行します。以下の簡単なテストケース...
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
バージョンを使用したいがJSON.parse(JSON.stringify(obj))
、Date オブジェクトを失うことなく、メソッドの2 番目の引数をparse
使用して、文字列を Date に戻すことができます。
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(obj), function(k, v) {
if (typeof v === 'string' && regExp.test(v))
return new Date(v)
return v;
})
}
// usage:
var original = {
a: [1, null, undefined, 0, {a:null}, new Date()],
b: {
c(){ return 0 }
}
}
var cloned = clone(original)
console.log(cloned)
ECMAScript 6またはtranspilersを使用できる場合のみ。
特徴:
- コピー中にゲッター/セッターをトリガーしません。
- ゲッター/セッターを保持します。
- プロトタイプ情報を保持します。
- オブジェクトリテラルと関数型 オブジェクト指向の両方の記述スタイルで動作します。
コード:
function clone(target, source){
for(let key in source){
// Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
let descriptor = Object.getOwnPropertyDescriptor(source, key);
if(descriptor.value instanceof String){
target[key] = new String(descriptor.value);
}
else if(descriptor.value instanceof Array){
target[key] = clone([], descriptor.value);
}
else if(descriptor.value instanceof Object){
let prototype = Reflect.getPrototypeOf(descriptor.value);
let cloneObject = clone({}, descriptor.value);
Reflect.setPrototypeOf(cloneObject, prototype);
target[key] = cloneObject;
}
else {
Object.defineProperty(target, key, descriptor);
}
}
let prototype = Reflect.getPrototypeOf(source);
Reflect.setPrototypeOf(target, prototype);
return target;
}
ここで最大の票を得た回答には同意しません。Recursive Deep Cloneは、前述のJSON.parse(JSON.stringify(obj))アプローチよりもはるかに高速です。
- Jsperfはここでそれを第 1 位にランク付けします: https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
- 上記の回答のJsbenが更新され、再帰的な深いクローンが言及されている他のすべてのものを打ち負かすことが示されました: http://jsben.ch/13YKQ
クイックリファレンス用の関数は次のとおりです。
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;
};
AngularJS
angularを使用している場合は、これも行うことができます
var newObject = angular.copy(oldObject);
これは、あらゆる 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;
};
deepCopy
JavaScript では、次のようにメソッドを記述できます。
function deepCopy(src) {
let target = Array.isArray(src) ? [] : {};
for (let prop in src) {
let value = src[prop];
if(value && typeof value === 'object') {
target[prop] = deepCopy(value);
} else {
target[prop] = value;
}
}
return target;
}
私の経験では、再帰バージョンは大幅に優れていJSON.parse(JSON.stringify(obj))
ます。以下は、1 行に収まる最新の再帰的ディープ オブジェクト コピー関数です。
function deepCopy(obj) {
return Object.keys(obj).reduce((v, d) => Object.assign(v, {
[d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]
}), {});
}
これは、メソッドよりも約40 倍高速に実行されます。JSON.parse...
単一行の ECMAScript 6 ソリューション (Date/Regex などの特殊なオブジェクト タイプは処理されません):
const clone = (o) =>
typeof o === 'object' && o !== null ? // only clone objects
(Array.isArray(o) ? // if cloning an array
o.map(e => clone(e)) : // clone each of its elements
Object.keys(o).reduce( // otherwise reduce every key in the object
(r, k) => (r[k] = clone(o[k]), r), {} // and save its cloned value into a new object
)
) :
o; // return non-objects as is
var x = {
nested: {
name: 'test'
}
};
var y = clone(x);
console.log(x.nested !== y.nested);
ES 2017 の例:
let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
これは、私が作成したプロトタイプを使用しない最速のメソッドであるため、新しいオブジェクトで hasOwnProperty を維持します。
解決策は、元のオブジェクトの最上位のプロパティを繰り返し、2 つのコピーを作成し、元のオブジェクトから各プロパティを削除してから、元のオブジェクトをリセットして新しいコピーを返すことです。トップレベルのプロパティと同じ回数だけ反復する必要があります。if
これにより、各プロパティが関数、オブジェクト、文字列などであるかどうかを確認するためのすべての条件が保存され、各子孫プロパティを反復する必要がなくなります。
唯一の欠点は、元のオブジェクトをリセットするために、元のオブジェクトに元の作成された名前空間を提供する必要があることです。
copyDeleteAndReset:function(namespace,strObjName){
var obj = namespace[strObjName],
objNew = {},objOrig = {};
for(i in obj){
if(obj.hasOwnProperty(i)){
objNew[i] = objOrig[i] = obj[i];
delete obj[i];
}
}
namespace[strObjName] = objOrig;
return objNew;
}
var namespace = {};
namespace.objOrig = {
'0':{
innerObj:{a:0,b:1,c:2}
}
}
var objNew = copyDeleteAndReset(namespace,'objOrig');
objNew['0'] = 'NEW VALUE';
console.log(objNew['0']) === 'NEW VALUE';
console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};
今後の参考のために、 ECMAScript 6の現在のドラフトでは、オブジェクトを複製する方法としてObject.assignが導入されています。コード例は次のとおりです。
var obj1 = { a: true, b: 1 };
var obj2 = Object.assign(obj1);
console.log(obj2); // { a: true, b: 1 }
これを書いている時点では、サポートはブラウザーの Firefox 34 に限定されているため、まだ製品コードでは使用できません (もちろん、Firefox 拡張機能を作成している場合を除きます)。
多くの答えがありますが、どれも私が必要とする望ましい効果をもたらしませんでした. jQuery のディープ コピーの機能を利用したかったのですが、配列に実行されると、単に配列への参照をコピーし、その中の項目をディープ コピーします。これを回避するために、新しい配列を自動的に作成するちょっとした再帰関数を作成しました。
(必要に応じて kendo.data.ObservableArray もチェックします! ただし、配列を再び監視可能にする場合は、必ず kendo.observable(newItem) を呼び出してください。)
したがって、既存のアイテムを完全にコピーするには、次のようにします。
var newItem = jQuery.extend(true, {}, oldItem);
createNewArrays(newItem);
function createNewArrays(obj) {
for (var prop in obj) {
if ((kendo != null && obj[prop] instanceof kendo.data.ObservableArray) || obj[prop] instanceof Array) {
var copy = [];
$.each(obj[prop], function (i, item) {
var newChild = $.extend(true, {}, item);
createNewArrays(newChild);
copy.push(newChild);
});
obj[prop] = copy;
}
}
}
ES2015
これは、デフォルト値とスプレッド演算子を使用してオブジェクトをディープクローンする私の方法です
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
const testObj = {
"type": "object",
"properties": {
"userId": {
"type": "string",
"chance": "guid"
},
"emailAddr": {
"type": "string",
"chance": {
"email": {
"domain": "fake.com"
}
},
"pattern": ".+@fake.com"
}
},
"required": [
"userId",
"emailAddr"
]
}
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
console.log(makeDeepCopy(testObj))
を使用Object.create()
してprototype
および のサポートを取得しinstanceof
、ループを使用for()
して列挙可能なキーを取得します。
function cloneObject(source) {
var key,value;
var clone = Object.create(source);
for (key in source) {
if (source.hasOwnProperty(key) === true) {
value = source[key];
if (value!==null && typeof value==="object") {
clone[key] = cloneObject(value);
} else {
clone[key] = value;
}
}
}
return clone;
}
class Handler {
static deepCopy (obj) {
if (Object.prototype.toString.call(obj) === '[object Array]') {
const result = [];
for (let i = 0, len = obj.length; i < len; i++) {
result[i] = Handler.deepCopy(obj[i]);
}
return result;
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
const result = {};
for (let prop in obj) {
result[prop] = Handler.deepCopy(obj[prop]);
}
return result;
}
return obj;
}
}
オブジェクトのクローン作成アルゴリズムを一般化する場合、これが最適なソリューションだと思います。
jQuery の有無にかかわらず使用できますが、複製されたオブジェクトに元のオブジェクトと同じ「クラス」を持たせたい場合は、jQuery の extends メソッドを除外することをお勧めします。
function clone(obj){
if(typeof(obj) == 'function')//it's a simple function
return obj;
//of it's not an object (but could be an array...even if in javascript arrays are objects)
if(typeof(obj) != 'object' || obj.constructor.toString().indexOf('Array')!=-1)
if(JSON != undefined)//if we have the JSON obj
try{
return JSON.parse(JSON.stringify(obj));
}catch(err){
return JSON.parse('"'+JSON.stringify(obj)+'"');
}
else
try{
return eval(uneval(obj));
}catch(err){
return eval('"'+uneval(obj)+'"');
}
// I used to rely on jQuery for this, but the "extend" function returns
//an object similar to the one cloned,
//but that was not an instance (instanceof) of the cloned class
/*
if(jQuery != undefined)//if we use the jQuery plugin
return jQuery.extend(true,{},obj);
else//we recursivley clone the object
*/
return (function _clone(obj){
if(obj == null || typeof(obj) != 'object')
return obj;
function temp () {};
temp.prototype = obj;
var F = new temp;
for(var key in obj)
F[key] = clone(obj[key]);
return F;
})(obj);
}
浅いコピーの場合、ECMAScript2018 標準で導入された優れたシンプルな方法があります。Spread Operatorの使用が含まれます。
let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };
let objClone = { ...obj };
Chrome ブラウザーでテストしましたが、両方のオブジェクトが別の場所に保存されているため、いずれかで直接の子の値を変更しても、他方は変更されません。(例では)値を変更すると、e
両方のコピーに影響します。
このテクニックは非常にシンプルで簡単です。これは、この質問に対する真のベスト プラクティスだと思います。
新しいブラウザが必要ですが...
ネイティブ オブジェクトを拡張して、実数 を取得しましょう.extend()
。
Object.defineProperty(Object.prototype, 'extend', {
enumerable: false,
value: function(){
var that = this;
Array.prototype.slice.call(arguments).map(function(source){
var props = Object.getOwnPropertyNames(source),
i = 0, l = props.length,
prop;
for(; i < l; ++i){
prop = props[i];
if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
that[prop] = that[prop].extend(source[prop]);
}else{
Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
}
}
});
return this;
}
});
オブジェクトで .extend() を使用するコードの前にそれをポップするだけです。
例:
var obj1 = {
node1: '1',
node2: '2',
node3: 3
};
var obj2 = {
node1: '4',
node2: 5,
node3: '6'
};
var obj3 = ({}).extend(obj1, obj2);
console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}