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


5180

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

我已经做了类似的事情,obj = JSON.parse(JSON.stringify(o));但是对效率提出了质疑。

我还看到了具有各种缺陷的递归复制功能。
我很惊讶没有规范的解决方案存在。


566
评估不是邪恶的。评估使用不佳是。如果您担心它的副作用,那就错了。您担心的副作用是使用它的原因。顺便说一句,实际上有人回答了您的问题吗?
詹姆斯

15
克隆对象是一件棘手的事情,尤其是使用任意集合的自定义对象时。这可能就是为什么没有开箱即用的方式来做到这一点的原因。
2013年

12
eval()通常是个坏主意,因为在处理通过设置的变量时,许多Javascript引擎的优化程序都必须关闭eval。仅包含eval()在代码中会导致性能下降。
user56reinstatemonica8 2013年


12
请注意,该JSON方法将删除JSON中没有等效功能的所有Javascript类型。例如:JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))将产生{a: null, b: null, c: null, g: false}
oriadam '17

Answers:


4729

原生深克隆

它被称为“结构化克隆”,可在Node 11及更高版本上进行实验性工作,并有望登陆浏览器。有关更多详细信息,请参见此答案

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

如果不使用DateS,功能,undefinedInfinity,正则表达式,地图,集合,斑点,的文件列表,ImageDatas,稀疏数组,类型化数组或其他复杂类型的对象中,一个很简单的一个衬垫深克隆的对象是:

JSON.parse(JSON.stringify(object))

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

有关基准,请参阅Corban的答案

使用库进行可靠的克隆

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

ES6

为了完整起见,请注意,ES6提供了两种浅表复制机制:Object.assign()扩展语法。它将所有可枚举的自身属性的值从一个对象复制到另一个对象。例如:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

7
@ ThiefMaster github.com/jquery/jquery/blob/master/src/core.js在第276行(有一些代码可以完成其他工作,但是其中有“如何在JS中做到这一点”的代码:)
符文FS

7
对于感兴趣的任何人,这是jQuery深度复制背后的JS代码:github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
哇!只是非常清楚:不知道为什么选择此响应作为正确答案,这是对以下给出的响应的答复:stackoverflow.com/a/122190/6524(建议这样做.clone(),这不是正确的代码。在此上下文中使用)。不幸的是,这个问题经过了多次修订,原来的讨论不再明显!如果您关心速度,请遵循Corban的建议并编写循环或将属性直接复制到新对象。或亲自测试一下!
John Resig 2014年

9
这是一个JavaScript问题(没有提到jQuery)。
gphilip

60
不使用jQuery如何做到这一点?
Awesomeness01年

2264

查看此基准测试:http : //jsben.ch/#/bWfk9

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

JSON.parse(JSON.stringify(obj))

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

deep标志设置为false(浅克隆)时,jQuery.extend非常快。这是一个不错的选择,因为它包括一些用于类型验证的额外逻辑,并且不会复制未定义的属性等,但这也会使您慢下来。

如果您知道要克隆的对象的结构,或者可以避免使用深层嵌套的数组,则可以for (var i in obj)在检查hasOwnProperty时编写一个简单的循环来克隆对象,这将比jQuery快得多。

最后,如果您尝试在热循环中克隆已知的对象结构,则只需内联克隆过程并手动构造对象,即可获得更多的性能。

JavaScript跟踪引擎在优化for..in循环方面很烂,检查hasOwnProperty也会使您的速度变慢。绝对必要时必须手动克隆。

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

当心JSON.parse(JSON.stringify(obj))Date对象上使用方法- JSON.stringify(new Date())以ISO格式返回日期的字符串表示形式,该格式JSON.parse() 不会转换回Date对象。有关更多详细信息,请参见此答案

此外,请注意,至少在Chrome 65中,本机克隆不是可行的方法。根据JSPerf的说法,通过创建新功能执行本机克隆的速度比使用JSON.stringify的速度要快近800倍,而JSON.stringify的整个过程都非常快。

