如何正确克隆JavaScript对象?

我有一个对象x。我想将它复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象将导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。

如何正确克隆JavaScript对象?

2249870 次浏览

来自本文: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;};
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;}

如果您的对象中没有循环依赖项,我建议使用其他答案之一或jQuery的复制方法,因为它们看起来都非常有效。

如果存在循环依赖关系(即,两个子对象相互链接),那么您就像有(从理论角度来看)一样陷入困境没有办法优雅地解决这个问题

Apple JavaScript编码指南

// Create an inner object with a variable x whose default// value is 3.function innerObj(){this.x = 3;}innerObj.prototype.clone = function() {var temp = new innerObj();for (myvar in this) {// this object does not contain any objects, so// use the lightweight copy code.temp[myvar] = this[myvar];}return temp;}
// Create an outer object with a variable y whose default// value is 77.function outerObj(){// The outer object contains an inner object.  Allocate it here.this.inner = new innerObj();this.y = 77;}outerObj.prototype.clone = function() {var temp = new outerObj();for (myvar in this) {if (this[myvar].clone) {// This variable contains an object with a// clone operator.  Call it to create a copy.temp[myvar] = this[myvar].clone();} else {// This variable contains a scalar value,// a string value, or an object with no// clone function.  Assign it directly.temp[myvar] = this[myvar];}}return temp;}
// Allocate an outer object and assign non-default values to variables in// both the outer and inner objects.outer = new outerObj;outer.inner.x = 4;outer.y = 16;
// Clone the outer object (which, in turn, clones the inner object).newouter = outer.clone();
// Verify that both values were copied.alert('inner x is '+newouter.inner.x); // prints 4alert('y is '+newouter.y); // prints 16

史蒂夫

2022年更新

有一个新的JS标准称为结构化克隆。它适用于许多浏览器(参见我可以使用)。

const clone = structuredClone(object);

老答案

对JavaScript中的任何对象都这样做并不简单或直接。你会遇到错误地从对象原型中提取属性的问题,这些属性应该留在原型中,而不是复制到新实例。例如,如果你要向Object.prototype添加一个clone方法,正如一些答案所描述的那样,你需要显式跳过该属性。但是,如果有其他额外的方法添加到Object.prototype,或者其他你不知道的中间原型怎么办?在这种情况下,你会复制不应该复制的属性,所以你需要用#3方法检测不可预见的非局部属性。

除了不可枚举的属性之外,当你尝试复制具有隐藏属性的对象时,你会遇到一个更棘手的问题。例如,prototype是一个函数的隐藏属性。此外,一个对象的原型被属性__proto__引用,它也是隐藏的,并且不会被迭代源对象属性的for/in循环复制。我认为__proto__可能是特定于Firefox的JavaScript解释器的,它可能在其他浏览器中有所不同,但你明白了。并非所有东西都是可枚举的。如果你知道隐藏属性的名称,你可以复制它,但我不知道有什么方法可以自动发现它。

寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果你的源对象的原型是Object,那么简单地用{}创建一个新的通用对象就可以了,但如果源对象的原型是Object的后代,那么你将错过该原型中你使用hasOwnProperty过滤器跳过的额外成员,或者在原型中但一开始就不可枚举的成员。一种解决方案可能是调用源对象的constructor属性来获取初始复制对象,然后复制属性,但你仍然不会获得不可枚举的属性。例如,#5对象将其数据存储为隐藏成员:

function clone(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;}
var d1 = new Date();
/* Executes function after 5 seconds. */setTimeout(function(){var d2 = clone(d1);alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());}, 5000);

d1的日期字符串将比d2晚5秒。一种使Date与另一个相同的方法是调用setTime方法,但这是特定于Date类的。我不认为这个问题有一个防弹的通用解决方案,尽管我很乐意错了!

当我不得不实现一般的深度复制时,我最终妥协了,假设我只需要复制普通的ObjectArrayDateStringNumberBoolean。最后3种类型是不可变的,所以我可以执行浅层复制,而不用担心它会改变。我进一步假设ObjectArray中包含的任何元素也将是该列表中6种简单类型之一。这可以通过如下代码来完成:

function clone(obj) {var copy;
// Handle the 3 simple types, and null or undefinedif (null == obj || "object" != typeof obj) return obj;
// Handle Dateif (obj instanceof Date) {copy = new Date();copy.setTime(obj.getTime());return copy;}
// Handle Arrayif (obj instanceof Array) {copy = [];for (var i = 0, len = obj.length; i < len; i++) {copy[i] = clone(obj[i]);}return copy;}
// Handle Objectif (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.");}

只要对象和数组中的数据形成树形结构,上述函数就可以充分适用于我提到的6种简单类型。也就是说,对象中对同一数据的引用不超过一个。例如:

// This would be cloneable:var tree = {"left"  : { "left" : null, "right" : null, "data" : 3 },"right" : null,"data"  : 8};
// This would kind-of work, but you would get 2 copies of the// inner node instead of 2 references to the same copyvar directedAcylicGraph = {"left"  : { "left" : null, "right" : null, "data" : 3 },"data"  : 8};directedAcyclicGraph["right"] = directedAcyclicGraph["left"];
// Cloning this would cause a stack overflow due to infinite recursion:var cyclicGraph = {"left"  : { "left" : null, "right" : null, "data" : 3 },"data"  : 8};cyclicGraph["right"] = cyclicGraph;

它将无法处理任何JavaScript对象,但它可能足以满足许多目的,只要您不认为它只适用于您抛给它的任何东西。

一个特别不优雅的解决方案是使用JSON编码来制作没有成员方法的对象的深度副本。方法论是对你的目标对象进行JSON编码,然后通过解码它,你得到了你正在寻找的副本。你可以解码任意次数,根据需要制作任意数量的副本。

当然,函数不属于JSON,所以这只适用于没有成员方法的对象。

这个方法论非常适合我的用例,因为我将JSON blob存储在键值存储中,当它们在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

使用jQuery,您可以浅拷贝延长

var copiedObject = jQuery.extend({}, originalObject)

copiedObject的后续更改不会影响originalObject,反之亦然。

或者做一个深度复制

var copiedObject = jQuery.extend(true, {}, originalObject)

A. 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;}

另见安迪·伯克在答案中的注释。

有很多答案,但没有一个提到ECMAScript 5中的Object.create,诚然它没有给你一个精确的副本,而是将源设置为新对象的原型。

