在JavaScript中深度克隆对象的最有效方法是什么?

克隆JavaScript对象的最有效方法是什么?我见过obj = eval(uneval(o));被使用,但是这是非标准的,只有Firefox支持

我做过类似obj = JSON.parse(JSON.stringify(o));的事情,但质疑效率。

我还见过带有各种缺陷的递归复制函数。
我很惊讶没有规范的解决方案存在。

2688768 次浏览

如果没有内置的,你可以尝试:

function clone(obj) {if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)return obj;
if (obj instanceof Date)var temp = new obj.constructor(); //or new Date(obj);elsevar temp = obj.constructor();
for (var key in obj) {if (Object.prototype.hasOwnProperty.call(obj, key)) {obj['isActiveClone'] = null;temp[key] = clone(obj[key]);delete obj['isActiveClone'];}}return temp;}
function clone(obj){ var clone = {};clone.prototype = obj.prototype;for (property in obj) clone[property] = obj[property];return clone;}

原生深度克隆

现在有一个名为"结构化克隆"的JS标准,它在Node 11及更高版本中进行实验,将登陆浏览器,并且具有用于现有系统的PolyFill

structuredClone(value)

如果需要,请先加载PolyFill:

import structuredClone from '@ungap/structured-clone';

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

老答案

数据丢失的快速克隆-JSON.parse/stringify

如果你不在你的对象中使用Date、函数、undefinedInfinity、RegExps、Maps、Sets、Blob、FileList、ImageDatas、稀疏数组、类型化数组或其他复杂类型,一个非常简单的用于深度克隆一个对象的方法是:

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're: /.*/,  // lost}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()

基准测试见Corban的回答

使用库进行可靠的克隆

由于克隆对象并不简单(复杂类型、循环引用、函数等),大多数主要库都提供了克隆对象的函数。不要重新发明轮子-如果您已经在使用库,请检查它是否有对象克隆函数。例如,

  • Lodash-#0;可以通过lodash.clonedeep模块单独导入,如果您还没有使用提供深度克隆功能的库,它可能是您的最佳选择
  • AngularJS-#0
  • jQuery-#0.clone()只克隆DOM元素
  • 只是库-#0;零依赖npm模块库的一部分,只做一件事。在任何场合都可以免费使用。

代码:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returnedfunction extend(from, to){if (from == null || typeof from != "object") return from;if (from.constructor != Object && from.constructor != Array) return from;if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||from.constructor == String || from.constructor == Number || from.constructor == Boolean)return new from.constructor(from);
to = to || new from.constructor();
for (var name in from){to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];}
return to;}

测试:

var obj ={date: new Date(),func: function(q) { return 1 + q; },num: 123,text: "asdasd",array: [1, "asd"],regex: new RegExp(/aaa/i),subobj:{num: 234,text: "asdsaD"}}
var clone = extend(obj);

这就是我正在使用的:

function cloneObject(obj) {var clone = {};for(var i in obj) {if(typeof(obj[i])=="object" && obj[i] != null)clone[i] = cloneObject(obj[i]);elseclone[i] = obj[i];}return clone;}
var clone = function() {var newObj = (this instanceof Array) ? [] : {};for (var i in this) {if (this[i] && typeof this[i] == "object") {newObj[i] = this[i].clone();}else{newObj[i] = this[i];}}return newObj;};
Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
// obj target object, vals source objectvar setVals = function (obj, vals) {if (obj && vals) {for (var x in vals) {if (vals.hasOwnProperty(x)) {if (obj[x] && typeof vals[x] === 'object') {obj[x] = setVals(obj[x], vals[x]);} else {obj[x] = vals[x];}}}}return obj;};

Crockford建议(我更喜欢)使用这个函数:

function object(o) {function F() {}F.prototype = o;return new F();}
var newObject = object(oldObject);

它很简洁,按预期工作,你不需要图书馆。


编辑:

这是一个Object.create的多边形填充,所以你也可以使用它。

var newObject = Object.create(oldObject);

注:如果你使用其中的一些,你可能会在使用hasOwnProperty的一些迭代中遇到问题。因为,create创建了继承oldObject的新空对象。但它对于克隆对象仍然有用和实用。

例如如果oldObject.a = 5;

newObject.a; // is 5

但是:

oldObject.hasOwnProperty(a); // is truenewObject.hasOwnProperty(a); // is false

对于类似数组的对象,似乎还没有理想的深度克隆运算符。正如下面的代码所示,John Resig的jQuery克隆器将具有非数字属性的数组转换为不是数组的对象,而RegDwight的JSON克隆器丢弃了非数字属性。以下测试在多个浏览器上说明了这些点:

function jQueryClone(obj) {return jQuery.extend(true, {}, obj)}
function JSONClone(obj) {return JSON.parse(JSON.stringify(obj))}
var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];arrayLikeObj.names = ["m", "n", "o"];var JSONCopy = JSONClone(arrayLikeObj);var jQueryCopy = jQueryClone(arrayLikeObj);
alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +"\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +"\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +"\nAnd what are the JSONClone names? " + JSONCopy.names)

假设你的对象中只有属性而没有任何函数,你可以只使用:

var newObject = JSON.parse(JSON.stringify(oldObject));

检查此基准:http://jsben.ch/#/bWfk9

在我之前的测试中,速度是我发现的主要问题

JSON.parse(JSON.stringify(obj))

是深度克隆对象的最慢方法(它比jQuery.extend慢,deep标志设置为true 10-20%)。

deep标志设置为false(浅克隆)时,jQuery.extend非常快。这是一个很好的选择,因为它包含了一些额外的类型验证逻辑,不会复制未定义的属性等,但这也会让你慢一点。

如果你知道你要克隆的对象的结构,或者可以避免深度嵌套数组,你可以写一个简单的for (var i in obj)循环来克隆你的对象,同时检查hasOwnProperty,它会比jQuery快得多。

最后,如果您试图在热循环中克隆一个已知的对象结构,您可以通过简单地内联克隆过程并手动构建对象来获得更多的性能。

JavaScript跟踪引擎在优化for..in循环和检查hasOwnProperty方面也会减慢你的速度。当速度是绝对必须的时,手动克隆。

var clonedObject = {knownProp: obj.knownProp,..}

注意在Date对象上使用JSON.parse(JSON.stringify(obj))方法-JSON.stringify(new Date())以ISO格式返回日期的字符串表示,JSON.parse()不要将其转换回Date对象。查看此答案了解更多详情

