JavaScriptオブジェクトを複製するための最も効率的な方法は何ですか? obj = eval(uneval(o));
が使われているのを見たことがありますが、 これは標準的ではなく、Firefoxでしかサポートされていません 。
私はobj = JSON.parse(JSON.stringify(o));
のようなことをしましたが、効率性について質問します。
再帰的なコピー機能にもさまざまな欠陥があります。
標準的な解決策が存在しないことに驚きました。
注: これは別の回答に対する回答であり、この質問に対する正しい回答ではありません。あなたが速いオブジェクトのクローンを作りたいのなら、この質問に 彼らの答えに対するCorbanのアドバイス に従ってください。
jQuery の .clone()
メソッドはDOM要素のみを複製することに注意してください。 JavaScriptオブジェクトを複製するには、次のようにします。
// Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
より詳しい情報は jQuery documentation にあります。
また、ディープコピーは実際には上に示したものよりはるかにスマートであることにも注目したい - それは多くのトラップを避けることができる(例えばDOM要素を深く拡張しようとするなど)。これはjQueryコアやプラグインで頻繁に使用されています。
このベンチマークを確認してください: http://jsben.ch/#/bWfk9
私の以前のテストでは、速度が主な関心事でした
JSON.parse(JSON.stringify(obj))
オブジェクトをディープクローンする最も遅い方法である(10〜20%でdeep
フラグがtrueに設定されている jQuery.extend よりも遅い)。
deep
フラグがfalse
(浅いクローン)に設定されている場合、jQuery.extendは非常に高速です。型の検証のための追加のロジックが含まれており、未定義のプロパティなどをコピーしないため、これは適切なオプションですが、これにより少し遅くなります。
クローンを作成しようとしているオブジェクトの構造がわかっている場合、または深くネストされた配列を回避できる場合は、単純なfor (var i in obj)
ループを記述してhasOwnPropertyをチェックしながらオブジェクトをクローンできます。これはjQueryよりもはるかに高速です。
最後に、ホットループで既知のオブジェクト構造のクローンを作成しようとしている場合、クローンプロシージャをインライン化し、オブジェクトを手動で構築するだけで、さらに多くのパフォーマンスを得ることができます。
JavaScriptトレースエンジンはfor..in
ループを最適化するのが面倒で、hasOwnPropertyをチェックすると速度も低下します。速度が絶対必要な場合の手動クローン。
var clonedObject = {
knownProp: obj.knownProp,
..
}
Date
オブジェクトでJSON.parse(JSON.stringify(obj))
メソッドを使用することに注意してください-JSON.stringify(new Date())
は、JSON.parse()
doesn 'のISO形式で日付の文字列表現を返しますtDate
オブジェクトに変換します。 詳細については、この回答をご覧ください 。
また、少なくともChrome 65では、ネイティブクローニングは使用できません。 このJSPerf によると、新しい関数を作成してネイティブクローニングを実行すると、信じられないほどJSON.stringifyを使用するよりもほぼ800x遅くなります全面的に高速です。
Javascript ES6を使用している場合は、このネイティブ方式のクローン作成またはシャローコピーを試してください。
Object.assign({}, obj);
オブジェクトには変数しかなく、関数はないと仮定すると、単に使用できます。
var newObject = JSON.parse(JSON.stringify(oldObject));
HTML標準には、 内部の構造化クローン作成/直列化アルゴリズム が含まれており、これによってオブジェクトの深いクローンを作成できます。それはまだ特定の組み込み型に制限されていますが、JSONでサポートされているいくつかの型に加えて、日付、正規表現、マップ、セット、BLOB、ファイルリスト、ImageDatas、スパース配列、型付き配列などもサポートします。 。また、クローンデータ内の参照も保持されるため、JSONでエラーが発生する可能性がある循環的および再帰的な構造をサポートできます。
Node.jsのv8
モジュールは現在(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を使用して構造化クローンを作成するためのオーバーヘッドの少ない方法は、 MessageChannels の1つのポートを介してデータをポストすることです。もう一方のポートは、付属の.data
の構造化クローンとともにmessage
イベントを発行します。残念ながら、これらのイベントをリスニングするのは必然的に非同期であり、同期の代替方法は実用的ではありません。
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;
}
Object.assign
メソッドはECMAScript 2015(ES6)規格の一部であり、まさにあなたが必要とすることを行います。
var clone = Object.assign({}, obj);
Object.assign()メソッドは、列挙可能なすべての独自プロパティの値を1つ以上のソースオブジェクトからターゲットオブジェクトにコピーするために使用されます。
polyfill は古いブラウザをサポートします。
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;
}
パフォーマンスによるディープコピー: 最高から最低へのランク付け
文字列または数字の配列をコピーする(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});
私はこれが古い記事であることを知っています、しかし私はこれがつまずく次の人への何らかの助けになるかもしれないと思いました。
オブジェクトを何にも割り当てない限り、それはメモリ内で参照を維持しません。そのため、他のオブジェクトと共有したいオブジェクトを作成するには、次のようにファクトリを作成する必要があります。
var a = function(){
return {
father:'zacharias'
};
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Cloning
ObjectはJSでは常に関心事でしたが、ES6以前のものでした。JavaScriptでオブジェクトをコピーするさまざまな方法を以下にリストします。以下にObjectがあると想像してみてください。
var obj = {a:1, b:2, c:3, d:4};
Originを変更せずにこのオブジェクトをコピーする方法はいくつかあります。
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)AngularJs:
var deepCopyObj = angular.copy(obj);
4)jQuery:
var deepCopyObj = jQuery.extend(true, {}, obj);
5)アンダースコア&ロード:
var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy
これらの助けを願っています...
ライブラリ(「クローン」と呼ばれます) があります。それは私が知っている任意のオブジェクトの最も完全な再帰的クローニング/コピーを提供します。循環参照もサポートしていますが、これは他の回答ではカバーされていません。
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' } }
(免責事項:私は図書館の著者です。)
あなたがそれを使っているなら、 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;
}
この関数はmy simpleoo libraryでも利用できます。
編集:
これはより堅牢なバージョンです(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));
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を使用 link 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.Uders Underscore.js _.clone link 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
LodashにはNice _.cloneDeep(value) methodがあります。
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
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
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
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
あなたが角度を使っているならまああなたもこれを行うことが
var newObject = angular.copy(oldObject);
私は賛成票で答えに同意しません ここ 。 再帰的ディープクローン is はるかに速い 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オブジェクトを失うことなく、parse
メソッドの 第2引数 を使って文字列を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(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;
};
私は通常var newObj = JSON.parse( JSON.stringify(oldObje) );
を使いますが、これはもっと適切な方法です:
var o = {};
var oo = Object.create(o);
(o === oo); // => false
従来のブラウザを見てください。
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;
}
今日のJavaScriptを使ったオブジェクトのクローン作成: ECMAScript 2015(以前のECMAScript 6として知られている)
var original = {a: 1};
// Method 1: New object with original assigned.
var copy1 = Object.assign({}, original);
// Method 2: New object with spread operator assignment.
var copy2 = {...original};
古いブラウザはECMAScript 2015をサポートしていないかもしれません。一般的な解決策はBabelのようなJavaScriptからJavaScriptへのコンパイラを使って ECMAScript 5 のバージョンのJavaScriptコードを出力することです。
@ jim-hallが指摘したように 、 これは単なる浅いコピーです 。プロパティのプロパティは参照としてコピーされます。一方を変更すると、もう一方のオブジェクト/インスタンスの値も変更されます。
単一行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);
私はこの質問に答えるのは遅れていますが、オブジェクトを複製する別の方法があります。
function cloneObject(obj) {
if (obj === null || typeof(obj) !== 'object')
return obj;
var temp = obj.constructor(); // changed
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = cloneObject(obj[key]);
delete obj['isActiveClone'];
}
}
return temp;
}
var b = cloneObject({"a":1,"b":2}); // calling
それよりはるかに良くて速いです:
var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));
そして
var a = {"a":1,"b":2};
// Deep copy
var newObject = jQuery.extend(true, {}, a);
コードをベンチマークしました。結果をテストできます ここ :
そして結果を共有する: 参照: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
Lodashにはそれをあなたのために処理する機能があります。
var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};
var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}}
ドキュメントを読んでください ここ 。
これは私が作成したプロトタイプを使用しない最速の方法なので、新しいオブジェクトでは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の拡張機能を作成しているのでない限り)。
ES 2017の例:
let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
Promise
による非同期オブジェクトのクローン作成はどうですか?
async function clone(thingy /**/)
{
if(thingy instanceof Promise)
{
throw Error("This function cannot clone Promises.");
}
return thingy;
}
JavaScriptでは、
deepCopy
メソッドを次のように書くことができます。
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;
}
これを実現する方法はたくさんありますが、ライブラリを使用せずにこれを実行する場合は、次の方法を使用できます。
const cloneObject = (oldObject) => {
let newObject = oldObject;
if (oldObject && typeof oldObject === 'object') {
if(Array.isArray(oldObject)) {
newObject = [];
} else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {
newObject = new Date(oldObject.getTime());
} else {
newObject = {};
for (let i in oldObject) {
newObject[i] = cloneObject(oldObject[i]);
}
}
}
return newObject;
}
どう考えているか教えてください。
これは私のバージョンのobject clonerです。これはjQueryメソッドのスタンドアロン版で、わずかな調整と調整があります。 フィドル をチェックしてください。 x_xの間は、この関数だけを使うことに気付くまで、たくさんのjQueryを使ってきました。
使い方はjQuery APIに記述されているものと同じです。
extend(object_dest, object_source);
extend(true, object_dest, object_source);
オブジェクトが複製に適しているかどうかを定義するために、1つの追加機能が使用されます。
/**
* This is a quasi clone of jQuery's extend() function.
* by Romain WEEGER for wJs library - www.wexample.com
* @returns {*|{}}
*/
function extend() {
// Make a copy of arguments to avoid JavaScript inspector hints.
var to_add, name, copy_is_array, clone,
// The target object who receive parameters
// form other objects.
target = arguments[0] || {},
// Index of first argument to mix to target.
i = 1,
// Mix target with all function arguments.
length = arguments.length,
// Define if we merge object recursively.
deep = false;
// Handle a deep copy situation.
if (typeof target === 'boolean') {
deep = target;
// Skip the boolean and the target.
target = arguments[ i ] || {};
// Use next object as first added.
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== 'object' && typeof target !== 'function') {
target = {};
}
// Loop trough arguments.
for (false; i < length; i += 1) {
// Only deal with non-null/undefined values
if ((to_add = arguments[ i ]) !== null) {
// Extend the base object.
for (name in to_add) {
// We do not wrap for loop into hasOwnProperty,
// to access to all values of object.
// Prevent never-ending loop.
if (target === to_add[name]) {
continue;
}
// Recurse if we're merging plain objects or arrays.
if (deep && to_add[name] && (is_plain_object(to_add[name]) || (copy_is_array = Array.isArray(to_add[name])))) {
if (copy_is_array) {
copy_is_array = false;
clone = target[name] && Array.isArray(target[name]) ? target[name] : [];
}
else {
clone = target[name] && is_plain_object(target[name]) ? target[name] : {};
}
// Never move original objects, clone them.
target[name] = extend(deep, clone, to_add[name]);
}
// Don't bring in undefined values.
else if (to_add[name] !== undefined) {
target[name] = to_add[name];
}
}
}
}
return target;
}
/**
* Check to see if an object is a plain object
* (created using "{}" or "new Object").
* Forked from jQuery.
* @param obj
* @returns {boolean}
*/
function is_plain_object(obj) {
// Not plain objects:
// - Any object or value whose internal [[Class]] property is not "[object Object]"
// - DOM nodes
// - window
if (obj === null || typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
return false;
}
// Support: Firefox <20
// The try/catch suppresses exceptions thrown when attempting to access
// the "constructor" property of certain Host objects, i.e. |window.location|
// https://bugzilla.mozilla.org/show_bug.cgi?id=814622
try {
if (obj.constructor && !this.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
}
catch (e) {
return false;
}
// If the function hasn't returned already, we're confident that
// |obj| is a plain object, created by {} or constructed with new Object
return true;
}
オブジェクトクローニングアルゴリズムを一般化したい場合は、これが最善の解決策であると思います。
jQueryを使用しても使用しなくても使用できますが、複製したオブジェクトに元のオブジェクトと同じ "クラス"を設定する場合は、jQueryのextendメソッドを省略してください。
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);
}
たくさんの答えがありますが、どれも私が必要としていた望ましい効果をもたらしませんでした。私は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
のデフォルト値とspread演算子を使ったオブジェクトのディープクローンの私のやり方です
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": "[email protected]"
}
},
"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))
プロトタイプ継承に触れることなく、以下のようにオブジェクトと配列を深くすることができます。
function objectClone(o){
var ot = Array.isArray(o);
return o !== null && typeof o === "object" ? Object.keys(o)
.reduce((r,k) => o[k] !== null && typeof o[k] === "object" ? (r[k] = objectClone(o[k]),r)
: (r[k] = o[k],r), ot ? [] : {})
: o;
}
var obj = {a: 1, b: {c: 2, d: {e: 3, f: {g: 4, h: null}}}},
arr = [1,2,[3,4,[5,6,[7]]]],
nil = null,
clobj = objectClone(obj),
clarr = objectClone(arr),
clnil = objectClone(nil);
console.log(clobj, obj === clobj);
console.log(clarr, arr === clarr);
console.log(clnil, nil === clnil);
clarr[2][2][2] = "seven";
console.log(arr, clarr);
浅いコピーのために、ECMAScript 2018規格に導入された素晴らしい簡単な方法があります。これには Spread Operator の使用が含まれます。
let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };
let objClone = { ...obj };
私はChromeブラウザでそれをテストしました、両方のオブジェクトは異なる場所に保存されているので、どちらかで直接の子の値を変更しても他方は変わりません。ただし、(例では)e
の値を変更すると、両方のコピーに影響があります。
このテクニックはとてもシンプルで簡単です。私はこの質問に対する真のベストプラクティスだと思います。
prototype
を取得してinstanceof
をサポートするにはObject.create()
を使用し、列挙キーを取得するには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;
}
}
新しいブラウザが必要ですが...
ネイティブObjectを拡張して、 real / .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"}
2019年に私は使っています:
deepCopy(object) {
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if(typeof value === 'object' && value !== null) {
if(seen.has(value)) return;
seen.add(value);
}
return value;
};
};
return JSON.parse(JSON.stringify(object, getCircularReplacer()));
}
const theCopy = deepCopy(originalObject);
再帰はJavaScriptには高すぎるので、私が見つけた答えのほとんどは再帰を使用していますが、JSONアプローチではJSONに変換できない部分(関数など)はスキップされます。それで私は少し研究をして、それを避けるためにこのトランポリン技術を見つけました。これがコードです:
/*
* Trampoline to avoid recursion in JavaScript, see:
* http://www.integralist.co.uk/posts/js-recursion.html
*/
function trampoline() {
var func = arguments[0];
var args = [];
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
var currentBatch = func.apply(this, args);
var nextBatch = [];
while (currentBatch && currentBatch.length > 0) {
currentBatch.forEach(function(eachFunc) {
var ret = eachFunc();
if (ret && ret.length > 0) {
nextBatch = nextBatch.concat(ret);
}
});
currentBatch = nextBatch;
nextBatch = [];
}
};
/*
* Deep clone an object using the trampoline technique.
*
* @param target {Object} Object to clone
* @return {Object} Cloned object.
*/
function clone(target) {
if (typeof target !== 'object') {
return target;
}
if (target == null || Object.keys(target).length == 0) {
return target;
}
function _clone(b, a) {
var nextBatch = [];
for (var key in b) {
if (typeof b[key] === 'object' && b[key] !== null) {
if (b[key] instanceof Array) {
a[key] = [];
}
else {
a[key] = {};
}
nextBatch.Push(_clone.bind(null, b[key], a[key]));
}
else {
a[key] = b[key];
}
}
return nextBatch;
};
var ret = target instanceof Array ? [] : {};
(trampoline.bind(null, _clone))(target, ret);
return ret;
};
この要旨も参照してください。 https://Gist.github.com/SeanOceanHu/7594cafbfab682f790eb
将来の参考のために、このコードを使うことができます
ES6:
_clone: function(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
ES5:
function clone(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
例えば
var obj ={a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}
xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
Object.assign({},sourceObj)
は、プロパティが参照型のキーを持たない場合にのみオブジェクトを複製します。元
obj={a:"lol",b:["yes","no","maybe"]}
clonedObj = Object.assign({},obj);
clonedObj.b.Push("skip")// changes will reflected to the actual obj as well because of its reference type.
obj.b //will also console => yes,no,maybe,skip
そのため、ディープクローニングはこの方法では実現できません。
最も効果的な解決策は
var obj = Json.stringify(yourSourceObj)
var cloned = Json.parse(obj);
私の経験では、再帰的なバージョンはJSON.parse(JSON.stringify(obj))
よりはるかに優れています。これは近代化された再帰的なディープオブジェクトコピー関数で、一行に収まります。
function deepCopy(obj) {
return Object.keys(obj).reduce((v, d) => Object.assign(v, {
[d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]
}), {});
}
これはJSON.parse...
メソッドよりも 40倍速い を中心にしています。
Javascriptでオブジェクトを複製する方法は3つあります。
1:繰り返しを使用したディープコピー
function iterationCopy(src) {
let target = {};
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
target[prop] = src[prop];
}
}
return target;
}
const source = {a:1, b:2, c:3};
const target = iterationCopy(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1
ご覧のとおり、うまくいっています。
それでは、実際にはもっと洗練されていますが、使用方法が限定されている2番目の解決策を追いかけましょう。
2:JSONへの変換とその逆
function jsonCopy(src) {
return JSON.parse(JSON.stringify(src));
}
const source = {a:1, b:2, c:3};
const target = jsonCopy(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1
注:ソースオブジェクトはJSONセーフでなければならないため、このメソッドの使用には注意してください。そのため、ソースオブジェクトがJSONに変換できない場合に安全に保つために、ある種の例外処理が必要になる場合があります。
3:Object.assignを使う
更新:この方法では、浅いコピーしかできないという欠点があります。つまり、入れ子になったプロパティはまだ参照によってコピーされます。それに注意してください。
この方法は私が個人的に私のプロジェクトで消費する最善かつ最も安全な方法です。これはObjectオブジェクトの組み込み静的メソッドを利用しており、言語によって処理および提供されます。それでこれを使ってください!
function bestCopyEver(src) {
return Object.assign({}, src);
}
const source = {a:1, b:2, c:3};
const target = bestCopyEver(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1
これは再帰による解決策です。
obj = {
a: { b: { c: { d: ['1', '2'] } } },
e: 'Saeid'
}
const Clone = function (obj) {
const container = Array.isArray(obj) ? [] : {}
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if(typeof obj[key] == 'object') {
container[key] = Clone(obj[key])
}
else
container[key] = obj[key].slice()
}
return container
}
console.log(Clone(obj))
もしあなたがこの種のことを普通にやっているのであれば(例えば - 元に戻す/やり直し機能を作る)それは Immutable.js を調べる価値があるかもしれません
const map1 = Immutable.fromJS( { a: 1, b: 2, c: { d: 3 } } );
const map2 = map1.setIn( [ 'c', 'd' ], 50 );
console.log( `${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }` ); // "3 vs 50"
この長い回答リストを見てみると、私が知っているものを除いて、ほとんどすべての解決策が網羅されています。これは、オブジェクトをディープクローンするためのVanilla JSの方法のリストです。
JSON.parse(JSON.stringify(obj));
PushStateまたはreplaceStateを持つhistory.stateを通じて
Web通知APIですが、これにはユーザーに許可を求めることの欠点があります。
各レベルをコピーするためにオブジェクトを通してあなた自身の再帰的なループをすること。
私が見なかった答え - > ServiceWorkersを使う。ページとServiceWorkerスクリプトとの間でやり取りされるメッセージ(オブジェクト)は、あらゆるオブジェクトのディープクローンになります。
この質問はObject.assignやディープクローンへのカスタムコードといった作り付けの機能に関して多くの注意と答えを持っているので、いくつかのライブラリをディープクローンと共有したいと思います
1. esclone
npm install --savedev esclone https://www.npmjs.com/package/esclone
ES6での使用例
import esclone from "esclone";
const rockysGrandFather = {
name: "Rockys grand father",
father: "Don't know :("
};
const rockysFather = {
name: "Rockys Father",
father: rockysGrandFather
};
const rocky = {
name: "Rocky",
father: rockysFather
};
const rockyClone = esclone(rocky);
ES5での使用例
var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)
2.ディープコピー
npm install deep-copy https://www.npmjs.com/package/deep-copy
例:
var dcopy = require('deep-copy')
// deep copy object
var copy = dcopy({a: {b: [{c: 5}]}})
// deep copy array
var copy = dcopy([1, 2, {a: {b: 5}}])
3. clone-deep
$ npm install --save clone-deep https://www.npmjs.com/package/clone-deep
例:
var cloneDeep = require('clone-deep');
var obj = {a: 'b'};
var arr = [obj];
var copy = cloneDeep(arr);
obj.c = 'd';
console.log(copy);
//=> [{a: 'b'}]
console.log(arr);
私のシナリオは少し違っていました。関数だけでなくネストしたオブジェクトを持つオブジェクトがありました。したがって、Object.assign()
とJSON.stringify()
は私の問題の解決策ではありませんでした。サードパーティのライブラリを使用することも私にとって選択肢ではありませんでした。
そのため、リテラルプロパティ、ネストしたオブジェクト、および関数を使用してオブジェクトをコピーするための組み込みメソッドを使用する単純な関数を作成することにしました。
let deepCopy = (target, source) => {
Object.assign(target, source);
// check if there's any nested objects
Object.keys(source).forEach((prop) => {
/**
* assign function copies functions and
* literals (int, strings, etc...)
* except for objects and arrays, so:
*/
if (typeof(source[prop]) === 'object') {
// check if the item is, in fact, an array
if (Array.isArray(source[prop])) {
// clear the copied referenece of nested array
target[prop] = Array();
// iterate array's item and copy over
source[prop].forEach((item, index) => {
// array's items could be objects too!
if (typeof(item) === 'object') {
// clear the copied referenece of nested objects
target[prop][index] = Object();
// and re do the process for nested objects
deepCopy(target[prop][index], item);
} else {
target[prop].Push(item);
}
});
// otherwise, treat it as an object
} else {
// clear the copied referenece of nested objects
target[prop] = Object();
// and re do the process for nested objects
deepCopy(target[prop], source[prop]);
}
}
});
};
これがテストコードです。
let a = {
name: 'Human',
func: () => {
console.log('Hi!');
},
prop: {
age: 21,
info: {
hasShirt: true,
hasHat: false
}
},
mark: [89, 92, { exam: [1, 2, 3] }]
};
let b = Object();
deepCopy(b, a);
a.name = 'Alien';
a.func = () => { console.log('Wassup!'); };
a.prop.age = 1024;
a.prop.info.hasShirt = false;
a.mark[0] = 87;
a.mark[1] = 91;
a.mark[2].exam = [4, 5, 6];
console.log(a); // updated props
console.log(b);
効率性に関連する問題では、これが私が抱えていた問題に対する最も簡単で効率的な解決策であると思います。私はそれをより効率的にすることができるこのアルゴリズムについてのコメントをいただければ幸いです。
あなたのオブジェクトがネストされていて、それがデータオブジェクト、他の構造化オブジェクト、あるいは何らかのプロパティオブジェクトなどを含んでいる場合、JSON.parse(JSON.stringify(object))
やObject.assign({}, obj)
あるいは$.extend(true, {}, obj)
を使ってもうまくいきません。その場合はlodashを使用してください。それはシンプルで簡単です。
var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };
var A = _.cloneDeep(obj);
今、Aは何も参照せずにobjのあなたの新しいクローンになります..
お役に立てれば。
function deepClone(obj) {
/*
* Duplicates an object
*/
var ret = null;
if (obj !== Object(obj)) { // primitive types
return obj;
}
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
ret = obj; // for ex: obj = new String("Spidergap")
} else if (obj instanceof Date) { // date
ret = new obj.constructor();
} else
ret = Object.create(obj.constructor.prototype);
var prop = null;
var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also
var props = {};
for (var i in allProps) {
prop = allProps[i];
props[prop] = false;
}
for (i in obj) {
props[i] = i;
}
//now props contain both enums and non enums
var propDescriptor = null;
var newPropVal = null; // value of the property in new object
for (i in props) {
prop = obj[i];
propDescriptor = Object.getOwnPropertyDescriptor(obj, i);
if (Array.isArray(prop)) { //not backward compatible
prop = prop.slice(); // to copy the array
} else
if (prop instanceof Date == true) {
prop = new prop.constructor();
} else
if (prop instanceof Object == true) {
if (prop instanceof Function == true) { // function
if (!Function.prototype.clone) {
Function.prototype.clone = function() {
var that = this;
var temp = function tmp() {
return that.apply(this, arguments);
};
for (var ky in this) {
temp[ky] = this[ky];
}
return temp;
}
}
prop = prop.clone();
} else // normal object
{
prop = deepClone(prop);
}
}
newPropVal = {
value: prop
};
if (propDescriptor) {
/*
* If property descriptors are there, they must be copied
*/
newPropVal.enumerable = propDescriptor.enumerable;
newPropVal.writable = propDescriptor.writable;
}
if (!ret.hasOwnProperty(i)) // when String or other predefined objects
Object.defineProperty(ret, i, newPropVal); // non enumerable
}
return ret;
}
もしあなたがes6を使っているなら、あなたは単純に拡散演算子を使ってこれをすることができます。
let a = {id:1, name:'sample_name'}
let b = {...a};
JavaScriptでオブジェクトを複製するには、Spread演算子を使用できます。
//オペレータが連結ジョブを実行している
let arr = [1,2,3]; let arr2 = [4,5]; arr = [...arr,...arr2]; console.log(arr);
これは、ライブラリまたはネイティブJavaScript関数を使用しない私のソリューションです。
function deepClone(obj) {
if (typeof obj !== "object") {
return obj;
} else {
let newObj =
typeof obj === "object" && obj.length !== undefined ? [] : {};
for (let key in obj) {
if (key) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
}
のマージはどうですか キー そのオブジェクトの 値?
function deepClone(o) {
var keys = Object.keys(o);
var values = Object.values(o);
var clone = {};
keys.forEach(function(key, i) {
clone[key] = typeof values[i] == 'object' ? Object.create(values[i]) : values[i];
});
return clone;
}
注:この方法は、必ずしも浅いコピーを作成するわけではありませんが、1つの内部オブジェクトの深さでのみコピーしますつまり、{a: {b: {c: null}}}
のようなものが与えられると、その中に直接あるオブジェクトのみが複製されるため、deepClone(a.b).c
は技術的にはa.b.c
への参照ですが、deepClone(a).b
クローンであり、参照ではありません。
新しいメソッドの提案と共に Object.fromEntries() それはいくつかのブラウザの新しいバージョンでサポートされています( reference )。次の再帰的アプローチに貢献したいです。
const obj = {
key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
key2: {key21: "key21", key22: "key22"},
key3: "key3",
key4: [1,2,3, {key: "value"}]
}
const cloneObj = (obj) =>
{
if (Object(obj) !== obj)
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:Lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
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.");
}
は次の方法より遅いので、JSON.parse(JSON.stringify(obj))
の代わりに次の方法を使用してください