因此,这不是对这个问题的确切答案,但它是一个单行解决方案,因此很优雅。它最适合两种情况:

  1. 如果这样的继承是有用的(duh!)
  2. 其中源对象不会被修改,从而使2个对象之间的关系成为一个非问题。

示例:

var foo = { a : 1 };var bar = Object.create(foo);foo.a; // 1bar.a; // 1foo.a = 2;bar.a; // 2 - prototype changedbar.a = 3;foo.a; // Still 2, since setting bar.a makes it an "own" property

为什么我认为这个解决方案更好?它是原生的,因此没有循环,没有递归。然而,旧的浏览器需要一个Poly填充。

扬·图罗在上面的答案非常接近,由于兼容性问题,可能是在浏览器中最好的使用,但它可能会导致一些奇怪的枚举问题。例如,执行:

for ( var i in someArray ) { ... }

将在迭代数组的元素后将clone()方法分配给i。这是一个避免枚举并使用node.js的适配:

Object.defineProperty( Object.prototype, "clone", {value: 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;}});
Object.defineProperty( Date.prototype, "clone", {value: function() {var copy = new Date();copy.setTime( this.getTime() );return copy;}});
Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

这避免了使clone()方法可枚举,因为定义属性()默认情况下可枚举为false。

你可以使用一行代码克隆一个对象并从上一个对象中删除任何引用。只需执行:

var obj1 = { text: 'moo1' };var obj2 = Object.create(obj1); // Creates a new clone without references
obj2.text = 'moo2'; // Only updates obj2's text property
console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

对于当前不支持Object.create的浏览器/引擎,您可以使用此Poly填充:

// Polyfill Object.create if it does not existif (!Object.create) {Object.create = function (o) {var F = function () {};F.prototype = o;return new F();};}

我只是想添加到这篇文章中的所有Object.create解决方案中,这并不能以nodejs所需的方式工作。

在Firefox中,结果

var a = {"test":"test"};var b = Object.create(a);console.log(b);´

{test:"test"}

在nodejs中

{}

如果你没有在你的对象中使用Date、函数、未定义、regExp或Infinity,一个非常简单的一行是JSON.parse(JSON.stringify(object))

const a = {string: 'string',number: 123,bool: false,nul: null,date: new Date(),  // stringifiedundef: undefined,  // lostinf: Infinity,  // forced to 'null'}console.log(a);console.log(typeof a.date);  // Date objectconst clone = JSON.parse(JSON.stringify(a));console.log(clone);console.log(typeof clone.date);  // result of .toISOString()

这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。

另请参阅本文介绍浏览器的结构化克隆算法,它在向工作人员发布消息时使用。它还包含一个用于深度克隆的函数。

如果您可以使用浅拷贝,underscore.js库有一个克隆方法。

y = _.clone(x);

或者你可以把它扩展成

copiedObject = _.extend({},originalObject);

这是对A. Levy代码的改编,用于处理函数和多个/循环引用的克隆——这意味着如果被克隆树中的两个属性是同一个对象的引用,克隆的对象树将使这些属性指向被引用对象的同一个克隆。这也解决了循环依赖的情况,如果不处理循环依赖,会导致无限循环。该算法的复杂度为O(n)

function clone(obj){var clonedObjectsArray = [];var originalObjectsArray = []; //used to remove the unique ids when finishedvar 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 Dateif (obj instanceof Date) {var copy = new Date();copy.setTime(obj.getTime());return copy;}
// Handle Arrayif (obj instanceof Array) {var copy = [];for (var i = 0; i < obj.length; ++i) {copy[i] = cloneRecursive(obj[i]);}return copy;}
// Handle Objectif (obj instanceof Object) {if (clonedObjectsArray[objectId(obj)] != undefined)return clonedObjectsArray[objectId(obj)];
var copy;if (obj instanceof Function)//Handle Functioncopy = function(){return obj.apply(this, arguments);};elsecopy = {};
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 idsfor (var i = 0; i < originalObjectsArray.length; i++){delete originalObjectsArray[i].__obj_id;};
return cloneObj;}

一些快速测试

var auxobj = {prop1 : "prop1 aux val",prop2 : ["prop2 item1", "prop2 item2"]};
var obj = new Object();obj.prop1 = "prop1_value";obj.prop2 = [auxobj, auxobj, "some extra val", undefined];obj.nr = 3465;obj.bool = true;
obj.f1 = function (){this.prop1 = "prop1 val changed by f1";};
objclone = clone(obj);
//some tests i've madeconsole.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));
objclone.f1();console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));objclone.f1.prop = 'some prop';console.log("test function cloning 2: " + (obj.f1.prop == undefined));
objclone.prop2[0].prop1 = "prop1 aux val NEW";console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
function clone(src, deep) {
var toString = Object.prototype.toString;if(!src && typeof src != "object"){//any non-object ( Boolean, String, Number ), null, undefined, NaNreturn src;}
//Honor native/custom clone methodsif(src.clone && toString.call(src.clone) == "[object Function]"){return src.clone(deep);}
//DOM Elementsif(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){return src.cloneNode(deep);}
//Dateif(toString.call(src) == "[object Date]"){return new Date(src.getTime());}
//RegExpif(toString.call(src) == "[object RegExp]"){return new RegExp(src);}
//Functionif(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;//Arrayif(toString.call(src) == "[object Array]"){//[].slice(0) would soft cloneret = src.slice();if(deep){index = ret.length;while(index--){ret[index] = clone(ret[index], true);}}}//Objectelse {ret = src.constructor ? new src.constructor() : {};for (var prop in src) {ret[prop] = deep? clone(src[prop], true): src[prop];}}
return ret;};

由于mindeavor声明要克隆的对象是“字面构造”对象,因此解决方案可能是简单地多次生成对象,而不是克隆对象的实例:

function createMyObject(){var myObject ={...};return myObject;}
var myObjectInstance1 = createMyObject();var myObjectInstance2 = createMyObject();

使用Lodash:

var y = _.clone(x, true);

在我的代码中,我经常定义一个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 7B.bInThePrototype // trueB instanceof Wigit // true

有关W3C的“安全传递结构化数据”算法,请参阅http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data,该算法旨在由浏览器实现,用于将数据传递给例如Web工作人员。然而,它有一些限制,因为它不处理函数。有关更多信息,请参阅https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm,包括JS中的替代算法,它可以让您部分实现。

我最喜欢的优雅的JS对象克隆解决方案是

function CloneObject() {}function cloneObject(o) {CloneObject.prototype = o;return new CloneObject();}

使用cloneObject(object)获取JS对象的克隆。

与许多复制解决方案不同,此克隆在克隆对象中保持原型关系。

//// creates 'clone' method on context object////  var//     clon = Object.clone( anyValue );//!((function (propertyName, definition) {this[propertyName] = definition();}).call(Object,"clone",function () {function isfn(fn) {return typeof fn === "function";}
function isobj(o) {return o === Object(o);}
function isarray(o) {return Object.prototype.toString.call(o) === "[object Array]";}
function fnclon(fn) {return function () {fn.apply(this, arguments);};}
function owns(obj, p) {return obj.hasOwnProperty(p);}
function isemptyobj(obj) {for (var p in obj) {return false;}return true;}
function isObject(o) {return Object.prototype.toString.call(o) === "[object Object]";}return function (input) {if (isfn(input)) {return fnclon(input);} else if (isobj(input)) {var cloned = {};for (var p in input) {owns(Object.prototype, p)|| (isfn(input[p])&& ( cloned[p] = function () { return input[p].apply(input, arguments); } )|| ( cloned[p] = input[p] ));}if (isarray(input)) {cloned.length = input.length;"concat every filter forEach indexOf join lastIndexOf map pop push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift".split(" ").forEach(function (methodName) {isfn( Array.prototype[methodName] )&& (cloned[methodName] =function () {return Array.prototype[methodName].apply(cloned, arguments);});});}return isemptyobj(cloned)? (isObject(input)? cloned: input): cloned;} else {return input;}};}));//