此外,请注意,至少在65Chrome,本机克隆不是可行的方法。根据JSPerf的说法,通过创建新函数执行本机克隆几乎比使用JSON.stringify慢800x,后者在整个过程中都非常快。

ES6更新

如果您使用的是Javascript ES6,请尝试此本机方法进行克隆或浅拷贝。

Object.assign({}, obj);

我认为这是最好的解决方案,如果你想推广你的对象克隆算法。它可以与jQuery一起使用或不使用,尽管如果您希望克隆的对象与原始对象具有相同的“类”,我建议保留jQuery的扩展方法。

function clone(obj){if(typeof(obj) == 'function')//it's a simple functionreturn obj;//of it's not an object (but could be an array...even if in javascript arrays are objects)if(typeof(obj) !=  'object' || obj.constructor.toString().indexOf('Array')!=-1)if(JSON != undefined)//if we have the JSON objtry{return JSON.parse(JSON.stringify(obj));}catch(err){return JSON.parse('"'+JSON.stringify(obj)+'"');}elsetry{return eval(uneval(obj));}catch(err){return eval('"'+uneval(obj)+'"');}// I used to rely on jQuery for this, but the "extend" function returns//an object similar to the one cloned,//but that was not an instance (instanceof) of the cloned class/*if(jQuery != undefined)//if we use the jQuery pluginreturn jQuery.extend(true,{},obj);else//we recursivley clone the object*/return (function _clone(obj){if(obj == null || typeof(obj) != 'object')return obj;function temp () {};temp.prototype = obj;var F = new temp;for(var key in obj)F[key] = clone(obj[key]);return F;})(obj);}

这通常不是最有效的解决方案,但它确实满足了我的需要。

function clone(obj, clones) {// Makes a deep copy of 'obj'. Handles cyclic structures by// tracking cloned obj's in the 'clones' parameter. Functions// are included, but not cloned. Functions members are cloned.var new_obj,already_cloned,t = typeof obj,i = 0,l,pair;
clones = clones || [];
if (obj === null) {return obj;}
if (t === "object" || t === "function") {
// check to see if we've already cloned objfor (i = 0, l = clones.length; i < l; i++) {pair = clones[i];if (pair[0] === obj) {already_cloned = pair[1];break;}}
if (already_cloned) {return already_cloned;} else {if (t === "object") { // create new objectnew_obj = new obj.constructor();} else { // Just use functions as isnew_obj = obj;}
clones.push([obj, new_obj]); // keep track of objects we've cloned
for (key in obj) { // clone object membersif (obj.hasOwnProperty(key)) {new_obj[key] = clone(obj[key], clones);}}}}return new_obj || obj;}

循环阵列测试…

a = []a.push("b", "c", a)aa = clone(a)aa === a //=> falseaa[2] === a //=> falseaa[2] === a[2] //=> falseaa[2] === aa //=> true

功能测试…

f = new Functionf.a = aff = clone(f)ff === f //=> trueff.a === a //=> false

这是我创建的不使用原型的最快方法,因此它将在新对象中维护hasOwnProperty。

解决方案是迭代原始对象的顶级属性,创建两个副本,从原始对象中删除每个属性,然后重置原始对象并返回新副本。它只需要迭代顶级属性的次数。这保存了所有if条件来检查每个属性是否是函数、对象、字符串等,并且不必迭代每个后代属性。

唯一的缺点是必须为原始对象提供其原始创建的命名空间,以便重置它。

copyDeleteAndReset:function(namespace,strObjName){var obj = namespace[strObjName],objNew = {},objOrig = {};for(i in obj){if(obj.hasOwnProperty(i)){objNew[i] = objOrig[i] = obj[i];delete obj[i];}}namespace[strObjName] = objOrig;return objNew;}
var namespace = {};namespace.objOrig = {'0':{innerObj:{a:0,b:1,c:2}}}
var objNew = copyDeleteAndReset(namespace,'objOrig');objNew['0'] = 'NEW VALUE';
console.log(objNew['0']) === 'NEW VALUE';console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};

我知道这是一篇旧文章,但我认为这可能对下一个跌跌撞撞的人有所帮助。

只要你不将对象分配给它在内存中没有引用的任何东西。所以要制作一个你想在其他对象之间共享的对象,你必须创建一个工厂,如下所示:

var a = function(){return {father:'zacharias'};},b = a(),c = a();c.father = 'johndoe';alert(b.father);

如果你正在使用它,Underscore.js库有一个克隆方法。

var newObject = _.clone(oldObject);

结构化克隆

2022更新#0全局函数已经在Firefox 94、Node 17和Deno 1.14中可用

超文本标记语言标准包括一种内部结构化克隆/序列化算法,它可以创建对象的深度克隆。它仍然限于某些内置类型,但除了JSON支持的几种类型之外,它还支持Dates、RegExps、Maps、Sets、Blob、FileList、ImageDatas、稀疏数组、类型化数组,将来可能会更多。它还保留克隆数据中的引用,允许它支持会导致JSON错误的循环和递归结构。

支持Node.js:

#0全局函数由Node 17.0提供:

const clone = structuredClone(original);

以前的版本:Node.js(从Node 11开始)直接公开结构化序列化API中的v8模块,但此功能仍标记为“实验”,可能会在未来版本中更改或删除。如果您使用的是兼容版本,克隆对象就像以下操作一样简单:

const v8 = require('v8');
const structuredClone = obj => {return v8.deserialize(v8.serialize(obj));};

浏览器中的直接支持:在Firefox 94中可用

#0全局函数很快将被所有主要浏览器提供(之前在GitHub上的Whatwg/html#793中讨论过)。它看起来/将看起来像这样:

const clone = structuredClone(original);

在发布之前,浏览器的结构化克隆实现仅间接地公开。

异步解决方法:可用。😕

使用现有API创建结构化克隆的低开销方法是通过消息渠道的一个端口发布数据。另一个端口将发出一个message事件,其中包含附加.data的结构化克隆。不幸的是,侦听这些事件必然是异步的,同步替代方案不太实用。

class StructuredCloner {constructor() {this.pendingClones_ = new Map();this.nextKey_ = 0;    
const channel = new MessageChannel();this.inPort_ = channel.port1;this.outPort_ = channel.port2;    
this.outPort_.onmessage = ({data: {key, value}}) => {const resolve = this.pendingClones_.get(key);resolve(value);this.pendingClones_.delete(key);};this.outPort_.start();}
cloneAsync(value) {return new Promise(resolve => {const key = this.nextKey_++;this.pendingClones_.set(key, resolve);this.inPort_.postMessage({key, value});});}}
const structuredCloneAsync = window.structuredCloneAsync =StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

示例使用:

const main = async () => {const original = { date: new Date(), number: Math.random() };original.self = original;
const clone = await structuredCloneAsync(original);
// They're different objects:console.assert(original !== clone);console.assert(original.date !== clone.date);
// They're cyclical:console.assert(original.self === original);console.assert(clone.self === clone);
// They contain equivalent values:console.assert(original.number === clone.number);console.assert(Number(original.date) === Number(clone.date));  
console.log("Assertions complete.");};
main();

同步解决方法:可怕!🤢

同步创建结构化克隆没有好的选择。这里有几个不切实际的技巧。

history.pushState()history.replaceState()都创建了它们的第一个参数的结构化克隆,并将该值分配给history.state。您可以使用它来创建任何对象的结构化克隆,如下所示:

const structuredClone = obj => {const oldState = history.state;history.replaceState(obj, null);const clonedObj = history.state;history.replaceState(oldState, null);return clonedObj;};

示例使用:

'use strict';
const main = () => {const original = { date: new Date(), number: Math.random() };original.self = original;
const clone = structuredClone(original);  
// They're different objects:console.assert(original !== clone);console.assert(original.date !== clone.date);
// They're cyclical:console.assert(original.self === original);console.assert(clone.self === clone);
// They contain equivalent values:console.assert(original.number === clone.number);console.assert(Number(original.date) === Number(clone.date));  
console.log("Assertions complete.");};
const structuredClone = obj => {const oldState = history.state;history.replaceState(obj, null);const clonedObj = history.state;history.replaceState(oldState, null);return clonedObj;};
main();

虽然是同步的,但这可能非常慢。它会产生与操作浏览器历史记录相关的所有开销。重复调用此方法可能会导致Chrome暂时无响应。

#0构造函数创建其关联数据的结构化克隆。它还尝试向用户显示浏览器通知,但这将静默失败,除非您请求通知权限。如果您有其他目的的权限,我们将立即关闭我们创建的通知。

const structuredClone = obj => {const n = new Notification('', {data: obj, silent: true});n.onshow = n.close.bind(n);return n.data;};

示例使用:

'use strict';
const main = () => {const original = { date: new Date(), number: Math.random() };original.self = original;
const clone = structuredClone(original);  
// They're different objects:console.assert(original !== clone);console.assert(original.date !== clone.date);
// They're cyclical:console.assert(original.self === original);console.assert(clone.self === clone);
// They contain equivalent values:console.assert(original.number === clone.number);console.assert(Number(original.date) === Number(clone.date));  
console.log("Assertions complete.");};
const structuredClone = obj => {const n = new Notification('', {data: obj, silent: true});n.close();return n.data;};
main();

浅拷贝单行(ECMAScript第5版):

var origin = { foo : {} };var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});
console.log(origin, copy);console.log(origin == copy); // falseconsole.log(origin.foo == copy.foo); // true

浅层复制单行(ECMAScript第6版,2015):

var origin = { foo : {} };var copy = Object.assign({}, origin);
console.log(origin, copy);console.log(origin == copy); // falseconsole.log(origin.foo == copy.foo); // true

这是一个全面的clone()方法,可以克隆任何JavaScript对象。它处理几乎所有的情况:

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

有一个库(称为“克隆”),它做得很好。它提供了我所知道的最完整的递归克隆/复制任意对象。它还支持循环引用,这是其他答案没有涵盖的。

您也可以npm上找到。它可以用于浏览器以及Node.js.

下面是一个如何使用它的例子:

安装它与

npm install clone

或者用安德包装它。

ender build clone [...]

您也可以手动下载源代码。

然后您可以在源代码中使用它。

var clone = require('clone');
var a = { foo: { bar: 'baz' } };  // inital value of avar b = clone(a);                 // clone a -> ba.foo.bar = 'foo';                // change a
console.log(a);                   // { foo: { bar: 'foo' } }console.log(b);                   // { foo: { bar: 'baz' } }

(免责声明:我是图书馆的作者。

这是上面ConRoyP答案的一个版本,即使构造函数具有必需的参数也可以工作:

//If Object.create isn't already defined, we just do the simple shim,//without the second argument, since that's all we need herevar object_create = Object.create;if (typeof object_create !== 'function') {object_create = function(o) {function F() {}F.prototype = o;return new F();};}
function deepCopy(obj) {if(obj == null || typeof(obj) !== 'object'){return obj;}//make sure the returned object has the same prototype as the originalvar ret = object_create(obj.constructor.prototype);for(var key in obj){ret[key] = deepCopy(obj[key]);}return ret;}

这个函数也可以在我的简单库中使用。

编辑:

这是一个更强大的版本(感谢Justin McCandless,它现在也支持循环引用):

/*** Deep copy an object (make copies of all its object properties, sub-properties, etc.)* An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone* that doesn't break if the constructor has required parameters** It also borrows some code from http://stackoverflow.com/a/11621004/560114*/function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {if(src === null || typeof(src) !== 'object'){return src;}
//Honor native/custom clone methodsif(typeof src.clone == 'function'){return src.clone(true);}
//Special cases://Dateif(src instanceof Date){return new Date(src.getTime());}//RegExpif(src instanceof RegExp){return new RegExp(src);}//DOM Elementif(src.nodeType && typeof src.cloneNode == 'function'){return src.cloneNode(true);}
// Initialize the visited objects arrays if needed.// This is used to detect cyclic references.if (_visited === undefined){_visited = [];_copiesVisited = [];}
// Check if this object has already been visitedvar i, len = _visited.length;for (i = 0; i < len; i++) {// If so, get the copy we already madeif (src === _visited[i]) {return _copiesVisited[i];}}
//Arrayif (Object.prototype.toString.call(src) == '[object Array]') {//[].slice() by itself would soft clonevar ret = src.slice();
//add it to the visited array_visited.push(src);_copiesVisited.push(ret);
var i = ret.length;while (i--) {ret[i] = deepCopy(ret[i], _visited, _copiesVisited);}return ret;}
//If we've reached here, we have a regular object
//make sure the returned object has the same prototype as the originalvar proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);if (!proto) {proto = src.constructor.prototype; //this line would probably only be reached by very old browsers}var dest = object_create(proto);
//add this object to the visited array_visited.push(src);_copiesVisited.push(dest);
for (var key in src) {//Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.//For an example of how this could be modified to do so, see the singleMixin() functiondest[key] = deepCopy(src[key], _visited, _copiesVisited);}return dest;}
//If Object.create isn't already defined, we just do the simple shim,//without the second argument, since that's all we need herevar object_create = Object.create;if (typeof object_create !== 'function') {object_create = function(o) {function F() {}F.prototype = o;return new F();};}

我有两个很好的答案,这取决于你的目标是否是克隆一个“普通的旧JavaScript对象”。

我们还假设您的意图是创建一个完整的克隆,没有返回源对象的原型引用。如果您对完整的克隆不感兴趣,那么您可以使用其他一些答案中提供的许多Object.clone()例程(Crockford的模式)。

对于普通的旧JavaScript对象,在现代运行时中克隆对象的一种可靠且真正的好方法非常简单:

var clone = JSON.parse(JSON.stringify(obj));

请注意,源对象必须是纯JSON对象。也就是说,它的所有嵌套属性必须是标量(如布尔值、字符串、数组、对象等)。任何函数或特殊对象,如RegExp或Date,都不会被克隆。

效率高吗?当然高。我们尝试过各种克隆方法,这种方法效果最好。我相信有些忍者能想出更快的方法。但我怀疑我们说的是边际收益。

这种方法简单且易于实现。将其包装成一个便利函数,如果您真的需要挤出一些收益,请稍后再使用。

现在,对于非纯JavaScript对象,没有一个真正简单的答案。事实上,不可能有,因为JavaScript函数和内部对象状态的动态特性。深度克隆带有函数的JSON结构需要您重新创建这些函数及其内部上下文。而JavaScript根本没有标准化的方式来做到这一点。

同样,正确的方法是通过您在代码中声明和重用的方便方法。方便方法可以赋予您对自己的对象的一些理解,以便您可以确保在新对象中正确重新创建图形。

我们是自己写的,但我见过的最好的一般方法在这里介绍:

http://davidwalsh.name/javascript-clone

这是正确的想法。作者(David Walsh)注释了广义函数的克隆。这是您可能选择做的事情,具体取决于您的用例。

主要思想是您需要在每种类型的基础上特殊处理函数(或原型类)的实例化。在这里,他提供了RegExp和Date的一些示例。

这段代码不仅简短,而且可读性很强。很容易扩展。

这有效吗?是的。鉴于目标是生成真正的深度复制克隆,那么你将不得不遍历源对象图的成员。通过这种方法,你可以准确调整要处理哪些子成员以及如何手动处理自定义类型。

就这样。两种方法。在我看来都是有效的。

Lodash有一个不错的_cloneDeep(value)方法:

var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);console.log(deep[0] === objects[0]);// => false

答案有很多,但没有一个能达到我想要的效果。我想利用jQuery的深度复制功能……然而,当它遇到数组时,它只是将引用复制到数组并深度复制其中的项目。为了解决这个问题,我做了一个不错的小递归函数,它会自动创建一个新数组。

(如果你愿意,它甚至会检查kendo.data.ObservableArray!但是,如果你希望数组再次可观察,请确保调用kendo.observable(newItem)。)

因此,要完全复制现有项目,只需这样做:

var newItem = jQuery.extend(true, {}, oldItem);createNewArrays(newItem);

function createNewArrays(obj) {for (var prop in obj) {if ((kendo != null && obj[prop] instanceof kendo.data.ObservableArray) || obj[prop] instanceof Array) {var copy = [];$.each(obj[prop], function (i, item) {var newChild = $.extend(true, {}, item);createNewArrays(newChild);copy.push(newChild);});obj[prop] = copy;}}}

我通常使用var newObj = JSON.parse( JSON.stringify(oldObje) );,但是,这里有一个更合适的方法:

var o = {};
var oo = Object.create(o);
(o === oo); // => false

观看传统浏览器!

这是我的对象克隆版本。这是jQuery方法的独立版本,只有很少的调整和调整。查看小提琴。我使用了很多jQuery,直到有一天我意识到我大部分时间都只使用这个函数x_x。

用法与jQuery API中的描述相同:

  • 非深度克隆:extend(object_dest, object_source);
  • 深度克隆:extend(true, object_dest, object_source);

一个额外的函数用于定义对象是否适合被克隆。

/*** This is a quasi clone of jQuery's extend() function.* by Romain WEEGER for wJs library - www.wexample.com* @returns {*|{}}*/function extend() {// Make a copy of arguments to avoid JavaScript inspector hints.var to_add, name, copy_is_array, clone,
// The target object who receive parameters// form other objects.target = arguments[0] || {},
// Index of first argument to mix to target.i = 1,
// Mix target with all function arguments.length = arguments.length,
// Define if we merge object recursively.deep = false;
// Handle a deep copy situation.if (typeof target === 'boolean') {deep = target;
// Skip the boolean and the target.target = arguments[ i ] || {};
// Use next object as first added.i++;}
// Handle case when target is a string or something (possible in deep copy)if (typeof target !== 'object' && typeof target !== 'function') {target = {};}
// Loop trough arguments.for (false; i < length; i += 1) {
// Only deal with non-null/undefined valuesif ((to_add = arguments[ i ]) !== null) {
// Extend the base object.for (name in to_add) {
// We do not wrap for loop into hasOwnProperty,// to access to all values of object.// Prevent never-ending loop.if (target === to_add[name]) {continue;}
// Recurse if we're merging plain objects or arrays.if (deep && to_add[name] && (is_plain_object(to_add[name]) || (copy_is_array = Array.isArray(to_add[name])))) {if (copy_is_array) {copy_is_array = false;clone = target[name] && Array.isArray(target[name]) ? target[name] : [];}else {clone = target[name] && is_plain_object(target[name]) ? target[name] : {};}
// Never move original objects, clone them.target[name] = extend(deep, clone, to_add[name]);}
// Don't bring in undefined values.else if (to_add[name] !== undefined) {target[name] = to_add[name];}}}}return target;}
/*** Check to see if an object is a plain object* (created using "{}" or "new Object").* Forked from jQuery.* @param obj* @returns {boolean}*/function is_plain_object(obj) {// Not plain objects:// - Any object or value whose internal [[Class]] property is not "[object Object]"// - DOM nodes// - windowif (obj === null || typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {return false;}// Support: Firefox <20// The try/catch suppresses exceptions thrown when attempting to access// the "constructor" property of certain host objects, i.e. |window.location|// https://bugzilla.mozilla.org/show_bug.cgi?id=814622try {if (obj.constructor && !this.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {return false;}}catch (e) {return false;}
// If the function hasn't returned already, we're confident that// |obj| is a plain object, created by {} or constructed with new Objectreturn true;}

为了将来参考,ECMAScript 6的当前草案引入了Object.assign作为克隆对象的一种方式。示例代码如下:

var obj1 = { a: true, b: 1 };var obj2 = Object.assign(obj1);console.log(obj2); // { a: true, b: 1 }

在编写支持仅限于浏览器中的Firefox 34时,它还不能在生产代码中使用(当然,除非您正在编写Firefox扩展)。

按性能进行深度复制:从最好到最差

  • 扩展运算符...(仅限原始数组)
  • splice(0)(原始数组-仅)
  • slice()(原始数组-仅)
  • concat()(原始数组-仅)
  • 自定义函数,如下所示(任何数组)
  • jQuery的$.extend()(任何数组)
  • JSON.parse(JSON.stringify())(原始和文字数组-仅)
  • 下划线的_.clone()(仅限原始和文字数组)
  • Lodash的_.cloneDeep()(任何数组)

在哪里:

  • 原语=字符串、数字和布尔值
  • 文字=对象文字{},数组文字[]
  • any=原语、文字和原型

深度复制一个原语数组:

let arr1a = [1, 'a', true];

要仅深度复制具有原语(即数字、字符串和布尔值)的数组,可以使用重新赋值、slice()concat()和下划线的clone()

其中传播速度最快:

let arr1b = [...arr1a];

slice()concat()有更好的性能:https://jsbench.me/x5ktn7o94d/

let arr1c = arr1a.splice(0);let arr1d = arr1a.slice();let arr1e = arr1a.concat();

深度复制原始和对象文字数组:

let arr2a = [1, 'a', true, {}, []];let arr2b = JSON.parse(JSON.stringify(arr2a));

深度复制原始、对象文字和原型的数组:

let arr3a = [1, 'a', true, {}, [], new Object()];

编写一个自定义函数(性能比$.extend()JSON.parse快):

function copy(aObject) {// Prevent undefined objects// if (!aObject) return aObject;
let bObject = Array.isArray(aObject) ? [] : {};
let value;for (const key in aObject) {
// Prevent self-references to parent object// if (Object.is(aObject[key], aObject)) continue;    
value = aObject[key];
bObject[key] = (typeof value === "object") ? copy(value) : value;}
return bObject;}
let arr3b = copy(arr3a);

或使用第三方实用程序功能:

let arr3c = $.extend(true, [], arr3a); // jQuery Extendlet arr3d = _.cloneDeep(arr3a); // Lodash

注意:jQuery的$.extend也比JSON.parse(JSON.stringify())具有更好的性能:

使用Object.create()获取prototype并支持instanceof,并使用for()循环获取可枚举键:

function cloneObject(source) {var key,value;var clone = Object.create(source);
for (key in source) {if (source.hasOwnProperty(key) === true) {value = source[key];
if (value!==null && typeof value==="object") {clone[key] = cloneObject(value);} else {clone[key] = value;}}}
return clone;}

需要新的浏览器,但…

让我们扩展原生Object并获得真正.extend()

Object.defineProperty(Object.prototype, 'extend', {enumerable: false,value: function(){var that = this;
Array.prototype.slice.call(arguments).map(function(source){var props = Object.getOwnPropertyNames(source),i = 0, l = props.length,prop;
for(; i < l; ++i){prop = props[i];
if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){that[prop] = that[prop].extend(source[prop]);}else{Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));}}});
return this;}});

只需在对象上使用.扩展()的任何代码之前弹出它。

示例:

var obj1 = {node1: '1',node2: '2',node3: 3};
var obj2 = {node1: '4',node2: 5,node3: '6'};
var obj3 = ({}).extend(obj1, obj2);
console.log(obj3);// Object {node1: "4", node2: 5, node3: "6"}

下面创建同一个对象的两个实例。我找到了它,目前正在使用它。它简单易用。

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

只有当您可以使用ECMAScript 6转译器时。

产品特点:

  • 复制时不会触发getter/setter。
  • 保留getter/setter。
  • 保留原型信息。
  • 适用于对象文字功能OO写作风格。

代码:

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

对于想要使用JSON.parse(JSON.stringify(obj))版本但不丢失Date对象的人,您可以使用#1方法的第二个参数将字符串转换回Date:

function clone(obj) {var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;return JSON.parse(JSON.stringify(obj), function(k, v) {if (typeof v === 'string' && regExp.test(v))return new Date(v)return v;})}
// usage:var original = {a: [1, null, undefined, 0, {a:null}, new Date()],b: {c(){ return 0 }}}
var cloned = clone(original)
console.log(cloned)

在一行代码中克隆(而不是深度克隆)对象的有效方法

#0方法是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;}});}

由于递归对于JavaScript来说太贵了,而且我找到的大多数答案都使用递归,而JSON方法将跳过不可JSON转换的部分(函数等)。所以我做了一点研究,发现了这种蹦床技术来避免它。这是代码:

/** Trampoline to avoid recursion in JavaScript, see:*     https://www.integralist.co.uk/posts/functional-recursive-javascript-programming/*/function trampoline() {var func = arguments[0];var args = [];for (var i = 1; i < arguments.length; i++) {args[i - 1] = arguments[i];}
var currentBatch = func.apply(this, args);var nextBatch = [];
while (currentBatch && currentBatch.length > 0) {currentBatch.forEach(function(eachFunc) {var ret = eachFunc();if (ret && ret.length > 0) {nextBatch = nextBatch.concat(ret);}});
currentBatch = nextBatch;nextBatch = [];}};
/**  Deep clone an object using the trampoline technique.**  @param target {Object} Object to clone*  @return {Object} Cloned object.*/function clone(target) {if (typeof target !== 'object') {return target;}if (target == null || Object.keys(target).length == 0) {return target;}
function _clone(b, a) {var nextBatch = [];for (var key in b) {if (typeof b[key] === 'object' && b[key] !== null) {if (b[key] instanceof Array) {a[key] = [];}else {a[key] = {};}nextBatch.push(_clone.bind(null, b[key], a[key]));}else {a[key] = b[key];}}return nextBatch;};
var ret = target instanceof Array ? [] : {};(trampoline.bind(null, _clone))(target, ret);return ret;};

使用今天的JavaScript克隆一个对象:ECMAScript 2015(以前称为ECMAScript 6)

var original = {a: 1};
// Method 1: New object with original assigned.var copy1 = Object.assign({}, original);
// Method 2: New object with spread operator assignment.var copy2 = {...original};

旧浏览器可能不支持ECMAScript 2015。一个常见的解决方案是使用像Babel这样的JavaScript到JavaScript编译器来输出JavaScript代码的ECMAScript 5版本。

作为由@jim-Hall指出这只是一个简单的拷贝。属性的属性被复制为引用:更改一个将更改另一个对象/实例中的值。

只是因为我没有看到AngularJS被提到,并认为人们可能想知道…

#0还提供了深度复制对象和数组的方法。

单行ECMAScript 6解决方案(不处理Date/Regex等特殊对象类型):

const clone = (o) =>typeof o === 'object' && o !== null ?      // only clone objects(Array.isArray(o) ?                        // if cloning an arrayo.map(e => clone(e)) :                   // clone each of its elementsObject.keys(o).reduce(                   // otherwise reduce every key in the object(r, k) => (r[k] = clone(o[k]), r), {}  // and save its cloned value into a new object)) :o;                                         // return non-objects as is
var x = {nested: {name: 'test'}};
var y = clone(x);
console.log(x.nested !== y.nested);

我使用npm克隆库。显然它也可以在浏览器中工作。

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

let a = clone(b)

AngularJS

好吧,如果你使用角,你也可以这样做

var newObject = angular.copy(oldObject);

这是一个带有递归的解决方案:

obj = {a: { b: { c: { d: ['1', '2'] } } },e: 'Saeid'}const Clone = function (obj) {  
const container = Array.isArray(obj) ? [] : {}const keys  = Object.keys(obj)   
for (let i = 0; i < keys.length; i++) {const key = keys[i]if(typeof obj[key] == 'object') {container[key] = Clone(obj[key])}elsecontainer[key] = obj[key].slice()}  
return container}console.log(Clone(obj))

为了将来参考,可以使用此代码

ES6:

_clone: function(obj){let newObj = {};for(let i in obj){if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){newObj[i] = clone(obj[i]);} else{newObj[i] = obj[i];}}return Object.assign({},newObj);}

ES5:

function clone(obj){let newObj = {};for(let i in obj){if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){newObj[i] = clone(obj[i]);} else{newObj[i] = obj[i];}}return Object.assign({},newObj);

}

e. g

var obj ={a:{b:1,c:3},d:4,e:{f:6}}var xc = clone(obj);console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}
xc.a.b = 90;console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}

