x
。我想将它作为对象 y
复制,使得对 y
的更改不会修改 x
。我意识到,复制从内置 JavaScript 对象派生的对象会导致额外的、不必要的属性。这不是问题,因为我要复制的是我自己通过字面量构造的对象。如何正确地克隆 JavaScript 对象?
x
。我想将它作为对象 y
复制,使得对 y
的更改不会修改 x
。我意识到,复制从内置 JavaScript 对象派生的对象会导致额外的、不必要的属性。这不是问题,因为我要复制的是我自己通过字面量构造的对象。在JavaScript中,有三种方法可以克隆对象。由于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
方法会移除对象中的所有方法。 - Andreas一种特别不优雅的解决方案是使用 JSON 编码来创建没有成员方法的对象的深层副本。该方法是将目标对象转换为 JSON 编码,然后通过解码它,你可以得到所需的副本。你可以解码多次以获得所需的多个副本。
当然,函数不适用于 JSON,因此此方法仅适用于没有成员方法的对象。
这种方法非常适合我的用例,因为我在键值存储中存储了 JSON 数据块,在 JavaScript API 中将其公开为对象时,每个对象实际上都包含原始对象状态的副本,以便我们在调用方修改公开对象后计算差异。
var object1 = {key:"value"};
var object2 = object1;
object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);
object2.key = "a change";
console.log(object1);// returns value
{'foo': function() {return 1;}}
是一个字面构造的对象。 - abarnert完全克隆:
let x = {a: 'value1'}
let x2 = {...x}
// => mutate without references:
x2.a = 'value2'
console.log(x.a) // => 'value1'
克隆带有第二级引用:
const y = {a: {b: 'value3'}}
const y2 = {...y}
// => nested object is still a references:
y2.a.b = 'value4'
console.log(y.a.b) // => 'value4'
实际上,JavaScript 并不原生支持深克隆。可以使用一个实用函数。例如 Ramda:
const first = {a: 'foo', b: 'bar'};
const second = {...{a} = first}
- Christian Vincenzo Trainaconst objClone = { ...obj };
请注意,嵌套对象仍然被复制为引用。
const objDeepClone = JSON.parse(JSON.stringify(obj));
- Pavan Garre以下内容翻译自Brian Huisman的文章:如何在Javascript中复制数组和对象:
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;
};
var copiedObj = Object.create(obj);
不是一个很好的方法呢? - Dan P.info
的属性,而且 info
对象有一些属性,包括一个 weight
属性。那么赋值 myclone.info.weight
将会修改原型,因为它从原型中读取 info
,然后将其分配给该属性的 weight
属性。 - doug65536如果您使用AngularJS,该库还提供了直接克隆或扩展对象的方法。
var destination = angular.copy(source);
或者angular.copy(source, destination);
更多关于angular.copy的内容请查看文档...
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;
}
new
正确调用了构造函数,所以我点了赞。而被接受的答案则没有这么做。 - GetFreeA.Levy提供的答案几乎是完整的,这里是我的小贡献:有一种方法可以处理递归引用,请查看以下代码行:
if(this[attr]==this) copy[attr] = copy;
如果对象是XML DOM元素,则必须使用cloneNode
if(this.cloneNode) return this.cloneNode(true);
在受到A.Levy的详尽研究和Calvin的原型方法启发后,我提出了这个解决方案:
Object.prototype.clone = function() {
if(this.cloneNode) return this.cloneNode(true);
var copy = this instanceof Array ? [] : {};
for(var attr in this) {
if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
copy[attr] = this[attr];
else if(this[attr]==this) copy[attr] = copy;
else copy[attr] = this[attr].clone();
}
return copy;
}
Date.prototype.clone = function() {
var copy = new Date();
copy.setTime(this.getTime());
return copy;
}
Number.prototype.clone =
Boolean.prototype.clone =
String.prototype.clone = function() {
return this;
}
请参考答案中Andy Burke的注释。
Date.prototype.clone = function() {return new Date(+this)};
- RobG今天2020.04.30,我在MacOs High Sierra v10.13.6上测试了Chrome v81.0,Safari v13.1和Firefox v75.0上的选定解决方案。
我关注复制数据(具有简单类型字段的对象,而不是方法等)的速度。方案A-I只能进行浅拷贝,方案J-U可以进行深拷贝。
{...obj}
(A) 在Chrome和Firefox上最快,在Safari上中等快Object.assign
的解决方案(B)在所有浏览器上都很快JSON.parse/stringify
(K)相当慢JSON.parse/stringify
(K)相当慢对于已选择的解决方案: A B C(我的) D E F G H I J K L M N O P Q R S T U, 我执行了4个测试
测试用到的对象在下面的片段中显示
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;
}
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)
使用 Lodash:
var y = _.clone(x, true);
_.cloneDeep(x)
,因为它本质上与上面的代码相同,但更易于阅读。 - garbanzio
mObj=JSON.parse(JSON.stringify(jsonObject));
。 - Lord Loh.Object.create(o)
,它完全满足了作者的要求。 - froginvasionvar x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2;
执行完这段代码后,y.deep.key
的值也会变成2,因此 Object.create 不能用于克隆对象。 - Ruben Stolk