结构化克隆

2022更新#0全局函数已经在Node 17、Deno 1.14和大多数主要浏览器中可用(参见我可以使用)。

可以使用与超文本标记语言标准相同的结构化克隆机制,用于在域之间发送数据。

const clone = structuredClone(original);

查看另一个答案了解更多详情。

function clone(obj){var cloneObj = Object.create(obj);
return cloneObj;}

在Javascript中,对象单独继承另一个对象(原型继承)。Object.create(obj)返回一个对象,该对象是obj的子对象或子对象。在上面的函数中,它将有效地返回对象的副本。

然而,这是一种非常奇怪的克隆方式,因为我没有将继承用于真正的目的。

由于同样的问题,我来到这个页面,但我既不使用JQuery,也没有克隆方法适用于我自己的对象。

我知道我的答案与这个问题没有太大的关系,因为这是一种不同的方法。我使用了创建函数而不是使用克隆函数。它对我有用,用于以下(不幸的是限制)目的:

  1. 我主要使用JSP生成的Javascript
  2. 我一开始就知道必须生成哪个对象(在我的情况下,它是来自数据库的信息,它只被获取一次,需要在JS中更频繁地部署。

首先,我像这样定义我的对象:

var obj= new Object();obj.Type='Row';obj.ID=1;obj.Value='Blah blah';

现在我把一切都像:

function getObjSelektor(id_nummer,selected){var obj = document.createElement("select");obj.setAttribute("id","Selektor_"+id_nummer);obj.setAttribute("name","Selektor");obj.setAttribute("size","1");
var obj_opt_1 = document.createElement("option");obj_opt_1.setAttribute("value","1");if(1==selected)posopval_opt_1.setAttribute("selected","selected");obj_opt_1.innerHTML="Blah blah";obj.appendChild(obj_opt_1);
var obj_opt_2 = document.createElement("option");obj_opt_2.setAttribute("value","2");if(2==selected)obj_opt_2.setAttribute("selected","selected");obj_opt_2.innerHTML="2nd Row";obj.appendChild(obj_opt_2);
...
return obj;}

并调用常规代码中的函数:

myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));

如前所述,这是一种不同的方法,为我的目的解决了我的问题。

如果你有一个带有函数的对象,你可以用JSONfn来做,参见http://www.eslinstructor.net/jsonfn/

var obj= {name:'Marvin',getName :  function(){return this.name;}}var cobj = JSONfn.parse(JSONfn.stringify(obj));

你可以使用函数闭包来获得深度副本的所有好处,而无需深度副本。这是一个非常不同的范式,但效果很好。与其尝试复制现有对象,不如在需要时使用函数实例化一个新对象。

首先,创建一个返回对象的函数

function template() {return {values: [1, 2, 3],nest: {x: {a: "a", b: "b"}, y: 100}};}

然后创建一个简单的浅复制函数

function copy(a, b) {Object.keys(b).forEach(function(key) {a[key] = b[key];});}

创建一个新对象,并将模板的属性复制到它上面

var newObject = {};copy(newObject, template());

但是上面的复制步骤是不必要的。你需要做的就是:

var newObject = template();

现在你有了一个新对象,测试它的属性是什么:

console.log(Object.keys(newObject));

这显示:

["values", "nest"]

是的,这些是newObject自己的属性,而不是对另一个对象属性的引用。让我们检查一下:

console.log(newObject.nest.x.b);

这显示:

"b"

newObject已经获得了模板对象的所有属性,但没有任何依赖链。

http://jsbin.com/ISUTIpoC/1/edit?js,控制台

我添加这个例子是为了鼓励一些辩论,所以请添加一些评论:)

复制一个最终可能指向自身的对象的问题可以通过简单的检查来解决。每次有复制操作时添加此检查。它可能是缓慢,但它应该工作。

我使用toType()方法名函数显式地返回对象类型。我也有自己的拷贝对象函数,它在逻辑上非常相似,它回答了所有三个Object()、Array()和Date()情况。

我在NodeJS中运行它。

还没有测试。

// Returns true, if one of the parent's children is the target.// This is useful, for avoiding copyObj() through an infinite loop!function isChild(target, parent) {if (toType(parent) == '[object Object]') {for (var name in parent) {var curProperty = parent[name];
// Direct child.if (curProperty = target) return true;
// Check if target is a child of this property, and so on, recursively.if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {if (isChild(target, curProperty)) return true;}}} else if (toType(parent) == '[object Array]') {for (var i=0; i < parent.length; i++) {var curItem = parent[i];
// Direct child.if (curItem = target) return true;
// Check if target is a child of this property, and so on, recursively.if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {if (isChild(target, curItem)) return true;}}}
return false;     // Not the target.}

互联网上的大多数解决方案都有几个问题。所以我决定做一个跟进,其中包括,为什么接受的答案不应该被接受。

起始情况