class Handler {static deepCopy (obj) {if (Object.prototype.toString.call(obj) === '[object Array]') {const result = [];      
for (let i = 0, len = obj.length; i < len; i++) {result[i] = Handler.deepCopy(obj[i]);}return result;} else if (Object.prototype.toString.call(obj) === '[object Object]') {const result = {};for (let prop in obj) {result[prop] = Handler.deepCopy(obj[prop]);}return result;}return obj;}}

克隆对象在JS中一直是一个问题,但在ES6之前,我在下面列出了在JavaScript中复制对象的不同方法,假设你有下面的对象,并希望有一个深度副本:

var obj = {a:1, b:2, c:3, d:4};

有几种方法可以在不更改原点的情况下复制此对象:

  1. ES5+,使用一个简单的函数为您复制:

    function deepCopyObj(obj) {if (null == obj || "object" != typeof obj) return obj;if (obj instanceof Date) {var copy = new Date();copy.setTime(obj.getTime());return copy;}if (obj instanceof Array) {var copy = [];for (var i = 0, len = obj.length; i < len; i++) {copy[i] = deepCopyObj(obj[i]);}return copy;}if (obj instanceof Object) {var copy = {};for (var attr in obj) {if (obj.hasOwnProperty(attr)) copy[attr] = deepCopyObj(obj[attr]);}return copy;}throw new Error("Unable to copy obj this object.");}
  2. ES5+,使用JSON.parseJSON.stringify

    var deepCopyObj = JSON.parse(JSON.stringify(obj));
  3. Angular:

    var deepCopyObj = angular.copy(obj);
  4. jQuery:

    var deepCopyObj = jQuery.extend(true, {}, obj);
  5. Underscore.js&Lodash:

    var deepCopyObj = _.cloneDeep(obj); //latest version of Underscore.js makes shallow copy