ES6更新

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

Object.assign({}, obj);

4
@trysis Object.create未克隆对象,正在使用原型对象... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser 2014年

105
这种方法也将删除keys从你的object,其中有functions他们的价值,因为JSON不支持的功能。
卡伦·基什米尔扬(Karlen Kishmiryan)

39
还要记住,JSON.parse(JSON.stringify(obj))在日期对象上使用还将以ISO8601格式的字符串表示形式将日期转换回UTC
dnlgmzddr

31
JSON方法还会阻塞循环引用。
rich remer

28
@velop,Object.assign({},objToClone)似乎看起来像是在进行浅层克隆-在开发工具控制台中播放时使用它,对象克隆仍指向已克隆对象的引用。因此,我认为这在这里并不真正适用。
加勒特·辛普森

473

假设您的对象中只有变量,而没有任何函数,则可以使用:

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

86
因为我刚刚发现这种方法的con是,如果你的对象有字符串化时,这些丢失任何功能(我有内部的getter和setter方法),那么..如果这就是你需要这个方法很好..
Markive

31
@Jason,此方法比浅复制(在深对象上)慢的原因是,根据定义,该方法是深复制。但是,由于JSON是在本机代码中实现的(在大多数浏览器中),因此这将比使用任何其他基于JavaScript的深度复制解决方案快得多,并且有时可能比基于JavaScript的浅表复制技术要快(请参阅:jsperf.com/cloning -an-object / 79)。
MiJyn

35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer 2014年

32
该技术还将销毁Date存储在对象内部的所有对象,并将它们转换为字符串形式。
fstab

13
它将无法复制不属于JSON规范(json.org)的任何内容
cdmckay,2016年

397

结构化克隆

HTML标准包括一个内部结构化克隆/序列化算法,该算法可以创建对象的深层克隆。它仍然仅限于某些内置类型,但除JSON支持的几种类型外,它还支持日期,正则表达式,地图,集合,Blob,文件列表,ImageData,稀疏数组,类型数组,并且将来可能还会更多。它还在克隆的数据中保留引用,使其支持循环和递归结构,这些结构将导致JSON错误。

Node.js中的支持:实验性

v8当前(截至节点11),Node.js中的模块直接公开结构化序列化API,但是此功能仍标记为“实验性”,并且在将来的版本中可能会更改或删除。如果使用兼容版本,则克隆对象非常简单:

const v8 = require('v8');

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

浏览器中的直接支持:也许最终?😐

浏览器当前不提供结构化克隆算法的直接接口,但是在GitHub上的whatwg / html#793中structuredClone()讨论了全局功能。按照目前的建议,将其用于大多数目的将非常简单:

const clone = structuredClone(original);

除非提供此功能,否则浏览器的结构化克隆实现只能间接公开。

异步解决方法:可用。😕

使用现有API创建结构化克隆的一种较低开销的方法是通过MessageChannels的一个端口发布数据。另一个端口将发出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;
};

使用示例:

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

Notification构造函数创建其相关数据的结构化克隆。它还尝试向用户显示浏览器通知,但是除非您请求了通知权限,否则此操作将以静默方式失败。如果您有其他用途的许可,我们将立即关闭我们创建的通知。

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

使用示例:


3
@rynah我只是再次浏览了规范,您是对的:history.pushState()history.replaceState()方法都同步设置history.state为其第一个参数的结构化克隆。有点奇怪,但是可以用。我正在更新我的答案。
杰里米·班克斯

40
这太错了!该API不能以这种方式使用。
Fardin K.

209
作为在Firefox中实现pushState的人,我对这种黑客感到自豪和反感。做的好各位。
贾斯汀·L.

pushState或Notification hack不适用于某些对象类型,例如Function
Shishir Arora,

323

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

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

20
JQuery解决方案将适用于DOM元素,但不仅适用于任何Object。Mootools具有相同的限制。希望他们对任何对象都有一个通用的“克隆” ...递归解决方案应该对任何对象都适用。这可能是要走的路。
jschrab