我想深度复制一个JavascriptObject及其所有子程序和它们的子程序等等。但由于我不是一个普通的开发人员,我的Object正常propertiescircular structures甚至nested objects

所以让我们先创建一个circular structure和一个nested object

function Circ() {this.me = this;}
function Nested(y) {this.y = y;}

让我们把所有的东西放在一个名为aObject中。

var a = {x: 'a',circ: new Circ(),nested: new Nested('a')};

接下来,我们要将a复制到名为b的变量中并对其进行突变。

var b = a;
b.x = 'b';b.nested.y = 'b';

你知道这里发生了什么,因为如果不是,你甚至不会在这个伟大的问题上着陆。

console.log(a, b);
a --> Object {x: "b",circ: Circ {me: Circ { ... }},nested: Nested {y: "b"}}
b --> Object {x: "b",circ: Circ {me: Circ { ... }},nested: Nested {y: "b"}}

现在让我们找到一个解决方案。

JSON

我尝试的第一次尝试是使用JSON

var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';b.nested.y = 'b';

不要浪费太多时间,你会得到TypeError: Converting circular structure to JSON

递归拷贝(接受的“答案”)

让我们来看看公认的答案。

function cloneSO(obj) {// Handle the 3 simple types, and null or undefinedif (null == obj || "object" != typeof obj) return obj;
// Handle Dateif (obj instanceof Date) {var copy = new Date();copy.setTime(obj.getTime());return copy;}
// Handle Arrayif (obj instanceof Array) {var copy = [];for (var i = 0, len = obj.length; i < len; i++) {copy[i] = cloneSO(obj[i]);}return copy;}
// Handle Objectif (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! Its type isn't supported.");}

看起来不错,呵呵?它是对象的递归副本,也处理其他类型,例如Date,但这不是必需的。

var b = cloneSO(a);
b.x = 'b';b.nested.y = 'b';

递归和circular structures不能很好地配合……RangeError: Maximum call stack size exceeded

原生解决方案

在和我的同事争论之后,我的老板问我们发生了什么事,他在谷歌搜索后发现了一个简单的解决方案。它被称为Object.create

var b = Object.create(a);
b.x = 'b';b.nested.y = 'b';

这个解决方案是在一段时间前添加到Javascript中的,甚至可以处理circular structure

console.log(a, b);
a --> Object {x: "a",circ: Circ {me: Circ { ... }},nested: Nested {y: "b"}}
b --> Object {x: "b",circ: Circ {me: Circ { ... }},nested: Nested {y: "b"}}

…你看,里面的嵌套结构不起作用。

用于原生溶液的PolyFill

在较旧的浏览器中,就像IE 8一样,有一个Object.create的多边形填充。它类似于Mozilla推荐的东西,当然,它并不完美,导致与原生解决方案相同的问题。

function F() {};function clonePF(o) {F.prototype = o;return new F();}
var b = clonePF(a);
b.x = 'b';b.nested.y = 'b';

我把F放在作用域之外,这样我们就可以看看instanceof告诉我们什么。

console.log(a, b);
a --> Object {x: "a",circ: Circ {me: Circ { ... }},nested: Nested {y: "b"}}
b --> F {x: "b",circ: Circ {me: Circ { ... }},nested: Nested {y: "b"}}
console.log(typeof a, typeof b);
a --> objectb --> object
console.log(a instanceof Object, b instanceof Object);
a --> trueb --> true
console.log(a instanceof F, b instanceof F);
a --> falseb --> true

原生解决方案相同的问题,但输出稍差。

更好(但不完美)的解决方案

在挖掘时,我发现了一个类似的问题(在Javascript中,当执行深度复制时,由于属性是“this”,我如何避免循环?),但有一个更好的解决方案。

function cloneDR(o) {const gdcc = "__getDeepCircularCopy__";if (o !== Object(o)) {return o; // primitive value}
var set = gdcc in o,cache = o[gdcc],result;if (set && typeof cache == "function") {return cache();}// elseo[gdcc] = function() { return result; }; // overwriteif (o instanceof Array) {result = [];for (var i=0; i<o.length; i++) {result[i] = cloneDR(o[i]);}} else {result = {};for (var prop in o)if (prop != gdcc)result[prop] = cloneDR(o[prop]);else if (set)result[prop] = cloneDR(cache);}if (set) {o[gdcc] = cache; // reset} else {delete o[gdcc]; // unset again}return result;}
var b = cloneDR(a);
b.x = 'b';b.nested.y = 'b';

让我们来看看输出…

console.log(a, b);
a --> Object {x: "a",circ: Object {me: Object { ... }},nested: Object {y: "a"}}
b --> Object {x: "b",circ: Object {me: Object { ... }},nested: Object {y: "b"}}
console.log(typeof a, typeof b);
a --> objectb --> object
console.log(a instanceof Object, b instanceof Object);
a --> trueb --> true
console.log(a instanceof F, b instanceof F);
a --> falseb --> false

需求是匹配的,但仍有一些较小的问题,包括将nestedcircinstance改为Object

共享一片叶子的树的结构不会被复制,它们将成为两片独立的叶子:

        [Object]                     [Object]/    \                       /    \/      \                     /      \|/_      _\|                 |/_      _\|[Object]    [Object]   ===>  [Object]    [Object]\        /                 |           |\      /                  |           |_\|  |/_                 \|/         \|/[Object]               [Object]    [Object]

结论

使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的真正深度副本。它处理简单的propertiescircular structuresnested object,但它会在克隆时弄乱它们的实例。

jsfiddle

对于那些使用AngularJS的人,也有直接的方法来克隆或扩展这个库中的对象。

var destination = angular.copy(source);

angular.copy(source, destination);

更多angular.copy留档

我在标量对象的情况下尝试过这个,它对我有效:

function binder(i) {return function () {return i;};}
a=1;b=binder(a)(); // copy value of a into b
alert(++a); // 2alert(b); // still 1

问候。

我写了自己的实现。不确定它是否算得上更好的解决方案:

/*a function for deep cloning objects that contains other nested objects and circular structures.objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.index (z)||||||                      depth (x)|_ _ _ _ _ _ _ _ _ _ _ _/_/_/_/_/_/_/_/_/_//_/_/_/_/_/_/_/_/_//_/_/_/_/_/_/.....//.................//.....            //                 //------------------object length (y)    /*/

以下是执行情况:

function deepClone(obj) {var depth = -1;var arr = [];return clone(obj, arr, depth);}
/**** @param obj source object* @param arr 3D array to store the references to objects* @param depth depth of the current object relative to the passed 'obj'* @returns {*}*/function clone(obj, arr, depth){if (typeof obj !== "object") {return obj;}
var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'
var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original objectif(result instanceof Array){result.length = length;}
depth++; // depth is increased because we entered an object here
arr[depth] = []; // this is the x-axis, each index here is the deptharr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)// start the depth at current and go down, cyclic structures won't form on depths more than the current onefor(var x = depth; x >= 0; x--){// loop only if the array at this depth and length already have elementsif(arr[x][length]){for(var index = 0; index < arr[x][length].length; index++){if(obj === arr[x][length][index]){return obj;}}}}
arr[depth][length].push(obj); // store the object in the array at the current depth and lengthfor (var prop in obj) {if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);}
return result;}

基于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属性(如果它存在)(假设它是一个数组),并且对于该数组中的每个元素,调用clone函数以基于第二个模板单独克隆它,该模板仅复制每个标记元素的ab属性。

如果您正在将对象带入和移出节点并且您想控制克隆这些对象的哪些属性,那么这是在node.js中控制它的好方法,并且代码也可以在浏览器中运行。

下面是它的使用示例:http://jsfiddle.net/hjchyLt1/

在ECMAScript 6中,有Object.assign方法,它将所有可枚举自身属性的值从一个对象复制到另一个对象。例如:

var x = {myProp: "value"};var y = Object.assign({}, x);

但是要注意这是一个浅薄的副本-嵌套对象仍然被复制为引用。

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(json_original));

来源:如何通过引用将JavaScript对象复制到新变量?

在一行代码中克隆Javascript对象的优雅方法

Object.assign方法是ECMAScript 2015(ES6)标准的一部分,完全符合您的需要。

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

Object.assign()方法用于将所有可枚举自身属性的值从一个或多个源对象复制到目标对象。

阅读更多…

聚填充支持旧浏览器:

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;}});}