希望这些帮助…

在不触及原型继承的情况下,您可以按如下方式深入单独的对象和数组;

function objectClone(o){var ot = Array.isArray(o);return o !== null && typeof o === "object" ? Object.keys(o).reduce((r,k) => o[k] !== null && typeof o[k] === "object" ? (r[k] = objectClone(o[k]),r): (r[k] = o[k],r), ot ? [] : {}): o;}var obj = {a: 1, b: {c: 2, d: {e: 3, f: {g: 4, h: null}}}},arr = [1,2,[3,4,[5,6,[7]]]],nil = null,clobj = objectClone(obj),clarr = objectClone(arr),clnil = objectClone(nil);console.log(clobj, obj === clobj);console.log(clarr, arr === clarr);console.log(clnil, nil === clnil);clarr[2][2][2] = "seven";console.log(arr, clarr);

Lodash有一个功能可以像这样为您处理。

var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};
var bar = _.cloneDeep(foo);// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}}

阅读文档这里

我不同意得票最多的答案这里。A递归深度克隆比所提到的JSON.parse(JSON.stringify(obj))方法更快

下面是快速参考的函数:

function cloneDeep (o) {let newOlet i
if (typeof o !== 'object') return o
if (!o) return o
if (Object.prototype.toString.apply(o) === '[object Array]') {newO = []for (i = 0; i < o.length; i += 1) {newO[i] = cloneDeep(o[i])}return newO}
newO = {}for (i in o) {if (o.hasOwnProperty(i)) {newO[i] = cloneDeep(o[i])}}return newO}