5
如果要克隆的对象具有需要参数的构造函数,则此函数中断。看来我们可以将其更改为“ var temp = new Object()”,并使其在每种情况下都有效,不是吗?
安德鲁·阿诺特

3
安德鲁,如果将其更改为var temp = new Object(),则您的克隆将没有与原始对象相同的原型。尝试使用:'var newProto = function(){}; newProto.prototype = obj.constructor; var temp = new newProto();'
limscoder 2011年

1
与limscoder的答案类似,请参见以下关于如何在不调用构造函数的情况下执行此操作的答案:stackoverflow.com/a/13333781/560114
Matt Browne

3
对于包含对子部件的引用的对象(即,对象网络),这不起作用:如果两个引用指向同一个子对象,则副本包含该对象的两个不同副本。并且,如果有递归引用,该函数将永远不会终止(嗯,至少不会以您想要的方式:-)对于这些一般情况,您必须添加一个已复制对象的字典,并检查是否已复制它...当您使用一种简单的语言编程很复杂
virtualnobi

153

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

一种Object.assign方法是在2015年的ECMAScript(ES6)标准的一部分,不正是你需要的。

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

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

阅读更多...

支持旧版浏览器的polyfill

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

82
这不会递归复制,因此实际上并不能为克隆对象的问题提供解决方案。
mwhite

5
尽管我测试了一些方法,并且_.extend({},(obj))的速度最快,但该方法仍然有效:例如,它比JSON.parse快20倍,比Object.assign快60%。它很好地复制了所有子对象。
尼科,

11
@mwhite克隆和深层克隆之间有区别。这个答案实际上可以克隆,但不能深度克隆。
Meirion Hughes

57
操作人员要求进行深度克隆。这不会进行深度克隆。
user566245 '16

9
此方法将创建SHALLOW副本,而不是DEEP副本!因此,这是完全错误的答案
巴拉塔(Bharata)'18

97

码:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

测试:

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

var clone = extend(obj);

3
怎么样var obj = {}obj.a = obj
neaumusic

5
我不了解此功能。假设from.constructorDate例如。if当第二个if测试成功并导致函数返回(自Date != Object && Date != Array)时,如何达到第三个测试?
亚当·麦基

1
@AdamMcKee因为javascript参数传递和变量分配很棘手。这种方法非常有用,包括日期(确实由第二次测试处理)-在这里进行测试的小提琴:jsfiddle.net/zqv9q9c6
brichins

1
@NickSweeting:尝试-可能有效。如果不是,请修复并更新答案。这就是它在社区中的工作方式:)
Kamarey '17

1
此函数不会在测试中克隆正则表达式,条件“ from.constructor!=对象&& from.constructor!=数组”对于其他构造函数(如Number,Date等)始终返回true。
aMarCruz

95

这就是我正在使用的:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

8
这似乎不正确。cloneObject({ name: null })=>{"name":{}}
Niyaz