您可以简单地使用扩散特性复制一个没有引用的对象。但要小心(请参阅注释),“复制”只是在最低的对象/数组级别。嵌套属性仍然是引用!


完整克隆:

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:

http://ramdajs.com/docs/#clone

要处理JSON.stringify无法处理的循环对象,您可以引入一个名为JSOG的库,该库将任意图序列化和反序列化为JSON格式。

var clone = JSOG.parse(JSOG.stringify(original));

尝试用这个技巧修补JSOG以进行克隆也可能很有趣(目前没有时间,但如果有人想试一试…):

序列化一个简单的函数:

foo.f = function(a) { return a }var stringForm = foo.f.toString() // "function (a) { return a }"

反序列化一个函数:

eval("foo.f = " + stringForm)

需要一些约定(可能在属性的名称中)来标识函数与常规字符串(可能是@func_f)。

当然,如果函数调用第二个函数,第二个函数将需要像原始函数一样存在。

然而,如果您要接受来自不受信任来源的序列化形式,上面的内容是非常危险的,但是然后接受来自不受信任来源的任何形式的任何函数都是危险的,所以如果您对克隆函数感兴趣,信任必须已经建立(或者您已经打算编写安全漏洞!)。

免责声明:我还没有测试JSOG stringify/parse与JSON stringify/parse的速度,但它确实适用于我测试的简单(循环)对象。

使用npm中的deepcopy。在浏览器和node中都可以作为npm module...使用

https://www.npmjs.com/package/deepcopy

let a = deepcopy(b)

我认为,带有缓存的复发是我们在没有库的情况下可以做的最好的事情。

被低估的弱点地图涉及到循环问题,其中存储对旧对象和新对象的引用可以帮助我们非常容易地重新创建整个树。

我阻止了DOM元素的深度克隆,可能你不想克隆整个页面:)

function deepCopy(object) {const cache = new WeakMap(); // Map of old - new references
function copy(obj) {if (typeof obj !== 'object' ||obj === null ||obj instanceof HTMLElement)return obj; // primitive value or HTMLElement
if (obj instanceof Date)return new Date().setTime(obj.getTime());
if (obj instanceof RegExp)return new RegExp(obj.source, obj.flags);
if (cache.has(obj))return cache.get(obj);
const result = obj instanceof Array ? [] : {};
cache.set(obj, result); // store reference to object before the recursive starts
if (obj instanceof Array) {for(const o of obj) {result.push(copy(o));}return result;}
const keys = Object.keys(obj);
for (const key of keys)result[key] = copy(obj[key]);
return result;}
return copy(object);}

一些测试:

// #1const obj1 = { };const obj2 = { };obj1.obj2 = obj2;obj2.obj1 = obj1; // Trivial circular reference
var copy = deepCopy(obj1);copy == obj1 // falsecopy.obj2 === obj1.obj2 // falsecopy.obj2.obj1.obj2 // and so on - no error (correctly cloned).
// #2const obj = { x: 0 }const clone = deepCopy({ a: obj, b: obj });clone.a == clone.b // true
// #3const arr = [];arr[0] = arr; // A little bit weird but who caresclone = deepCopy(arr)clone == arr // false;clone[0][0][0][0] == clone // true;

注意:我正在使用常量、for of loop、=>运算符和WeakMaps来创建更重要的代码。今天的浏览器支持这种语法(ES6)

MDN

  • 如果您想要浅拷贝,请使用Object.assign({}, a)
  • 对于“深度”复制,使用JSON.parse(JSON.stringify(a))

不需要外部库,但您需要选中浏览器兼容性第一

老问题的新答案!如果您有幸将ECMAScript 2016(ES6)与扩展语法一起使用,那很容易。

keepMeTheSame = {first: "Me!", second: "You!"};cloned = {...keepMeTheSame}

这为对象的浅层副本提供了一种干净的方法。制作深度副本,意味着为每个递归嵌套对象中的每个值制作一个新副本,需要上面更重的解决方案。

JavaScript不断发展。

好的,所以这可能是浅层复制的最佳选择。如果遵循许多使用赋值的示例,但它也保留了继承和原型。它也很简单,适用于大多数类似数组的对象,除了那些具有构造函数要求或只读属性的对象。但这意味着它在类型数组、RegExp、日期、映射、原语的集合和对象版本(布尔、字符串等…)方面失败得很惨。

function copy ( a ) { return Object.assign( new a.constructor, a ) }

其中a可以是任何Object或类构造的实例,但对于使用专门的getter和setter或具有构造函数要求的东西来说也不可靠,但对于更简单的情况,它会摇滚。它也适用于参数。