由于这个问题有很多关注和答案,参考内置功能,如Object.assign或自定义代码到深度克隆,我想分享一些库到深度克隆,

1. esclone

ES6中的示例使用:

import esclone from "esclone";
const rockysGrandFather = {name: "Rockys grand father",father: "Don't know :("};const rockysFather = {name: "Rockys Father",father: rockysGrandFather};
const rocky = {name: "Rocky",father: rockysFather};
const rockyClone = esclone(rocky);

ES5中的示例使用:

var esclone = require("esclone")var foo = new String("abcd")var fooClone = esclone.default(foo)console.log(fooClone)console.log(foo === fooClone)

2.深度复制

npm安装深度复制https://www.npmjs.com/package/deep-copy

示例:

var dcopy = require('deep-copy')
// deep copy objectvar copy = dcopy({a: {b: [{c: 5}]}})
// deep copy arrayvar copy = dcopy([1, 2, {a: {b: 5}}])

3.克隆深度

$npm install--保存克隆深度https://www.npmjs.com/package/clone-deep

示例:

var cloneDeep = require('clone-deep');
var obj = {a: 'b'};var arr = [obj];
var copy = cloneDeep(arr);obj.c = 'd';
console.log(copy);//=> [{a: 'b'}]
console.log(arr);

有很多方法可以实现这一点,但如果你想在没有任何库的情况下做到这一点,你可以使用以下方法:

const cloneObject = (oldObject) => {let newObject = oldObject;if (oldObject && typeof oldObject === 'object') {if(Array.isArray(oldObject)) {newObject = [];} else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {newObject = new Date(oldObject.getTime());} else {newObject = {};for (let i in oldObject) {newObject[i] = cloneObject(oldObject[i]);}}
}return newObject;}

让我知道你的想法。

我回答这个问题已经晚了,但我有另一种克隆对象的方法:

function cloneObject(obj) {if (obj === null || typeof(obj) !== 'object')return obj;var temp = obj.constructor(); // changedfor (var key in obj) {if (Object.prototype.hasOwnProperty.call(obj, key)) {obj['isActiveClone'] = null;temp[key] = cloneObject(obj[key]);delete obj['isActiveClone'];}}return temp;}
var b = cloneObject({"a":1,"b":2});   // calling

这样更好更快:

var a = {"a":1,"b":2};var b = JSON.parse(JSON.stringify(a));

var a = {"a":1,"b":2};
// Deep copyvar newObject = jQuery.extend(true, {}, a);

我已经对代码进行了基准标记,您可以测试结果这里

并分享结果:在此处输入图片描述引用:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

这是我用ES2015默认值和扩展运算符深度克隆对象的方法

 const makeDeepCopy = (obj, copy = {}) => {for (let item in obj) {if (typeof obj[item] === 'object') {makeDeepCopy(obj[item], copy)}if (obj.hasOwnProperty(item)) {copy = {...obj}}}return copy}

const testObj = {"type": "object","properties": {"userId": {"type": "string","chance": "guid"},"emailAddr": {"type": "string","chance": {"email": {"domain": "fake.com"}},"pattern": ".+@fake.com"}},"required": ["userId","emailAddr"]}
const makeDeepCopy = (obj, copy = {}) => {for (let item in obj) {if (typeof obj[item] === 'object') {makeDeepCopy(obj[item], copy)}if (obj.hasOwnProperty(item)) {copy = {...obj}}}return copy}
console.log(makeDeepCopy(testObj))

Promise完成的异步对象克隆呢?

async function clone(thingy /**/){if(thingy instanceof Promise){throw Error("This function cannot clone Promises.");}return thingy;}

通过查看这一长串答案,几乎所有的解决方案都被涵盖了,除了我知道的一个。这是VANILLA JS深度克隆对象的方法列表。

  1. JSON.parse(JSON.stringify(obj));

  2. 通过history.statepushState或replace eState

  3. Web通知API,但这有向用户询问权限的缺点。

  4. 通过对象执行自己的递归循环以复制每个级别。

  5. 我没有看到的答案->使用ServiceWorker。在页面和ServiceWorker脚本之间来回传递的消息(对象)将是任何对象的深度克隆。

ES 2017示例:

let objectToCopy = someObj;let copyOfObject = {};Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));// copyOfObject will now be the same as objectToCopy