13
这是由于javascript中的另一个愚蠢的事情,typeof null > "object"Object.keys(null) > TypeError: Requested keys of a value that is not an object.将条件更改为if(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us 2013年

这会将obj的继承的可枚举属性直接分配给克隆,并假定obj是一个普通对象。
RobG '16

这也弄乱了数组,并使用数字键将其转换为对象。
刀片式服务器

如果您不使用null,就没有问题。
Jorge Bucaran'3

78

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

  • 重新分配“ =”(仅字符串数组,数字数组)
  • 切片(字符串数组,数字数组-仅)
  • 串联(仅字符串数组,数字数组)
  • 自定义功能:for循环或递归复制
  • jQuery的$ .extend
  • JSON.parse(仅字符串数组,数字数组,对象数组)
  • Underscore.js的_.clone(字符串数组,若干阵列-只)
  • Lo-Dash的_.cloneDeep

深度复制字符串或数字数组(一个级别-无引用指针):

当数组包含数字和字符串时-诸如.slice()、. concat()、. splice()之类的函数,赋值运算符“ =”和Underscore.js的clone函数;将复制该数组的元素。

重新分配表现最快的地方:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

而且.slice()的性能优于.concat(),http: //jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

深度复制对象数组(两个或多个级别-参考指针):

var arr1 = [{object:'a'}, {object:'b'}];

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

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

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

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

jQuery的$ .extend具有更好的性能:


我测试了一些,然后_.extend({},(obj))的BY FAR最快:比JSON.parse快20倍,比Object.assign快60%。它很好地复制了所有子对象。
尼科,

4
您所有的示例都是浅层次的。这不是一个好答案。问题是关于深度克隆,即至少两个级别。
Karl Morrison

1
深层复制是在不使用指向其他对象的引用指针的情况下将对象完整复制的情况。“深度复制对象数组”部分下的技术,例如jQuery.extend()和自定义函数(递归),将对象复制为“至少两个级别”。因此,并非所有示例都是“一个级别”的副本。
tfmontague '17

1
我喜欢您的自定义复制功能,但是您应该排除空值,否则所有空值都将转换为对象,即:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi

2
@HossamMourad-该错误由Josi于2月1日修复(在上面的评论中),但我未能正确更新答案。抱歉,此错误导致您的代码库重构。
tfmontague

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

好的答案,但是对于循环引用来说,这是失败的。
路加

59

用JavaScript深度复制对象(我认为最好和最简单)

1.使用JSON.parse(JSON.stringify(object));

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

2.使用创建的方法

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

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

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

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

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

4.使用Object.assign()方法

var obj = { 
  a: 1,
  b: 2
}

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

但是错误

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

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

5.采用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〜3 http://jsben.ch/KVQLd 性能用JavaScript深度复制对象


5
Object.assign()不会执行深层复制
Roymunson

1
您应该为此添加基准;这将非常有帮助
jcollum

当我在包含数组的对象上使用“创建的方法”时,无法在其上使用pop()或splice(),我不明白为什么?let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();它抛出:(TypeError: tmp.title.pop is not a function当然,如果我只是pop()可以正常工作do let tmp = data;但是那我不能在不影响数据的情况下修改tmp)
hugogogo

嘿,您的最后一个例子是错误的。我认为,对于错误的示例,必须使用_clone而不是_cloneDeep。
kenanyildiz

这个创建的方法(2.)对数组不起作用,对吗?
ToivoSäwén19年

57

有一个库(称为“克隆”),可以很好地完成此任务。它提供了我所知道的任意对象的最完整的递归克隆/复制。它还支持循环引用,但其他答案尚未涵盖。

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

这是有关如何使用它的示例:

用安装

npm install clone

或将其与Ender打包在一起。

ender build clone [...]

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

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

var clone = require('clone');

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

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

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


3
对于克隆任意嵌套的对象,npm clone对我来说非常宝贵。这是正确的答案。
安迪·雷

与您的lib相比,您的lib的性能JSON.parse(JSON.stringify(obj))如何?
pkyeck '16

这是一个说明有更快选择的库。还没有测试。
pvorb '16

好的解决方案,它支持循环引用(与JSON解析不同)
Luke

55

Cloning 在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] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2)ES5 +,使用JSON.parse和JSON.stringify。

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

3)AngularJs:

var  deepCopyObj = angular.copy(obj);

4)jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5)UnderscoreJs和Loadash:

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

希望这些帮助...


2
下划线克隆不是当前版本中的深层克隆
Rogelio

谢谢。是的,作为Underscore的新文档... clone_.clone(object)创建提供的普通对象的浅表副本。任何嵌套的对象或数组都将通过引用复制,而不是重复。_.clone({name:'moe'}); => {name:'moe'};
Alireza

59
Object.assign没有深拷贝。范例:var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d"。如果这是深复制,y.a.b那么仍然会c,但是现在d
kba

8
Object.assign()仅克隆第一级属性!
haemse '17