你也可以将它应用于原语以获得奇怪的结果,但是……除非它最终成为一个有用的黑客,谁在乎呢。

基本内置对象和数组的结果…

> a = { a: 'A', b: 'B', c: 'C', d: 'D' }{ a: 'A', b: 'B', c: 'C', d: 'D' }> b = copy( a ){ a: 'A', b: 'B', c: 'C', d: 'D' }> a = [1,2,3,4][ 1, 2, 3, 4 ]> b = copy( a )[ 1, 2, 3, 4 ]

并且由于均值get/setter、构造函数所需的参数或只读属性以及对父亲的罪恶而失败。

> a = /\w+/g/\w+/g> b = copy( a )  // fails because source and flags are read-only/(?:)/> a = new Date ( '1/1/2001' )2000-12-31T16:00:00.000Z> b = copy( a )  // fails because Date using methods to get and set things2017-02-04T14:44:13.990Z> a = new Boolean( true )[Boolean: true]> b = copy( a )  // fails because of of sins against the father[Boolean: false]> a = new Number( 37 )[Number: 37]> b = copy( a )  // fails because of of sins against the father[Number: 0]> a = new String( 'four score and seven years ago our four fathers' )[String: 'four score and seven years ago our four fathers']> b = copy( a )  // fails because of of sins against the father{ [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' }

在ES-6中,您可以简单地使用Object.assign(…)。例如:

let obj = {person: 'Thor Odinson'};let clone = Object.assign({}, obj);

这里有一个很好的参考:https://googlechrome.github.io/samples/object-assign-es6/

let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

ES6解决方案,如果您想(浅层)克隆类实例而不仅仅是属性对象。

好吧,我知道它有很多答案,但没有人指出,EcmaScript5有分配方法,在FF和Chrome上工作,它复制了可枚举和自己的属性和符号。

对象分配

OK,假设您在下面有这个对象并且想要克隆它:

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

我不知道这对哪些情况不起作用,但它给了我一个数组的副本。我认为它很可爱:)希望它有帮助

copiedArr = origArr.filter(function(x){return true})

这是一个没有Object.assign()陷阱的现代解决方案(不通过引用复制):

const cloneObj = (obj) => {return Object.keys(obj).reduce((dolly, key) => {dolly[key] = (obj[key].constructor === Object) ?cloneObj(obj[key]) :obj[key];return dolly;}, {});};

如果你的对象是一个类(例如https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes):

var copiedObject = jQuery.extend(true, {}, originalObject);copiedObject.__proto__ = originalObject.__proto__;

然后在copiedObject中,您有一个深度复制的originalObject类实例及其所有方法。

如果您使用的是TypeScript,需要支持较旧的Web浏览器(因此不能使用Object.assign),并且没有使用内置克隆方法的库,您可以在几行代码中使自己成为combine助手。它组合对象,如果您只有一个,只需克隆它。

/** Creates a new object that combines the properties of the specified objects. */function combine(...objs: {}[]) {const combined = {};objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));return combined;}

您可以克隆您的Object而无需修改父Object-

    /** [Object Extend]*/( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) {for ( var property in source )destination[property] = source[property];return destination;} ) );/** [/Object Extend]*//** [Object clone]*/( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) {return this.extend( {}, object );} ) );/** [/Object clone]*/
let myObj = {a:1, b:2, c:3, d:{a:1, b:2, c:3}};
let clone = Object.clone( myObj );
clone.a = 10;
console.log('clone.a==>', clone.a); //==> 10
console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here
let clone2 = Object.clone( clone );
clone2.a = 20;
console.log('clone2.a==>', clone2.a); //==> 20
console.log('clone.a==>', clone.a); //==> 10 // object not modified here

我提供了这个问题的答案,因为我在这里没有看到任何解决DOM元素问题的本机递归实现。

问题是<element>具有parentchild属性,它们链接到具有parentchild值的其他元素,这些元素指向原始<element>,导致无限递归循环冗余

如果你的对象是安全和简单的

{'123':456}

…那么这里的任何其他答案都可能奏效。

但如果你有…

{'123':<reactJSComponent>,'456':document.createElement('div'),}

然后你需要这样的东西:

    // cloneVariable() : Clone variable, return null for elements or components.var cloneVariable = function (args) {const variable = args.variable;
if(variable === null) {return null;}
if(typeof(variable) === 'object') {if(variable instanceof HTMLElement || variable.nodeType > 0) {return null;}
if(Array.isArray(variable)) {var arrayclone = [];
variable.forEach((element) => {arrayclone.push(cloneVariable({'variable':element}));});
return arrayclone;}
var objectclone = {};
Object.keys(variable).forEach((field) => {objectclone[field] = cloneVariable({'variable':variable[field]});});
return objectclone;}
return variable;}
const objClone = { ...obj };

注意嵌套对象仍然被复制作为参考。

根据Airbnb JavaScript风格指南有404个贡献者:

首选对象扩展运算符而不是Object.assign浅拷贝对象。使用对象休息运算符获得具有特定属性省略。

// very badconst original = { a: 1, b: 2 };const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠdelete copy.a; // so does this
// badconst original = { a: 1, b: 2 };const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// goodconst original = { a: 1, b: 2 };const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

另外,我想警告你,尽管Airbnb几乎不推荐对象传播操作员的方法。请记住,Microsoft Edge仍然不支持这个2018年的功能。

ES2016+Compat table>>

(以下主要是整合了@maciejbukowski、@A.利维、@扬·图罗、@Redu的答案,以及@LeviRoberts、@RobG的评论,非常感谢他们!!!)

深度复制?-是的!
浅拷贝?-不!(除了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("[")) // isSymbolKeyfnStr = fnStr.replace(/\[Symbol\..+?\]/, '')fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr)? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStrreturn 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 = 1obj1.o.o.g = function g(a = 0, b = 0) { return a + b }obj1.arr[1][1] = 3obj1.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.oobj0.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 Boyboy0.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))

参考文献

  1. Object.create()|MDN
  2. Object.defineProperties()|MDN
  3. 属性的可枚举性和所有权|MDN
  4. TypeError:循环对象值|MDN

使用的语言技巧

  1. 有条件地向对象添加prop

我认为有一个简单而有效的答案。在深度复制中,有两个问题:

  1. 保持属性相互独立。
  2. 并保持克隆对象上的方法活跃。

所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行赋值以复制函数。