根据我的经验,递归版本的性能大大优于JSON.parse(JSON.stringify(obj))。这是一个现代化的递归深度对象复制函数,可以放在一行上:

function deepCopy(obj) {return Object.keys(obj).reduce((v, d) => Object.assign(v, {[d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]}), {});}

这是围绕快40倍执行的,而不是JSON.parse...方法。

对于浅拷贝,ECMAScript2018标准中引入了一个很棒的简单方法。它涉及使用传播运算符

let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };
let objClone = { ...obj };

我在Chrome浏览器中测试过,两个对象都存储在不同的位置,因此更改其中一个中的直接子值不会更改另一个。尽管(在示例中)更改e中的值会影响两个副本。

这个技巧非常简单直接,我认为这是一劳永逸地解决这个问题的最佳实践。

希望这有帮助。

function deepClone(obj) {/** Duplicates an object*/
var ret = null;if (obj !== Object(obj)) { // primitive typesreturn obj;}if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecsret = obj; // for ex: obj = new String("Spidergap")} else if (obj instanceof Date) { // dateret = new obj.constructor();} elseret = Object.create(obj.constructor.prototype);
var prop = null;var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also

var props = {};for (var i in allProps) {prop = allProps[i];props[prop] = false;}
for (i in obj) {props[i] = i;}
//now props contain both enums and non enumsvar propDescriptor = null;var newPropVal = null; // value of the property in new objectfor (i in props) {prop = obj[i];propDescriptor = Object.getOwnPropertyDescriptor(obj, i);
if (Array.isArray(prop)) { //not backward compatibleprop = prop.slice(); // to copy the array} elseif (prop instanceof Date == true) {prop = new prop.constructor();} elseif (prop instanceof Object == true) {if (prop instanceof Function == true) { // functionif (!Function.prototype.clone) {Function.prototype.clone = function() {var that = this;var temp = function tmp() {return that.apply(this, arguments);};for (var ky in this) {temp[ky] = this[ky];}return temp;}}prop = prop.clone();
} else // normal object{prop = deepClone(prop);}
}
newPropVal = {value: prop};if (propDescriptor) {/** If property descriptors are there, they must be copied*/newPropVal.enumerable = propDescriptor.enumerable;newPropVal.writable = propDescriptor.writable;
}if (!ret.hasOwnProperty(i)) // when String or other predefined objectsObject.defineProperty(ret, i, newPropVal); // non enumerable
}return ret;}

https://github.com/jinujd/Javascript-Deep-Clone

在JavaScript中深度复制对象(我认为最好也是最简单的)

1.使用JSON.parse(JSON.stringify(对象));

var obj = {a: 1,b: {c: 2}}var newObj = JSON.parse(JSON.stringify(obj));obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } }

2.使用创建方法

function cloneObject(obj) {var clone = {};for(var i in obj) {if(obj[i] != null &&  typeof(obj[i])=="object")clone[i] = cloneObject(obj[i]);elseclone[i] = obj[i];}return clone;}
var obj = {a: 1,b: {c: 2}}var newObj = cloneObject(obj);obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } }

