如何正确克隆 JavaScript 对象?
Posted
技术标签:
【中文标题】如何正确克隆 JavaScript 对象?【英文标题】:How do I correctly clone a JavaScript object? 【发布时间】:2010-10-18 05:17:24 【问题描述】:我有一个对象x
。我想将它复制为对象y
,这样对y
的更改就不会修改x
。我意识到复制从内置 javascript 对象派生的对象会导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。
【问题讨论】:
看到这个问题:***.com/questions/122102/… 对于 JSON,我使用mObj=JSON.parse(JSON.stringify(jsonObject));
我真的不明白为什么没有人建议Object.create(o)
,它可以满足作者的所有要求?
var x = deep: key: 1 ; var y = Object.create(x); x.deep.key = 2;
执行此操作后,y.deep.key
也将为 2,因此 Object.create 不能用于克隆...
@r3wt 这不起作用...请仅在对解决方案进行基本测试后发布..
【参考方案1】:
function clone(obj)
if(obj == null || typeof(obj) != 'object')
return obj;
var temp = new obj.constructor();
for(var key in obj)
temp[key] = clone(obj[key]);
return temp;
【讨论】:
这个答案非常接近,但并不完全正确。如果您尝试克隆 Date 对象,您将不会获得相同的日期,因为对 Date 构造函数的调用会使用当前日期/时间初始化新 Date。该值不可枚举,不会被 for/in 循环复制。 并不完美,但对于那些基本情况来说很好。例如。允许对可以是基本对象、数组或字符串的参数进行简单克隆。 赞成使用new
正确调用构造函数。接受的答案没有。
适用于其他所有节点!仍然留下参考链接
递归的想法很棒。但是如果值是数组,它会工作吗?【参考方案2】:
2020 年 7 月 6 日更新
在 JavaScript 中有三 (3) 种方法可以克隆对象。由于 JavaScript 中的对象是引用值,因此不能简单地使用 = 进行复制。
方法有:
const food = food: 'apple', drink: 'milk'
// 1. Using the "Spread"
// ------------------
...food
// 2. Using "Object.assign"
// ------------------
Object.assign(, food)
// 3. "JSON"
// ------------------
JSON.parse(JSON.stringify(food))
// RESULT:
// food: 'apple', drink: 'milk'
这可以作为参考总结。
【讨论】:
这为这个问题增加了哪些新的/独特的信息?JSON
方法将删除对象的任何方法
从一个对象创建一个字符串,然后将该字符串解析为另一个对象只是为了复制该对象是一种Monty Python的编程风格:-D
这仅适用于对象字面量和可以这样表示的对象,但不适用于 像您在 OO 语言中遇到的通用“对象”。这似乎是 OP 要求的,因此没关系,但它不是适用于每种对象的通用解决方案。
扩展运算符和 Object.assign 对于具有层次结构的对象失败,即。嵌套对象。 JSON.parse/stringify 有效,但如上所述不复制方法。【参考方案3】:
const objClone = ...obj ;
请注意,嵌套对象仍被复制作为参考。
【讨论】:
感谢嵌套对象仍被复制作为参考的提示!调试我的代码时我几乎发疯了,因为我修改了“克隆”上的嵌套属性,但原来的被修改了。 这是 ES2016,不是 2018,这个答案是 two years earlier。 如果我也想要嵌套属性的副本怎么办 @SunilGarg 要复制嵌套属性,您可以使用const objDeepClone = JSON.parse(JSON.stringify(obj));
【参考方案4】:
结构化克隆
见my answer to a near-duplicate question here。您可以使用 html 标准包含的相同结构化克隆机制在领域之间发送数据。很快您就可以通过新的structuredClone
全局方法来实现这一点。
const clone = structuredClone(original);
更多详情请见the other answer。
【讨论】:
+1 用于给出最终可能以何种形式内置的想法——即使现在无法使用。【参考方案5】:我已经完成了上述所有解决方案,它们都很好。但是,您可以使用另一种方法来克隆对象(使用不引用的值)。 Object.assign
let x =
a: '1',
b: '2'
let y = Object.assign(, x)
y.a = "3"
console.log(x)
输出将是
a: '1', b: '2'
此外,您还可以使用相同的方法克隆数组。
clonedArray = Object.assign([], array)
【讨论】:
【参考方案6】:这会生成您的obj
的新副本(不仅仅是参考)。
let myCopy = JSON.parse(JSON.stringify(obj));
..比_.cloneDeep(obj)
更有效。
【讨论】:
【参考方案7】:(以下主要是@Maciej Bukowski,@A. Levy,@Jan Turoň,@Redu的回答和@LeviRoberts,@RobG的cmets的整合,非常感谢给他们!!!)
深拷贝? - 是的! (大部分);浅拷贝? - 不! (Proxy
除外)。
真诚欢迎大家测试clone()
.
此外,defineProp()
旨在轻松快速地(重新)定义或复制任何类型的描述符。
功能
function clone(object)
/*
Deep copy objects by value rather than by reference,
exception: `Proxy`
*/
const seen = new WeakMap()
return clone(object)
function clone(object)
if (object !== Object(object)) return object /*
—— Check if the object belongs to a primitive data type */
if (object instanceof Node) return object.cloneNode(true) /*
—— Clone DOM trees */
let _object // The clone of object
switch (object.constructor)
case Array:
case Object:
_object = cloneObject(object)
break
case Date:
_object = new Date(+object)
break
case Function:
_object = copyFn(object)
break
case RegExp:
_object = new RegExp(object)
break
default:
switch (Object.prototype.toString.call(object.constructor))
// // Stem from:
case "[object Function]":
switch (object[Symbol.toStringTag])
case undefined:
_object = cloneObject(object) // `class`
break
case "AsyncFunction":
case "GeneratorFunction":
case "AsyncGeneratorFunction":
_object = copyFn(object)
break
default:
_object = object
break
case "[object Undefined]": // `Object.create(null)`
_object = cloneObject(object)
break
default:
_object = object // `Proxy`
return _object
function cloneObject(object)
if (seen.has(object)) return seen.get(object) /*
—— Handle recursive references (circular structures) */
const _object = Array.isArray(object)
? []
: Object.create(Object.getPrototypeOf(object)) /*
—— Assign [[Prototype]] for inheritance */
seen.set(object, _object) /*
—— Make `_object` the associative mirror of `object` */
Reflect.ownKeys(object).forEach(key =>
defineProp(_object, key, value: clone(object[key]) , object)
)
return _object
function copyPropDescs(target, source)
Object.defineProperties(target,
Object.getOwnPropertyDescriptors(source)
)
function convertFnToStr(fn)
let fnStr = String(fn)
if (fn.name.startsWith("[")) // isSymbolKey
fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
fnStr = /^(?!(async )?(function\b|[^]+?=>))[^(]+?\(/.test(fnStr)
? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
return fnStr
function copyFn(fn)
const newFn = new Function(`return $convertFnToStr(fn)`)()
copyPropDescs(newFn, fn)
return newFn
function defineProp(object, key, descriptor = , copyFrom = )
const configurable: _configurable, writable: _writable
= Object.getOwnPropertyDescriptor(object, key)
|| configurable: true, writable: true
const test = _configurable // Can redefine property
&& (_writable === undefined || _writable) // Can assign to property
if (!test || arguments.length <= 2) return test
const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
|| configurable: true, writable: true // Custom…
|| ; // …or left to native default settings
["get", "set", "value", "writable", "enumerable", "configurable"]
.forEach(attr =>
descriptor[attr] === undefined &&
(descriptor[attr] = basisDesc[attr])
)
const get, set, value, writable, enumerable, configurable
= descriptor
return Object.defineProperty(object, key,
enumerable, configurable, ...get || set
? get, set // Accessor descriptor
: value, writable // Data descriptor
)
// 测试
const obj0 =
u: undefined,
nul: null,
t: true,
num: 9,
str: "",
sym: Symbol("symbol"),
[Symbol("e")]: Math.E,
arr: [[0], [1, 2]],
d: new Date(),
re: /f/g,
get g() return 0 ,
o:
n: 0,
o: f: function (...args)
,
f:
getAccessorStr(object)
return []
.concat(...
Object.values(Object.getOwnPropertyDescriptors(object))
.filter(desc => desc.writable === undefined)
.map(desc => Object.values(desc))
)
.filter(prop => typeof prop === "function")
.map(String)
,
f0: function f0() ,
f1: function () ,
f2: a => a / (a + 1),
f3: () => 0,
f4(params) return param => param + params ,
f5: (a, b) => ( c = 0 = ) => a + b + c
defineProp(obj0, "s", set(v) this._s = v )
defineProp(obj0.arr, "tint", value: is: "non-enumerable" )
obj0.arr[0].name = "nested array"
let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) return a + b
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", set(v) this._s = v + 1 )
defineProp(obj1.re, "multiline", value: true )
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Routinely")
console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()
console.log("obj0\n ",
".arr.tint:", obj0.arr.tint, "\n ",
".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
".arr.tint:", obj1.arr.tint, "\n ",
".arr[0].name:", obj1.arr[0].name
)
console.log()
console.log("Accessor-type descriptor\n ",
"of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
"of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
"set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
" → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)
console.log("—— obj0 has not been interfered.")
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - More kinds of functions")
const fnsForTest =
f(_) return _ ,
func: _ => _,
aFunc: async _ => _,
async function() ,
async asyncFunc() ,
aFn: async function () ,
*gen() ,
async *asyncGen() ,
aG1: async function* () ,
aG2: async function* gen() ,
*[Symbol.iterator]() yield* Object.keys(this)
console.log(Reflect.ownKeys(fnsForTest).map(k =>
`$String(k):
$fnsForTest[k].name-->
$String(fnsForTest[k])`
).join("\n"))
const normedFnsStr = `
f: function f(_) return _ ,
func: _ => _,
aFunc: async _ => _,
function: async function() ,
asyncFunc: async function asyncFunc() ,
aFn: async function () ,
gen: function* gen() ,
asyncGen: async function* asyncGen() ,
aG1: async function* () ,
aG2: async function* gen() ,
[Symbol.iterator]: function* () yield* Object.keys(this)
`
const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`($normedFnsStr)`))
console.log("Comparison of fnsForTest and its clone:",
Reflect.ownKeys(fnsForTest).map(k =>
[k, fnsForTest[k] === copiedFnsForTest[k]]
)
)
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Circular structures")
obj0.o.r =
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr
obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log("Clear obj0's recursion:",
obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
"obj0\n ",
".o.r:", obj0.o.r, "\n ",
".arr:", obj0.arr
)
console.log(
"obj1\n ",
".o.r:", obj1.o.r, "\n ",
".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")
console.log("\n\n" + "-".repeat(2 ** 6))
console.log(">:>: Test - Classes")
class Person
constructor(name)
this.name = name
class Boy extends Person
Boy.prototype.sex = "M"
const boy0 = new Boy
boy0.hobby = sport: "spaceflight"
const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"
boy0.name = "one"
boy1.name = "neo"
console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)
参考文献
Object.create()
| MDN
Object.defineProperties()
| MDN
Enumerability and ownership of properties | MDN
TypeError: cyclic object value | MDN
使用的语言技巧
-
Conditionally add prop to object
【讨论】:
既然Symbol("a") === Symbol("a")
是false
,那么clone(Symbol("a"))
不应该使用Symbol(object.description)
创建一个新符号吗?或者这会对众所周知的符号产生太奇怪的影响?【参考方案8】:
你可以使用rest操作符来克隆数组或对象
let myObj = 1: 100, 'a': 200;
let clone = ...myObj;
clone.a = 300;
console.log(clone.a) // Output :- 300
console.log(myObj.a) // Output :- 200
【讨论】:
请注意,这只会执行“浅克隆”,developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…【参考方案9】:在 JavaScript 中复制对象的方法
-
使用展开 (
...
) 语法
使用Object.assign()
方法
使用JSON.stringify()
和JSON.parse()
方法
const person =
firstName: 'John',
lastName: 'Doe'
;
// using spread ...
let p1 =
...person
;
// using Object.assign() method
let p2 = Object.assign(, person);
// using JSON
let p3 = JSON.parse(JSON.stringify(person));
【讨论】:
扩展语法是语法糖(在这种情况下是 Object.assign)。这个答案具有误导性。 用于快速测试 Object.assign(, person);这是完美的【参考方案10】:最正确的复制对象是使用Object.create
:
Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
这样的表示法将使相同的对象具有正确的原型和隐藏属性。
【讨论】:
是的,但取决于你想要什么(你可能想要简单地“复制”道具 values,not 实际的道具描述符参考...... ),并且根据源 obj,您可能还需要在此之上添加一个Object.assign
才能获得可枚举的属性(即在对象上设置的普通旧键值对,与原型和“动态”描述的道具无关。
【参考方案11】:
所以只是添加一些简单的东西,如果你想创建一个灯箱或类似的东西,上面的大多数答案都是不必要的复杂,简单地复制源属性和其他任何必要的东西更容易。
【讨论】:
听起来你也只需要 Object.assign :)【参考方案12】:在 ECMAScript 6 中有 Object.assign 方法,它将所有可枚举自身属性的值从一个对象复制到另一个对象。例如:
var x = myProp: "value";
var y = Object.assign(, x);
但请注意这是一个浅拷贝 - 嵌套对象仍被复制为引用。
【讨论】:
【参考方案13】:性能
今天 2020.04.30 我在 MacOs High Sierra v10.13.6 上对 Chrome v81.0、Safari v13.1 和 Firefox v75.0 上的所选解决方案进行了测试。
我专注于复制 DATA 的速度(具有简单类型字段的对象,而不是方法等)。解 A-I 只能做浅拷贝,解 J-U 可以做深拷贝。
浅拷贝的结果
solution...obj
(A) 在 chrome 和 firefox 上最快,在 safari 上中等速度
基于Object.assign
(B) 的解决方案在所有浏览器上都很快
jQuery (E) 和 lodash (F,G,H) 解决方案中等/相当快
解决方案JSON.parse/stringify
(K) 很慢
解决方案 D 和 U 在所有浏览器上都很慢
深拷贝的结果
解决方案 Q 在所有浏览器上都是最快的 jQuery (L) 和 lodash (J) 中等速度 解决方案JSON.parse/stringify
(K) 很慢
解决方案 U 在所有浏览器上最慢
lodash (J) 和解决方案 U 在 Chrome 上崩溃 1000 级深度对象
详情
对于选择的解决方案: A B C(我的) D E F G H I J K L M N O P Q R S T U, 我进行了 4 次测试
浅小:具有 10 个非嵌套字段的对象 - 你可以运行它HERE 浅大:具有 1000 个非嵌套字段的对象 - 你可以运行它HERE deep-small:具有 10 个级别嵌套字段的对象 - 您可以运行它HERE deep-big: 具有 1000 个级别嵌套字段的对象 - 你可以运行它HERE测试中使用的对象如下所示sn-p
let obj_ShallowSmall =
field0: false,
field1: true,
field2: 1,
field3: 0,
field4: null,
field5: [],
field6: ,
field7: "text7",
field8: "text8",
let obj_DeepSmall =
level0:
level1:
level2:
level3:
level4:
level5:
level6:
level7:
level8:
level9: [[[[[[[[[['abc']]]]]]]]]],
,
;
let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,);
let obj_DeepBig = genDeepObject(1000);
// ------------------
// Show objects
// ------------------
console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall));
console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall));
console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig));
console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig));
// ------------------
// HELPERS
// ------------------
function getField(k)
let i=k%10;
if(i==0) return false;
if(i==1) return true;
if(i==2) return k;
if(i==3) return 0;
if(i==4) return null;
if(i==5) return [];
if(i==6) return ;
if(i>=7) return "text"+k;
function genDeepObject(N)
// generate: level0:level1:...levelN: end:[[[...N-times...['abc']...]]] ...
let obj=;
let o=obj;
let arr = [];
let a=arr;
for(let i=0; i<N; i++)
o['level'+i]=;
o=o['level'+i];
let aa=[];
a.push(aa);
a=aa;
a[0]='abc';
o['end']=arr;
return obj;
下面的 sn-p 展示了经过测试的解决方案并显示了它们之间的差异
function A(obj)
return ...obj
function B(obj)
return Object.assign(, obj);
function C(obj)
return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), )
function D(obj)
let copyOfObject = ;
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));
return copyOfObject;
function E(obj)
return jQuery.extend(, obj) // shallow
function F(obj)
return _.clone(obj);
function G(obj)
return _.clone(obj,true);
function H(obj)
return _.extend(,obj);
function I(obj)
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj)
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
return copy;
function J(obj)
return _.cloneDeep(obj,true);
function K(obj)
return JSON.parse(JSON.stringify(obj));
function L(obj)
return jQuery.extend(true, , obj) // deep
function M(obj)
if(obj == null || typeof(obj) != 'object')
return obj;
var temp = new obj.constructor();
for(var key in obj)
temp[key] = M(obj[key]);
return temp;
function N(obj)
let EClone = function(obj)
var newObj = (obj instanceof Array) ? [] : ;
for (var i in obj)
if (i == 'EClone') continue;
if (obj[i] && typeof obj[i] == "object")
newObj[i] = EClone(obj[i]);
else newObj[i] = obj[i]
return newObj;
;
return EClone(obj);
;
function O(obj)
if (obj == null || typeof obj != "object") return obj;
if (obj.constructor != Object && obj.constructor != Array) return obj;
if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
return new obj.constructor(obj);
let to = new obj.constructor();
for (var name in obj)
to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name];
return to;
function P(obj)
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;
return clone(,obj);
function Q(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] = Q(obj[i]);
return copy;
// Handle Object
if (obj instanceof Object)
copy = ;
for (var attr in obj)
if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]);
return copy;
throw new Error("Unable to copy obj! Its type isn't supported.");
function R(obj)
const gdcc = "__getDeepCircularCopy__";
if (obj !== Object(obj))
return obj; // primitive value
var set = gdcc in obj,
cache = obj[gdcc],
result;
if (set && typeof cache == "function")
return cache();
// else
obj[gdcc] = function() return result; ; // overwrite
if (obj instanceof Array)
result = [];
for (var i=0; i<obj.length; i++)
result[i] = R(obj[i]);
else
result = ;
for (var prop in obj)
if (prop != gdcc)
result[prop] = R(obj[prop]);
else if (set)
result[prop] = R(cache);
if (set)
obj[gdcc] = cache; // reset
else
delete obj[gdcc]; // unset again
return result;
function S(obj)
const cache = new WeakMap(); // Map of old - new references
function copy(object)
if (typeof object !== 'object' ||
object === null ||
object instanceof HTMLElement
)
return object; // primitive value or HTMLElement
if (object instanceof Date)
return new Date().setTime(object.getTime());
if (object instanceof RegExp)
return new RegExp(object.source, object.flags);
if (cache.has(object))
return cache.get(object);
const result = object instanceof Array ? [] : ;
cache.set(object, result); // store reference to object before the recursive starts
if (object instanceof Array)
for(const o of object)
result.push(copy(o));
return result;
const keys = Object.keys(object);
for (const key of keys)
result[key] = copy(object[key]);
return result;
return copy(obj);
function T(obj)
var clonedObjectsArray = [];
var originalObjectsArray = []; //used to remove the unique ids when finished
var next_objid = 0;
function objectId(obj)
if (obj == null) return null;
if (obj.__obj_id == undefined)
obj.__obj_id = next_objid++;
originalObjectsArray[obj.__obj_id] = obj;
return obj.__obj_id;
function cloneRecursive(obj)
if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;
// Handle Date
if (obj instanceof Date)
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
// Handle Array
if (obj instanceof Array)
var copy = [];
for (var i = 0; i < obj.length; ++i)
copy[i] = cloneRecursive(obj[i]);
return copy;
// Handle Object
if (obj instanceof Object)
if (clonedObjectsArray[objectId(obj)] != undefined)
return clonedObjectsArray[objectId(obj)];
var copy;
if (obj instanceof Function)//Handle Function
copy = function()return obj.apply(this, arguments);;
else
copy = ;
clonedObjectsArray[objectId(obj)] = copy;
for (var attr in obj)
if (attr != "__obj_id" && obj.hasOwnProperty(attr))
copy[attr] = cloneRecursive(obj[attr]);
return copy;
throw new Error("Unable to copy obj! Its type isn't supported.");
var cloneObj = cloneRecursive(obj);
//remove the unique ids
for (var i = 0; i < originalObjectsArray.length; i++)
delete originalObjectsArray[i].__obj_id;
;
return cloneObj;
function U(obj)
/*
Deep copy objects by value rather than by reference,
exception: `Proxy`
*/
const seen = new WeakMap()
return clone(obj)
function defineProp(object, key, descriptor = , copyFrom = )
const configurable: _configurable, writable: _writable
= Object.getOwnPropertyDescriptor(object, key)
|| configurable: true, writable: true
const test = _configurable // Can redefine property
&& (_writable === undefined || _writable) // Can assign to property
if (!test || arguments.length <= 2) return test
const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
|| configurable: true, writable: true // Custom…
|| ; // …or left to native default settings
["get", "set", "value", "writable", "enumerable", "configurable"]
.forEach(attr =>
descriptor[attr] === undefined &&
(descriptor[attr] = basisDesc[attr])
)
const get, set, value, writable, enumerable, configurable
= descriptor
return Object.defineProperty(object, key,
enumerable, configurable, ...get || set
? get, set // Accessor descriptor
: value, writable // Data descriptor
)
function clone(object)
if (object !== Object(object)) return object /*
—— Check if the object belongs to a primitive data type */
if (object instanceof Node) return object.cloneNode(true) /*
—— Clone DOM trees */
let _object // The clone of object
switch (object.constructor)
case Array:
case Object:
_object = cloneObject(object)
break
case Date:
_object = new Date(+object)
break
case Function:
const fnStr = String(object)
_object = new Function("return " +
(/^(?!function |[^]+?=>)[^(]+?\(/.test(fnStr)
? "function " : ""
) + fnStr
)()
copyPropDescs(_object, object)
break
case RegExp:
_object = new RegExp(object)
break
default:
switch (Object.prototype.toString.call(object.constructor))
// // Stem from:
case "[object Function]": // `class`
case "[object Undefined]": // `Object.create(null)`
_object = cloneObject(object)
break
default: // `Proxy`
_object = object
return _object
function cloneObject(object)
if (seen.has(object)) return seen.get(object) /*
—— Handle recursive references (circular structures) */
const _object = Array.isArray(object)
? []
: Object.create(Object.getPrototypeOf(object)) /*
—— Assign [[Prototype]] for inheritance */
seen.set(object, _object) /*
—— Make `_object` the associative mirror of `object` */
Reflect.ownKeys(object).forEach(key =>
defineProp(_object, key, value: clone(object[key]) , object)
)
return _object
function copyPropDescs(target, source)
Object.defineProperties(target,
Object.getOwnPropertyDescriptors(source)
)
// ------------------------
// Test properties
// ------------------------
console.log(` shallow deep func circ undefined date RegExp bigInt`)
log(A);
log(B);
log(C);
log(D);
log(E);
log(F);
log(G);
log(H);
log(I);
log(J);
log(K);
log(L);
log(M);
log(N);
log(O);
log(P);
log(Q);
log(R);
log(S);
log(T);
log(U);
console.log(` shallow deep func circ undefined date RegExp bigInt
----
LEGEND:
shallow - solution create shallow copy
deep - solution create deep copy
func - solution copy functions
circ - solution can copy object with circular references
undefined - solution copy fields with undefined value
date - solution can copy date
RegExp - solution can copy fields with regular expressions
bigInt - solution can copy BigInt
`)
// ------------------------
// Helper functions
// ------------------------
function deepCompare(obj1,obj2)
return JSON.stringify(obj1)===JSON.stringify(obj2);
function getCase() // pure data case
return
undef: undefined,
bool: true, num: 1, str: "txt1",
e1: null, e2: [], e3: , e4: 0, e5: false,
arr: [ false, 2, "txt3", null, [], ,
[ true,4,"txt5",null, [], , [true,6,"txt7",null,[], ],
bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3: ,e4: 0, e5: false
],
bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3: ,e4: 0, e5: false
],
obj:
bool: true, num: 12, str: "txt13",
e1: null, e2: [], e3: , e4: 0, e5: false,
arr: [true,14,"txt15",null,[], ],
obj:
bool: true, num: 16, str: "txt17",
e1: null, e2: [], e3: , e4: 0, e5: false,
arr: [true,18,"txt19",null,[], ],
obj: bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3: ,e4: 0, e5: false
;
function check(org, copy, field, newValue)
copy[field] = newValue;
return deepCompare(org,copy);
function testFunc(f)
let o = a:1, fun: (i,j)=> i+j ;
let c = f(o);
let val = false
try
val = c.fun(3,4)==7;
catch(e)
return val;
function testCirc(f)
function Circ()
this.me = this;
var o =
x: 'a',
circ: new Circ(),
obj_circ: null,
;
o.obj_circ = o;
let val = false;
try
let c = f(o);
val = (o.obj_circ == o) && (o.circ == o.circ.me);
catch(e)
return val;
function testRegExp(f)
let o =
re: /a[0-9]+/,
;
let val = false;
try
let c = f(o);
val = (String(c.re) == String(/a[0-9]+/));
catch(e)
return val;
function testDate(f)
let o =
date: new Date(),
;
let val = false;
try
let c = f(o);
val = (+new Date(c.date) == +new Date(o.date));
catch(e)
return val;
function testBigInt(f)
let val = false;
try
let o =
big: 123n,
;
let c = f(o);
val = o.big == c.big;
catch(e)
return val;
function log(f)
let o = getCase(); // orginal object
let oB = getCase(); // "backup" used for shallow valid test
let c1 = f(o); // copy 1 for reference
let c2 = f(o); // copy 2 for test shallow values
let c3 = f(o); // copy 3 for test deep values
let is_proper_copy = deepCompare(c1,o); // shoud be true
// shallow changes
let testShallow =
[ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',] ]
.reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );
// should be true (original object shoud not have changed shallow fields)
let is_valid = deepCompare(o,oB);
// deep test (intruduce some change)
if (c3.arr[6]) c3.arr[6][7].num = 777;
let diff_shallow = !testShallow; // shoud be true (shallow field was copied)
let diff_deep = !deepCompare(c1,c3); // shoud be true (deep field was copied)
let can_copy_functions = testFunc(f);
let can_copy_circular = testCirc(f);
let can_copy_regexp = testRegExp(f);
let can_copy_date = testDate(f);
let can_copy_bigInt = testBigInt(f);
let has_undefined = 'undef' in c1; // field with undefined value is copied?
let is_ok = is_valid && is_proper_copy;
let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string
testFunc(f);
if(is_ok)
console.log(`$f.name $b(diff_shallow) $b(diff_deep) $b(can_copy_functions) $b(can_copy_circular) $b(has_undefined) $b(can_copy_date) $b(can_copy_regexp) $b(can_copy_bigInt)`)
else
console.log(`$f.name: INVALID $is_valid $is_proper_copy`,c1)
<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>
This snippet only presents tested solutions and show differences between them (but it no make performence tests)
下面是 Chrome 用于浅大对象的示例结果
【讨论】:
【参考方案14】:正如this link 所说,使用此代码:
let clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
【讨论】:
在这里回答问题很好,但这是一个老问题,有很多很好的答案。任何新答案都应为该主题添加重要的新信息和新见解。您在这里的回答非常简短,没有任何解释,指向异地页面的链接很有用,但如果链接失败,答案应该是独立的并且有足够的详细信息。 这个和Object.assign(newObj, obj)
一模一样,是浅拷贝【参考方案15】:
来自这篇文章:How to copy arrays and objects in Javascript by Brian Huisman:
Object.prototype.clone = function()
var newObj = (this instanceof Array) ? [] : ;
for (var i in this)
if (i == 'clone') continue;
if (this[i] && typeof this[i] == "object")
newObj[i] = this[i].clone();
else newObj[i] = this[i]
return newObj;
;
【讨论】:
这很接近,但不适用于任何对象。尝试用这个克隆一个 Date 对象。并非所有属性都是可枚举的,因此它们不会全部显示在 for/in 循环中。 像这样添加到对象原型对我来说破坏了 jQuery。即使我重命名为 clone2。 @iPadDeveloper2011 上面的代码有一个错误,它创建了一个名为“i”“(for i in this)”的全局变量,而不是“(for var i in this)”。我有足够的业力来编辑它并修复它,所以我做到了。 @Calvin: 这应该被创建一个不可枚举的属性,否则 'clone' 将出现在 'for' 循环中。 为什么var copiedObj = Object.create(obj);
不是一个好方法?【参考方案16】:
许多同行针对 deep_cloning 提出的解决方案JSON.parse(JSON.stringify(orig_obj)
有几个我发现的问题,如下所列:
-
它在复制原始对象中值为
undefined
的条目时丢弃条目,
如果有Infinity
、NaN
等值,复制时会转换成null
,
如果原始对象中有Date
类型,它将在克隆对象中被字符串化(typeof date_entry --> string
)。
找到了一种克隆对象的有效方法,它在各种情况下都对我很有效。请看下面的代码,因为它解决了上面提到的JSON.parse(...)
的所有陷阱,但导致正确的深度克隆:
var orig_obj =
string: 'my_str',
number: 123,
bool: false,
nul: null,
nested :
value : true
,
nan : NaN,
date: new Date(),
undef: undefined,
inf: Infinity,
console.log("original_obj before modification: ", orig_obj, "\n");
console.log(typeof orig_obj.date, "\n");
var clone_obj = Object.assign(, orig_obj);
//this below loop will help in deep cloning and solving above issues
for(let prop in orig_obj)
if(typeof orig_obj[prop] === "object")
if(orig_obj[prop] instanceof Date)
clone_obj[prop] = orig_obj[prop];
else
clone_obj[prop] = JSON.parse(JSON.stringify(orig_obj[prop]));
console.log("cloned_obj before modification: ", orig_obj, "\n");
clone_obj.bool = true;
clone_obj.nested.value = "false";
console.log("original_obj post modification: ", orig_obj, "\n");
console.log("cloned_obj post modification: ", clone_obj, "\n");
console.log(typeof clone_obj.date);
【讨论】:
您检查所有***属性以查看它们是否是日期,但如果它们不是,则将它们进行 JSON 字符串化/解析。如果***属性是包含 Date 作为其属性之一的对象怎么办?您的解决方案需要递归工作,以捕获所有可能无法进行 JSON.parsed 的嵌套对象的情况。【参考方案17】:克隆对象的简单递归方法。也可以使用 lodash.clone。
let clone = (obj) =>
let obj2 = Array.isArray(obj) ? [] : ;
for(let k in obj)
obj2[k] = (typeof obj[k] === 'object' ) ? clone(obj[k]) : obj[k];
return obj2;
let w = name: "Apple", types: ["Fuji", "Gala"];
let x = clone(w);
w.name = "Orange";
w.types = ["Navel"];
console.log(x);
console.log(w);
【讨论】:
【参考方案18】:如果你不在你的对象中使用Date
s、functions、undefined、regExp 或 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'
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()
这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。
另请参阅this article about the structured clone algorithm of browsers,它在向工作人员发送消息或从工作人员发送消息时使用。它还包含一个深度克隆功能。
【讨论】:
有时最好的答案是最简单的。天才。【参考方案19】:var x = 'e': 2, 'd': 8, 'b': 5;
const y = ;
for(let key in x)
y[key] = x[key];
console.log(y); // =>>> e: 2, d: 8, b: 5
const z = ;
Object.keys(x).forEach(key =>
z[key] = x[key];
);
console.log(z); // =>>> e: 2, d: 8, b: 5
const w = ;
for(let i = 0; i < Object.keys(x).length; i++)
w[Object.keys(x)[i]] = x[Object.keys(x)[i]];
console.log(w); // =>>> e: 2, d: 8, b: 5
const v = ;
for(let key of Object.keys(x))
v[key] = x[key];
console.log(v); // =>>> e: 2, d: 8, b: 5
x['q'] = 100; // Altering x will not affect the other objects
console.log(x); // =>>> e: 2, d: 8, b: 5, q: 100
console.log(y); // =>>> e: 2, d: 8, b: 5
console.log(z); // =>>> e: 2, d: 8, b: 5
console.log(w); // =>>> e: 2, d: 8, b: 5
console.log(v); // =>>> e: 2, d: 8, b: 5
【讨论】:
【参考方案20】:对象复制使用(...)
//bad
const original = a: 1, b: 2 ;
const copy = Object.assign(, original, c: 3 ); // copy => a: 1, b: 2,c: 3
//good
const originalObj = id: 5, name: 'San Francisco';
const copyObject = ...originalObj, pincode: 4444;
console.log(copyObject) // id: 5, name: 'San Francisco', pincode: 4444
同样可以用于复制数组从一个到另一个
const itemsCopy = [...items];
【讨论】:
为什么 Object.assign 方式被标记为“坏”?有人可以详细说明吗?【参考方案21】:Use lodash _.cloneDeep().
浅拷贝:lodash _.clone()
可以通过简单地复制引用来进行浅拷贝。
let obj1 =
a: 0,
b:
c: 0,
e:
f: 0
;
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;
console.log(JSON.stringify(obj1));
//"a":4,"b":"c":4,"e":"f":100
console.log(JSON.stringify(obj3));
//"a":0,"b":"c":4,"e":"f":100
深拷贝:lodash _.cloneDeep()
字段被取消引用:而不是对被复制对象的引用
let obj1 =
a: 0,
b:
c: 0,
e:
f: 0
;
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;
console.log(JSON.stringify(obj1));
"a":100,"b":"c":100,"e":"f":100
console.log(JSON.stringify(obj3));
"a":0,"b":"c":0,"e":"f":0
【讨论】:
【参考方案22】:简单
var restore = name:'charlesi',
age:9
var prev_data =
name: 'charles'
age : 10
var temp = JSON.stringify(prev_data)
restore = JSON.parse(temp)
restore =
name:'charlie',
age : 12
输出prev_data:
name: 'charles'
age : 10
【讨论】:
【参考方案23】:使用jQuery,你可以浅拷贝 extend:
var copiedObject = jQuery.extend(, originalObject)
对copiedObject
的后续更改不会影响originalObject
,反之亦然。
或者制作一个深拷贝:
var copiedObject = jQuery.extend(true, , originalObject)
【讨论】:
甚至:var copiedObject = jQuery.extend(,originalObject);
将 true 指定为深层复制的第一个参数也很有用:jQuery.extend(true, , originalObject);
【参考方案24】:
在我的代码中,我经常定义一个function (_)
来处理副本,以便我可以将by value
传递给函数。此代码创建一个深层副本,但保持继承。它还跟踪子副本,以便可以在没有无限循环的情况下复制自引用对象。随意使用。
它可能不是最优雅的,但它还没有让我失望。
_ = function(oReferance)
var aReferances = new Array();
var getPrototypeOf = function(oObject)
if(typeof(Object.getPrototypeOf)!=="undefined") return Object.getPrototypeOf(oObject);
var oTest = new Object();
if(typeof(oObject.__proto__)!=="undefined"&&typeof(oTest.__proto__)!=="undefined"&&oTest.__proto__===Object.prototype) return oObject.__proto__;
if(typeof(oObject.constructor)!=="undefined"&&typeof(oTest.constructor)!=="undefined"&&oTest.constructor===Object&&typeof(oObject.constructor.prototype)!=="undefined") return oObject.constructor.prototype;
return Object.prototype;
;
var recursiveCopy = function(oSource)
if(typeof(oSource)!=="object") return oSource;
if(oSource===null) return null;
for(var i=0;i<aReferances.length;i++) if(aReferances[i][0]===oSource) return aReferances[i][1];
var Copy = new Function();
Copy.prototype = getPrototypeOf(oSource);
var oCopy = new Copy();
aReferances.push([oSource,oCopy]);
for(sPropertyName in oSource) if(oSource.hasOwnProperty(sPropertyName)) oCopy[sPropertyName] = recursiveCopy(oSource[sPropertyName]);
return oCopy;
;
return recursiveCopy(oReferance);
;
// Examples:
Wigit = function();
Wigit.prototype.bInThePrototype = true;
A = new Wigit();
A.nCoolNumber = 7;
B = _(A);
B.nCoolNumber = 8; // A.nCoolNumber is still 7
B.bInThePrototype // true
B instanceof Wigit // true
【讨论】:
【参考方案25】:根据template
克隆一个对象。如果您不想要一个精确的副本,但您确实想要某种可靠克隆操作的稳健性但您只想要克隆位,或者您想确保您可以控制每个属性值的存在或格式,您会怎么做克隆?
我贡献这个是因为它对我们有用,我们创建它是因为我们找不到类似的东西。您可以使用它来克隆基于 template
对象的对象,该对象指定我要克隆的对象的哪些属性,并且模板允许函数将这些属性转换为不同的东西,如果它们不存在于源对象上或者您想处理克隆。如果它没有用,我相信有人可以删除这个答案。
function isFunction(functionToCheck)
var getType = ;
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
function cloneObjectByTemplate(obj, tpl, cloneConstructor)
if (typeof cloneConstructor === "undefined")
cloneConstructor = false;
if (obj == null || typeof (obj) != 'object') return obj;
//if we have an array, work through it's contents and apply the template to each item...
if (Array.isArray(obj))
var ret = [];
for (var i = 0; i < obj.length; i++)
ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
return ret;
//otherwise we have an object...
//var temp:any = ; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values.
var temp = cloneConstructor ? new obj.constructor() : ;
for (var key in tpl)
//if we are provided with a function to determine the value of this property, call it...
if (isFunction(tpl[key]))
temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
else
//if our object has this property...
if (obj[key] != undefined)
if (Array.isArray(obj[key]))
temp[key] = [];
for (var i = 0; i < obj[key].length; i++)
temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
else
temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
return temp;
一个简单的调用方式是这样的:
var source =
a: "whatever",
b:
x: "yeah",
y: "haha"
;
var template =
a: true, //we want to clone "a"
b:
x: true //we want to clone "b.x" too
;
var destination = cloneObjectByTemplate(source, template);
如果您想使用函数来确保返回属性或确保它是特定类型,请使用这样的模板。我们没有使用 ID: true
,而是提供了一个函数,它仍然只是复制源对象的ID attribute
,但它确保它是一个数字,即使它不存在于源对象上。
var template =
ID: function (srcObj)
if(srcObj.ID == undefined) return -1;
return parseInt(srcObj.ID.toString());
数组可以很好地克隆,但如果您愿意,您也可以让自己的函数处理这些单独的属性,并执行以下特殊操作:
var template =
tags: function (srcObj)
var tags = [];
if (process.tags != undefined)
for (var i = 0; i < process.tags.length; i++)
tags.push(cloneObjectByTemplate(
srcObj.tags[i],
a : true, b : true //another template for each item in the array
);
return tags;
所以在上面,我们的模板只是复制源对象的tags
属性(如果它存在)(假设它是一个数组),并且对于该数组中的每个元素,调用克隆函数来单独克隆它基于第二个模板,该模板仅复制每个标记元素的a
和b
属性。
如果您将对象进出节点,并且您想控制这些对象的哪些属性被克隆,那么这是在node.js
中控制它的好方法,并且代码也可以在浏览器中运行。
这是一个使用示例:http://jsfiddle.net/hjchyLt1/
【讨论】:
【参考方案26】:使用来自npm
的deepcopy
。在浏览器和node
中都可以作为npm module...
使用
https://www.npmjs.com/package/deepcopy
let a = deepcopy(b)
【讨论】:
【参考方案27】:对克隆简单对象感兴趣:
JSON.parse(JSON.stringify(json_original));
来源:How to copy JavaScript object to new variable NOT by reference?
【讨论】:
非常好 - 简单。 @MattH:这个答案已经给了in 2012。你看见了吗? Mohammed,您在复制其中一个之前检查过现有答案吗? 这是一种方式。你从来没有想过这个【参考方案28】:对于深拷贝和克隆,JSON.stringify 然后 JSON.parse 对象:
obj = a: 0 , b: c: 0;
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // a: 0, b: c: 0
【讨论】:
非常聪明...这种方法有什么缺点吗?【参考方案29】:我认为有一个简单而有效的答案。在深度复制中存在两个问题:
-
保持属性相互独立。
并在克隆对象上保持方法有效。
所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行分配以复制函数。
let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign(, source);
Object.assign(merged, deepCloned);
虽然这个问题有很多答案,但我希望这个也能有所帮助。
【讨论】:
虽然如果允许我导入 lodash,我更喜欢使用 lodashcloneDeep
。
我正在使用 JSON.parse(JSON.stringify(source))。一直在工作。
@Misha,这样你会错过这些功能。 “作品”一词有多种含义。
请记住,我提到的方式,只会复制第一层的功能。因此,如果我们在彼此内部有一些对象,那么唯一的方法就是逐个字段递归地复制。【参考方案30】:
好的,假设你在下面有这个对象并且你想克隆它:
let obj = a:1, b:2, c:3; //ES6
或
var obj = a:1, b:2, c:3; //ES5
答案主要取决于您使用的是哪个ECMAscript,在ES6+
,您可以简单地使用Object.assign
进行克隆:
let cloned = Object.assign(, obj); //new a:1, b:2, c:3;
或像这样使用扩展运算符:
let cloned = ...obj; //new a:1, b:2, c:3;
但是如果你使用ES5
,你可以使用一些方法,但是JSON.stringify
,只要确保你没有使用大量数据来复制,但在很多情况下它可能是一个方便的方法,像这样:
let cloned = JSON.parse(JSON.stringify(obj));
//new a:1, b:2, c:3;, can be handy, but avoid using on big chunk of data over and over
【讨论】:
您能否举例说明big chunk of data
的含义? 100KB? 100MB?谢谢!
是的,@user1063287,基本上更大的数据,性能更差......所以这真的取决于,而不是 kb、mb 或 gb,更多的是关于你想要做多少次。 ..它也不适用于函数和其他东西......
Object.assign
进行浅拷贝(就像传播一样,@Alizera)
你不能在 es5 中使用 let :^) @Alireza以上是关于如何正确克隆 JavaScript 对象?的主要内容,如果未能解决你的问题,请参考以下文章