let deepCloned = JSON.parse(JSON.stringify(source));let merged = Object.assign({}, source);Object.assign(merged, deepCloned);

虽然这个问题有很多答案,但我希望这个问题也有帮助。

使用默认(历史上特定于nodejs,但由于现代JS,现在可以从浏览器中使用):

import defaults from 'object.defaults';
const myCopy = defaults({}, myObject);

为了支持更好地理解对象的复制,这个说明性的jsbin可能有价值

class base {get under(){return true}}
class a extends base {}
const b = {get b1(){return true},b: true}
console.log('Object assign')let t1 = Object.create(b)t1.x = trueconst c = Object.assign(t1, new a())console.log(c.b1 ? 'prop value copied': 'prop value gone')console.log(c.x ? 'assigned value copied': 'assigned value gone')console.log(c.under ? 'inheritance ok': 'inheritance gone')console.log(c.b1 ? 'get value unchanged' : 'get value lost')c.b1 = falseconsole.log(c.b1? 'get unchanged' : 'get lost')console.log('-----------------------------------')console.log('Object assign  - order swopped')t1 = Object.create(b)t1.x = trueconst d = Object.assign(new a(), t1)console.log(d.b1 ? 'prop value copied': 'prop value gone')console.log(d.x ? 'assigned value copied': 'assigned value gone')console.log(d.under ? 'inheritance n/a': 'inheritance gone')console.log(d.b1 ? 'get value copied' : 'get value lost')d.b1 = falseconsole.log(d.b1? 'get copied' : 'get lost')console.log('-----------------------------------')console.log('Spread operator')t1 = Object.create(b)t2 = new a()t1.x = trueconst e = { ...t1, ...t2 }console.log(e.b1 ? 'prop value copied': 'prop value gone')console.log(e.x ? 'assigned value copied': 'assigned value gone')console.log(e.under ? 'inheritance ok': 'inheritance gone')console.log(e.b1 ? 'get value copied' : 'get value lost')e.b1 = falseconsole.log(e.b1? 'get copied' : 'get lost')console.log('-----------------------------------')console.log('Spread operator on getPrototypeOf')t1 = Object.create(b)t2 = new a()t1.x = trueconst e1 = { ...Object.getPrototypeOf(t1), ...Object.getPrototypeOf(t2) }console.log(e1.b1 ? 'prop value copied': 'prop value gone')console.log(e1.x ? 'assigned value copied': 'assigned value gone')console.log(e1.under ? 'inheritance ok': 'inheritance gone')console.log(e1.b1 ? 'get value copied' : 'get value lost')e1.b1 = falseconsole.log(e1.b1? 'get copied' : 'get lost')console.log('-----------------------------------')console.log('keys, defineProperty, getOwnPropertyDescriptor')f = Object.create(b)t2 = new a()f.x = 'a'Object.keys(t2).forEach(key=> {Object.defineProperty(f,key,Object.getOwnPropertyDescriptor(t2, key))})console.log(f.b1 ? 'prop value copied': 'prop value gone')console.log(f.x ? 'assigned value copied': 'assigned value gone')console.log(f.under ? 'inheritance ok': 'inheritance gone')console.log(f.b1 ? 'get value copied' : 'get value lost')f.b1 = falseconsole.log(f.b1? 'get copied' : 'get lost')console.log('-----------------------------------')console.log('defineProperties, getOwnPropertyDescriptors')let g = Object.create(b)t2 = new a()g.x = 'a'Object.defineProperties(g,Object.getOwnPropertyDescriptors(t2))console.log(g.b1 ? 'prop value copied': 'prop value gone')console.log(g.x ? 'assigned value copied': 'assigned value gone')console.log(g.under ? 'inheritance ok': 'inheritance gone')console.log(g.b1 ? 'get value copied' : 'get value lost')g.b1 = falseconsole.log(g.b1? 'get copied' : 'get lost')console.log('-----------------------------------')

对于深度复制和克隆,JSON.stringifyJSON.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}}

正如此链接所说,使用此代码:

let clone = Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj));

简单

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}

使用_cloneDeep().

浅拷贝:洛塔什_. 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}}}

浅拷贝_. clone()

Deep Copy: 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}}}

深度复制_. cloneDeep

使用()复制对象

//badconst original = { a: 1, b: 2 };const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2,c: 3 }
//goodconst originalObj = { id: 5, name: 'San Francisco'};const copyObject = {...originalObj, pincode: 4444};console.log(copyObject)  //{ id: 5, name: 'San Francisco', pincode: 4444 }

同样可以用于从一个到另一个的复制数组

const itemsCopy = [...items];
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}

克隆对象的简单递归方法。也可以使用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);