3.使用Lo-Dash的_. cloneDeep链接豆沙

var obj = {a: 1,b: {c: 2}}
var newObj = _.cloneDeep(obj);obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } }

4.使用Object.assign()方法

var obj = {a: 1,b: 2}
var newObj = _.clone(obj);obj.b = 20;console.log(obj); // { a: 1, b: 20 }console.log(newObj); // { a: 1, b: 2 }

但错误的是

var obj = {a: 1,b: {c: 2}}
var newObj = Object.assign({}, obj);obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.使用Underscore.js_. clone链接Underscore.js

var obj = {a: 1,b: 2}
var newObj = _.clone(obj);obj.b = 20;console.log(obj); // { a: 1, b: 20 }console.log(newObj); // { a: 1, b: 2 }

但错误的是

var obj = {a: 1,b: {c: 2}}
var newObj = _.cloneDeep(obj);obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH性能标杆游乐场1~3http://jsben.ch/KVQLd在JavaScript中深度复制对象的性能

当您的对象嵌套并且它包含数据对象、其他结构化对象或某些属性对象等时,使用JSON.parse(JSON.stringify(object))Object.assign({}, obj)$.extend(true, {}, obj)将不起作用。在这种情况下,使用洛达什。它简单易行…

var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };var A = _.cloneDeep(obj);

现在A将是你的obj的新克隆,没有任何引用。

如果你发现自己经常做这种事情(例如创建撤消重做功能),可能值得研究Immutable.js

const map1 = Immutable.fromJS( { a: 1, b: 2, c: { d: 3 } } );const map2 = map1.setIn( [ 'c', 'd' ], 50 );
console.log( `${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }` ); // "3 vs 50"

https://codepen.io/anon/pen/OBpqNE?editors=1111

在JavaScript中,您可以编写deepCopy方法,例如

function deepCopy(src) {let target = Array.isArray(src) ? [] : {};for (let prop in src) {let value = src[prop];if(value && typeof value === 'object') {target[prop] = deepCopy(value);} else {target[prop] = value;}}return target;}

如何将对象的与其合并?

function deepClone(o) {var keys = Object.keys(o);var values = Object.values(o);
var clone = {};
keys.forEach(function(key, i) {clone[key] = typeof values[i] == 'object' ? Object.create(values[i]) : values[i];});
return clone;}

备注:这种方法不一定要做浅拷贝,但它只复制一个内部对象的深度,这意味着当你被赋予类似{a: {b: {c: null}}}的东西时,它只会克隆直接在它们内部的对象,所以deepClone(a.b).c在技术上是对a.b.c的引用,而deepClone(a).b是一个克隆,不是参考

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

使用以下方法而不是JSON.parse(JSON.stringify(obj)),因为它比以下方法慢

如何正确克隆JavaScript对象?

随着一些浏览器(参考)的新版本支持的新方法Object.from条目()的提议。我想用下一个递归方法做出贡献:

const obj = {key1: {key11: "key11", key12: "key12", key13: {key131: 22}},key2: {key21: "key21", key22: "key22"},key3: "key3",key4: [1,2,3, {key: "value"}]}
const cloneObj = (obj) =>{if (Object(obj) !== obj)return obj;else if (Array.isArray(obj))return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(([k,v]) => ([k, cloneObj(v)])));}
// Clone the original object.let newObj = cloneObj(obj);
// Make changes on the original object.obj.key1.key11 = "TEST";obj.key3 = "TEST";obj.key1.key13.key131 = "TEST";obj.key4[1] = "TEST";obj.key4[3].key = "TEST";
// Display both objects on the console.console.log("Original object: ", obj);console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}.as-console-wrapper {max-height:100% !important; top:0;}

我的场景有点不同。我有一个带有嵌套对象和函数的对象。因此,Object.assign()JSON.stringify()不是我问题的解决方案。使用第三方库对我来说也不是一个选择。

因此,我决定创建一个简单的函数,使用内置方法来复制具有文字属性、嵌套对象和函数的对象。

let deepCopy = (target, source) => {Object.assign(target, source);// check if there's any nested objectsObject.keys(source).forEach((prop) => {/*** assign function copies functions and* literals (int, strings, etc...)* except for objects and arrays, so:*/if (typeof(source[prop]) === 'object') {// check if the item is, in fact, an arrayif (Array.isArray(source[prop])) {// clear the copied referenece of nested arraytarget[prop] = Array();// iterate array's item and copy oversource[prop].forEach((item, index) => {// array's items could be objects too!if (typeof(item) === 'object') {// clear the copied referenece of nested objectstarget[prop][index] = Object();// and re do the process for nested objectsdeepCopy(target[prop][index], item);} else {target[prop].push(item);}});// otherwise, treat it as an object} else {// clear the copied referenece of nested objectstarget[prop] = Object();// and re do the process for nested objectsdeepCopy(target[prop], source[prop]);}}});};

这是一个测试代码:

let a = {name: 'Human',func: () => {console.log('Hi!');},prop: {age: 21,info: {hasShirt: true,hasHat: false}},mark: [89, 92, { exam: [1, 2, 3] }]};
let b = Object();
deepCopy(b, a);
a.name = 'Alien';a.func = () => { console.log('Wassup!'); };a.prop.age = 1024;a.prop.info.hasShirt = false;a.mark[0] = 87;a.mark[1] = 91;a.mark[2].exam = [4, 5, 6];
console.log(a); // updated propsconsole.log(b);

对于与效率相关的问题,我相信这是我遇到的问题的最简单和最有效的解决方案。我希望对这个算法有任何评论,可以使它更有效率。

Object.assign({},sourceObj)仅在对象的属性没有引用类型键时才克隆对象。ex

obj={a:"lol",b:["yes","no","maybe"]}clonedObj = Object.assign({},obj);
clonedObj.b.push("skip")// changes will reflected to the actual obj as well because of its reference type.obj.b //will also console => yes,no,maybe,skip

所以对于深度克隆来说是不可能以这种方式实现的。

最好的解决办法是

var obj = Json.stringify(yourSourceObj)var cloned = Json.parse(obj);

这是我的解决方案,不使用任何库或原生javascript函数。

function deepClone(obj) {if (typeof obj !== "object") {return obj;} else {let newObj =typeof obj === "object" && obj.length !== undefined ? [] : {};for (let key in obj) {if (key) {newObj[key] = deepClone(obj[key]);}}return newObj;}}