5
什么是cloneSO()函数?
pastorello

53

我知道这是一篇过时的文章,但是我认为这对下一个偶然发现的人可能会有帮助。

只要您不将对象分配给任何对象,它就不会在内存中保留任何引用。因此,要创建一个要与其他对象共享的对象,就必须创建一个工厂,如下所示:

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

16
这个答案并不真正相关,因为问题是:给定实例b一个人如何创建副本c而又不知道工厂a或不想使用工厂a。一个人可能不想使用工厂的原因是在实例化之后b可能已经用其他数据(例如,用户输入)进行了初始化。
诺埃尔·亚伯拉罕斯

12
的确,这并不是对这个问题的真正答案,但是我认为在这里很重要,因为这是对这个问题的答案,我怀疑许多来这里的人确实有意思要问。
分号

8
抱歉,我真的不明白为什么要这么多投票。克隆对象是一个非常明确的概念,您可以从另一个对象中隔离一个对象,而与使用工厂模式创建一个新对象并没有多大关系。
打开

2
虽然这适用于预定义对象,但以这种方式进行“克隆”将无法识别添加到原始对象的新属性。如果创建a,则将新属性添加到a,然后创建b。b将不具有新属性。本质上,工厂模式对于新属性是不变的。这不是范式克隆。请参阅:jsfiddle.net/jzumbrun/42xejnbx
乔恩·

1
一般来说,我认为这是个不错的建议,因为const defaultFoo = { a: { b: 123 } };您可以不用使用而去const defaultFoo = () => ({ a: { b: 123 } };解决问题。但是,这确实不是问题的答案。作为对问题的评论而不是完整的答案,这可能更有意义。
来自Qaribou的Josh,17年

48

如果您使用它,则Underscore.js库具有克隆方法。

var newObject = _.clone(oldObject);

24
lodash具有cloneDeep方法,它还支持克隆另一个参数以使其更深:lodash.com/docs#clonelodash.com/docs#cloneDeep
2013年

12
@opensas同意。Lodash通常优于下划线
2014年

7
我主张删除此答案以及所有其他答案,这些答案只是对实用程序库.clone(...)方法的单行引用。每个主要的图书馆都会有它们,重复的简短的非详细答案对大多数不会使用该特定图书馆的访问者没有用。
杰里米·班克斯

41

这是上面ConroyP的答案的一个版本,即使构造函数具有必需的参数,该版本仍然有效:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

我的simpleoo库中也提供了此功能。

编辑:

这是一个更健壮的版本(由于Justin McCandless现在也支持循环引用):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

30

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

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

这个答案有什么问题吗?作为独立解决方案,它更有用,但很简单;但是jQuery解决方案更受欢迎。这是为什么?
ceremcem

是的,请让我知道。它似乎按预期工作,如果某处存在一些隐藏的破损,我需要使用其他解决方案。
森·罗杰斯

4
对于一个简单的对象,Chrome中给定答案的速度要比给定答案慢大约6倍,并且随着对象复杂性的增加而变得越来越慢。它可伸缩性极强,并且会很快使您的应用程序瓶颈。
tic

1
您不需要数据,只需了解发生的情况即可。这种克隆技术将整个对象序列化为一个字符串,然后解析该字符串序列化以构建一个对象。从本质上讲,这将比仅重新安排一些内存(这是更复杂的克隆所做的事情)要慢得多。话虽这么说,对于中小型项目(取决于您对“中型”的定义),谁在乎它的效率是否甚至低1000倍?如果您的对象很小,并且您没有克隆它们,那么几乎没有东西的数量可能是1000吨。
machineghost

3
而且,此方法会丢失方法(或JSON中不允许的任何内容),加上JSON.stringify会将Date对象转换为字符串,而不是将...转换为字符串;)不要使用此解决方案。
MT MT

22

Crockford建议(我更喜欢)使用此功能:

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

var newObject = object(oldObject);

它很简洁,可以按预期工作,您不需要库。


编辑:

这是的polyfill Object.create,因此您也可以使用它。

var newObject = Object.create(oldObject);

注意: 如果您使用其中的一些,则使用的某些迭代可能会遇到问题hasOwnProperty。因为,请create创建一个继承的新空对象oldObject。但是它对于克隆对象仍然有用且实用。

例如 oldObject.a = 5;

newObject.a; // is 5

但:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

9
如果我错了,请纠正我,但是那不是Crockford的原型继承的beget函​​数吗?它如何适用于克隆?
Alex Nolasco 2010年

3
是的,我很害怕这样的讨论:克隆,复制和原型继承之间的实际区别是什么,何时应使用每种继承,此页面上的哪些功能实际上在做什么?我通过搜索“ javascript复制对象”找到了此SO页面。我真正想要的是上面的功能,所以我回来分享了。我的猜测是提问者也在寻找这个。
克里斯·布罗斯基

51
克隆/复制与继承之间的区别在于,以您的示例为例,当我更改oldObject的属性时,该属性也会在newObject中更改。如果您进行复制,则可以使用oldObject进行所需的操作而无需更改newObject。
Ridcully 2010年

13
这将破坏hasOwnProperty检查,因此是克隆对象的一种很简单的方法,并且会给您带来意想不到的结果。
Corban Brook,

var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
科迪

22

Lodash有一个不错的_.cloneDeep(value)方法:

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

5
我主张删除此答案以及所有其他答案,这些答案只是对实用程序库.clone(...)方法的单行引用。每个主要的图书馆都会有它们,重复的简短的非详细答案对大多数不会使用该特定图书馆的访问者没有用。
杰里米·班克斯

一种更简单的方法是使用_.merge({}, objA)。如果只有lodash首先不对对象进行突变,那么该clone函数就不是必需的。
Rebs

7
Google搜索克隆JS对象的参考在这里。我正在使用Lodash,所以这个答案与我有关。请不要让所有“维基百科删除主义者”都回答。
Rebs

2
在节点9中,JSON.parse(JSON.stringify(arrayOfAbout5KFlatObjects))比_.deepClone(arrayOfAbout5KFlatObjects)快很多。
Dan Dascalescu

21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

17
方法的问题在于,如果obj中包含子对象,则将克隆其引用,而不是每个子对象的值。
Kamarey

1
只需使其递归即可将子对象深深克隆。
fiatjaf

只是好奇...克隆变量将不会具有指向原始对象属性的指针?因为似乎没有新的内存分配
Rupesh Patel

3
是。这只是一个浅表副本,因此克隆将指向原始对象指向的完全相同的对象。
马克·西达德

这不是答案。从字面上看,您只是将对象与另一个对象的引用塞在一起。对源对象进行更改将对“克隆”进行更改。
肖恩·惠纳

19

浅拷贝单线版ECMAScript第5版):

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

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

浅拷贝单线(ECMAScript第6版,2015年):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

6
这对于简单的对象可能很好,但是它仅复制属性值。它不会影响原型链,并且通过使用Object.keys它会跳过不可枚举和继承的属性。此外,它通过直接分配会丢失属性描述符。
马特·比纳

如果您也复制原型,那么您将只缺少非枚举和属性描述符,是吗?非常好。:)
萨姆

除了性能之外,这是一种浅复制对象的非常方便的方法。我经常使用它来在React组件中的分解任务中伪造的rest属性。
mjohnsonengr

17

只是因为我没有看到AngularJS并以为人们可能想知道...

angular.copy 还提供了深度复制对象和数组的方法。


或可以使用与jQiery扩展相同的方式使用:angular.extend({},obj);
Galvani

2
@Galvani:应该注意的是jQuery.extendangular.extend它们都是浅表副本。angular.copy是深复制。
丹·阿特金森

16

对于类似数组的对象,似乎还没有理想的深度克隆运算符。如下代码所示,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)

14
正如其他人在对Resig答案的注释中所指出的那样,如果要克隆类似数组的对象,则可以在扩展调用中将{}更改为[],例如jQuery.extend(true,[],obj)
Anentropic