解决方案JSON.parse(JSON.stringify(orig_obj)正如许多同行在这里为deep_cloning所述,我发现了几个问题,它们列在下面:

  1. 它在复制原始对象中值为undefined的条目时丢弃这些条目,
  2. 如果有一些值,如InfinityNaN等,它们将在复制时转换为null
  3. 如果原始对象中存在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 issuesfor(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);

性能

今天2020.04.30我在MacO High Sierra v10.13.6Chromev81.0、Safariv13.1和Firefox v75.0上执行所选解决方案的测试。

我专注于复制DATA(具有简单类型字段的对象,而不是方法等)的速度。解决方案A-I只能进行浅复制,解决方案J-U可以进行深度复制。

浅拷贝的结果

  • 解决方案{...obj}(A)在Chrome和Firefox上最快,在Safari上中等速度
  • 基于Object.assign(B)的解决方案在所有浏览器上都很快
  • jQuery(E)和洛达什(F、G、H)解决方案中等/相当快
  • 解决方案JSON.parse/stringify(K)非常慢
  • 解决方案D和U在所有浏览器上都很慢

在此处输入图片描述

深度复制的结果

  • 解决方案Q在所有浏览器上最快
  • jQuery(L)和洛达什(J)是中等速度
  • 解决方案JSON.parse/stringify(K)非常慢
  • 解决方案U在所有浏览器上最慢
  • Lodash(J)和解决方案U在1000级深目标的Chrome上崩溃

在此处输入图片描述

详情

对于选择的解决方案:ABC(我的)DEFGHIJKLMNOPQRSTU,我做了4个测试

  • 浅层小:具有10个非嵌套字段的对象-您可以运行它这里
  • Shash-Big:具有1000个非嵌套字段的对象-您可以运行它这里
  • 深度小:具有10个级别嵌套字段的对象-您可以运行它这里
  • Deep-Big:具有1000个级别嵌套字段的对象-您可以运行它这里

测试中使用的对象显示在下面的片段中

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 undefinedif (null == obj || "object" != typeof obj) return obj;
// Handle Dateif (obj instanceof Date) {copy = new Date();copy.setTime(obj.getTime());return copy;}
// Handle Arrayif (obj instanceof Array) {copy = [];for (var i = 0, len = obj.length; i < len; i++) {copy[i] = Q(obj[i]);}return copy;}
// Handle Objectif (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();}// elseobj[gdcc] = function() { return result; }; // overwriteif (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 finishedvar 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 Dateif (obj instanceof Date) {var copy = new Date();copy.setTime(obj.getTime());return copy;}
// Handle Arrayif (obj instanceof Array) {var copy = [];for (var i = 0; i < obj.length; ++i) {copy[i] = cloneRecursive(obj[i]);}return copy;}
// Handle Objectif (obj instanceof Object) {if (clonedObjectsArray[objectId(obj)] != undefined)return clonedObjectsArray[objectId(obj)];
var copy;if (obj instanceof Function)//Handle Functioncopy = function(){return obj.apply(this, arguments);};elsecopy = {};
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 idsfor (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 copydeep - solution create deep copyfunc - solution copy functionscirc - solution can copy object with circular referencesundefined - solution copy fields with undefined valuedate - solution can copy dateRegExp - solution can copy fields with regular expressionsbigInt - solution can copy BigInt`)

// ------------------------// Helper functions// ------------------------

function deepCompare(obj1,obj2) {return JSON.stringify(obj1)===JSON.stringify(obj2);}
function getCase() { // pure data casereturn {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 = falsetry{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 objectlet oB = getCase(); // "backup" used for shallow valid test  
let c1 = f(o); // copy 1 for referencelet c2 = f(o); // copy 2 for test shallow valueslet c3 = f(o); // copy 3 for test deep values
let is_proper_copy = deepCompare(c1,o);  // shoud be true  
// shallow changeslet 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的示例结果

在此处输入图片描述

更新06七月2020

在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' }

这可以作为参考摘要。

复制对象最正确的方法是useObject.create

Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

这种表示法将使相同的对象具有正确的原型和隐藏属性。

在JavaScript中复制对象的方法

  1. 使用传播(...)语法
  2. 使用Object.assign()方法
  3. 使用JSON.stringify()JSON.parse()方法
const person = {firstName: 'John',lastName: 'Doe'};
// using spread ...let p1 = {...person};
// using  Object.assign() methodlet p2 = Object.assign({}, person);
// using JSONlet p3 = JSON.parse(JSON.stringify(person));

您可以使用rest运算符来克隆数组或对象

let myObj = {1: 100, 'a': 200};
let clone = {...myObj};
clone.a = 300;
console.log(clone.a) // Output :- 300console.log(myObj.a) // Output :- 200

这使新副本成为你的obj(不仅仅是引用)。

let myCopy = JSON.parse(JSON.stringify(obj));

…工作效率高,然后_.cloneDeep(obj)

我已经完成了上述所有解决方案,它们都很好。但是,还有另一种方法可以用来克隆对象(使用值而不是引用)。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)

原生JS:

const shallowClone = {...originalObj};const deepClone = JSON.parse(JSON.stringify(originalObj));

使用库:

// Lodashconst shallowClone = _.clone(originalObj);const deepClone = _. cloneDeep(originalObj);
// JQueryconst shallowClone = jQuery.extend({}, originalObj);const deepClone = jQuery.extend(true, {}, originalObj);
// Angularconst deepClone = angular.copy(originalObj);

短而甜:

let clone = Object.fromEntries(Object.entries(obj));

演示:

let obj = {a: 'b'};let clone = Object.fromEntries(Object.entries(obj));
clone.a = 'c';
console.log(obj, clone);

使用扩展语法执行对象的浅拷贝。这意味着没有任何嵌套对象实例被克隆,正如您在下面的嵌套对象child示例中看到的那样

const user1 = {name: 'Alex',address: '15th Park Avenue',age: 43,child:{name: 'John'}}
const user2 = {...user1};
user1.child.name = 'chris';
console.log(user1);console.log(user2);

要解决这个嵌套对象问题并执行深度复制,我们可以使用JSON.parse(JSON.stringify(someObject))

const user1 = {name: 'Alex',address: '15th Park Avenue',age: 43,child:{name: 'John'}}
const user2 = JSON.parse(JSON.stringify(user1));
user1.child.name = 'chris';
console.log(user1);console.log(user2);

的不同

只复制顶级:{...object}Object.assign({}, object)

let objA = {a: "keyA",b: {c: "keyC",}}let objB = Object.assign({}, objA); // or  {...objB}// change objBobjB.a = "Change objA.a (top)"console.log("objA.a (top) No Change:\n" + JSON.stringify(objA, false, 2));
objB.b.c = "change should be only for objB.b.c but it in objA.b.c"console.log("objA.a.c second level has Change:\n" + JSON.stringify(objA, false, 2));

对于深度复制,使用#0 2022或JSON.parse(JSON.stringify(object))旧浏览器,轻松无黑客。

let objA = {a: "keyA",b: {c: "keyC",}}let objB = typeof structuredClone == 'function' ?structuredClone(objA) : JSON.parse(JSON.stringify(objA));// change objBobjB.a = "Change objA.a (top)"objB.b.c = "change should be only for objB.c but it in objA.c"
console.log("objA has no Change:\n" + JSON.stringify(objA, false, 2));

我在复制对象时遇到了一个问题。这是因为当你做以下操作时,你只是对对象进行了“引用”,当源对象值稍后更新时,克隆的复制对象也会更改值,因为它只是一个“引用”,因此你会看到源对象最后更改的多个值。

let x = { a: 1 };let y = x; // y is a reference to x, so if x changes y also changes and v/v

因此,要解决这个问题,您需要执行以下操作:

let y = JSON.parse(JSON.stringify(x)); //see Note below

防止引用的另一种方法是执行以下操作:

let x = { a: 1 };let y = Object.assign({}, x); // Object.assign(target, ...sources)
y.a = 2;console.log(x); // { a: 1 }console.log(y); // { a: 2 }

注:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#warning_for_deep_clone

我找到了一种使用函数克隆对象的方法(断开多行以便于理解):

const clone = Object.assign(Object.create(Object.getPrototypeOf(originalObject)),dataObject);