15

根据您的目标是克隆“普通的旧JavaScript对象”,我有两个很好的答案。

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

对于普通的旧JavaScript对象,在现代运行时中克隆对象的一种经过实践检验的好方法非常简单:

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

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

有效率吗?哎呀。我们尝试了各种克隆方法,并且效果最好。我敢肯定,有些忍者会想出一种更快的方法。但是我怀疑我们在谈论边际收益。

这种方法既简单又易于实现。将其包装为便利功能,如果您确实需要榨取一些收益,请稍后再试。

现在,对于非普通的JavaScript对象,没有一个真正简单的答案。实际上,不可能是因为JavaScript函数和内部对象状态的动态性质。深入克隆内部函数的JSON结构需要您重新创建这些函数及其内部上下文。而且JavaScript根本没有标准化的方法。

再一次,正确的方法是通过在代码中声明并重用的便捷方法。便捷方法可以使您对自己的对象有所了解,从而可以确保在新对象中正确地重新创建图形。

我们是自己编写的,但是这里介绍了我所见过的最好的通用方法:

http://davidwalsh.name/javascript-clone

这是正确的想法。作者(David Walsh)评论了广义函数的克隆。您可以根据自己的用例选择执行此操作。

主要思想是您需要按类型专门处理函数的实例化(或可以说是原型类)。在这里,他提供了RegExp和Date的一些示例。

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

这样有效吗?哎呀。鉴于目标是产生一个真正的深拷贝克隆,那么您将不得不遍历源对象图的成员。使用这种方法,您可以精确调整要处理的子成员以及如何手动处理自定义类型。

所以你去了。两种方法。我认为两者都是有效的。


13

通常这不是最有效的解决方案,但它可以满足我的需求。下面的简单测试用例...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

循环数组测试...

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

功能测试...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

11

AngularJS

好吧,如果您使用的是角度,也可以这样做

var newObject = angular.copy(oldObject);

11

我不同意这里的投票。一个递归深克隆要快得多JSON.parse(JSON.stringify(OBJ))中提到的方法。

  • 杰斯珀夫在这里排名第一:https :
  • 杰斯本上面答案中的进行了更新,以显示递归深层克隆击败了所有其他提到的问题:http : //jsben.ch/13YKQ

以下是供快速参考的功能:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

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

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}

2
我喜欢这种方法,但是它不能正确处理日期。考虑if(o instanceof Date) return new Date(o.valueOf());在检查
Luis

循环引用崩溃。
哈里

在最新的稳定版Firefox中,这比该Jsben.ch链接上的其他策略更长的时间,甚至高出一个数量级。它在错误的方向上击败了其他人。
WBT

11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

10

仅当您可以使用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;
}

9

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

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if (!src && typeof src != "object") {
        // Any non-object (Boolean, String, Number), null, undefined, NaN
        return src;
    }

    // Honor native/custom clone methods
    if (src.clone && toString.call(src.clone) == "[object Function]") {
        return src.clone(deep);
    }

    // DOM elements
    if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
        return src.cloneNode(deep);
    }

    // Date
    if (toString.call(src) == "[object Date]") {
        return new Date(src.getTime());
    }

    // RegExp
    if (toString.call(src) == "[object RegExp]") {
        return new RegExp(src);
    }

    // Function
    if (toString.call(src) == "[object Function]") {

        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });
    }

    var ret, index;
    //Array
    if (toString.call(src) == "[object Array]") {
        //[].slice(0) would soft clone
        ret = src.slice();
        if (deep) {
            index = ret.length;
            while (index--) {
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }
    return ret;
};

它将原语转换为包装对象,在大多数情况下不是一个好的解决方案。
Danubian Sailor'Aug

@DanubianSailor-我不认为这是...似乎从一开始就返回原语,并且似乎没有对它们做任何事情,它们会在返回时将它们变成包装对象。
Jimbo Jonny
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.