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

jschrab 4881 javascript clone object

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

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

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

John Resig.. 4217

注意:这是对另一个答案的回复,而不是对此问题的正确回答.如果您希望快速克隆对象,请在回答此问题时遵循Corban的建议.


我想要注意jQuery中的.clone()方法只能克隆DOM元素.要克隆JavaScript对象,您可以:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

可以在jQuery文档中找到更多信息.

我还要注意,深拷贝实际上比上面显示的更聪明 - 它能够避免许多陷阱(例如,尝试深度扩展DOM元素).它经常在jQuery核心和插件中使用,效果很好.

  • 哇!只是为了超级明确:不知道为什么这个回答被选为正确答案,这是对以下回复的答复:/sf/ask/17360801/(推荐`.clone() `,这不是在这种情况下使用的正确代码).不幸的是,这个问题已经经过了如此多的修改,原来的讨论已经不再明显了!如果您关心速度,请按照Corban的建议编写循环或将属性直接复制到新对象.或者亲自测试一下! (176认同)
  • 如果不使用jQuery,如何做到这一点? (52认同)
  • 对于那些没有意识到的人,John Resig的回答可能是对[ConroyP的回答](http://stackoverflow.com/a/122190/859858)的回应/澄清,而不是直接回答问题. (45认同)
  • @ThiefMaster https://github.com/jquery/jquery/blob/master/src/core.js第276行(有一些代码可以做其他事情但是"如何在JS中执行此操作"的代码是:) (7认同)
  • 这是jQuery深层拷贝背后的JS代码,适合所有感兴趣的人:https://github.com/jquery/jquery/blob/master/src/core.js#L265-327 (7认同)
  • 这是一个JavaScript问题(没有提到jQuery). (7认同)
  • 问题陈述了标题和正文中的javascript.它没有提到jQuery.那么,为什么jQuery的答案如此高度投票呢? (5认同)
  • 用jQuery答案回答JavaScript问题.为了便宜而低估了OP.框架和库编写得不好,会在客户端上浪费大量带宽.第一次就做对了:专业人士使用真实代码. (4认同)
  • 由于围绕使用jQuery似乎存在一些争论,我想我会提到我已经将`extend`功能解压缩到一个独立的脚本:https://gist.github.com/jonjaques/3036701 (2认同)
  • 这是非常好的方法,但我有一个问题,因为它**不能正确转换数组**.它将数组更改为具有属性名称0,1,2的对象...因此我最终使用JSON.parse(JSON.stringify(data)),这对我来说非常有效 (2认同)
  • 请注意,此方法不会保留setter和getter (2认同)
  • 如果使用jQuery并且我很懒,答案很有用; 我只是懒惰.因此这个答案没用.+1 @Corban Brook的回答如下. (2认同)
  • @ superluminary-所以你认为根据内容值得投票,但是不是因为作者?这甚至不是对OP的回答,它怎么能不进行投票呢? (2认同)

Corban Brook.. 2066

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

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

JSON.parse(JSON.stringify(obj))

成为深度克隆对象的最快方法(它将jQuery.extend与深度标志设置为10-20%).

当深度标志设置为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在整个过程中都非常快.

  • 这个方法也会从你的`object`中删除`keys`,它们的`functions`作为它们的值,因为``JSON`不支持函数. (90认同)
  • 另外请记住,在Date Objects上使用`JSON.parse(JSON.stringify(obj))`也会将日期转换回**ISO**以保证**ISO8601**格式的字符串表示形式. (31认同)
  • JSON方法也在循环引用上扼杀. (25认同)
  • @velop,Object.assign({},objToClone)看起来好像是一个浅层克隆 - 在dev工具控制台中使用它时,对象克隆仍然指向克隆对象的引用.所以我认为这不适用于此. (18认同)
  • @trysis Object.create没有克隆对象,正在使用原型对象... http://jsfiddle.net/rahpuser/yufzc1jt/2/ (4认同)
  • @aokaddaoc没有Object.assign要快得多.我把它添加到答案中给出的基准. (3认同)
  • JSON方法将无法复制任何不属于JSON规范(json.org)的内容,包括日期对象,函数,循环引用等 (2认同)
  • `Object.assign`不是深刻的克隆:( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Deep_Clone (2认同)
  • 它不再是Chrome中最快的 (2认同)

Sultan Shaki.. 434

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

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

  • 我刚刚发现这种方法的结果是如果你的对象有任何函数(我的内部有getter和setter),那么当字符串化时这些就会丢失..如果这就是你所需要的,这种方法很好.. (83认同)
  • `JSON.stringify({key:undefined})// =>"{}"` (34认同)
  • @Jason,这个方法比浅层复制(在深层对象上)慢的原因是这个方法,根据定义,深层复制.但由于`JSON`是在本机代码中实现的(在大多数浏览器中),这将比使用任何其他基于javascript的深度复制解决方案快得多,并且可能比基于javascript的浅层复制技术更快(参见:http: //jsperf.com/cloning-an-object/79). (31认同)
  • 这种技术也会破坏存储在对象中的所有`Date`对象,将它们转换为字符串形式. (28认同)
  • 它将无法复制任何不属于JSON规范的内容(http://json.org/) (12认同)

大智慧.. 308

结构化克隆

HTML标准包括一个内部结构化克隆/序列化算法,可以创建对象的深层克隆.它仍然局限于某些内置类型,但除了JSON支持的少数类型之外,它还支持日期,RegExps,地图,集合,Blob,文件列表,ImageDatas,稀疏数组,类型化数组,以及未来可能更多.它还保留了克隆数据中的引用,允许它支持会导致JSON错误的循环和递归结构.

Node.js支持:实验

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

const v8 = require('v8');

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

浏览器中的直接支持:可能最终?

浏览器目前不为结构化克隆算法提供直接接口,但在GitHub上的whatwg/html#793中structuredClone()讨论了全局函数.正如目前提出的那样,在大多数情况下使用它将如下所示:

const clone = structuredClone(original);

除非发布此内容,否则浏览器的结构化克隆实现仅间接公开.

异步解决方法:可用.

使用现有API创建结构化克隆的低开销方法是通过MessageChannel的一个端口发布数据.另一个端口将发出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暂时无响应.

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

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

  • 作为在Firefox中实现pushState的人,我觉得这个黑客的骄傲和厌恶是奇怪的混合.做的好各位. (172认同)
  • 这是错的!该API并不意味着以这种方式使用. (35认同)
  • @rynah我只是仔细查看了规范并且你是对的:`history.pushState()`和`history.replaceState()`方法都将`history.state`同步设置为第一个参数的结构化克隆.有点奇怪,但它的工作原理.我现在正在更新我的答案. (3认同)

ConroyP.. 295

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

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

  • JQuery解决方案适用于DOM元素,但不适用于任何Object.Mootools具有相同的限制.希望他们对任何对象都有一个通用的"克隆"......递归解决方案应该适用于任何事情.这可能是要走的路. (20认同)
  • 如果要克隆的对象具有需要参数的构造函数,则此函数会中断.似乎我们可以将它改为"var temp = new Object()"并让它在每种情况下都有效,不是吗? (5认同)
  • 安德鲁,如果将其更改为var temp = new Object(),那么您的克隆将不会具有与原始对象相同的原型.尝试使用:'var newProto = function(){}; newProto.prototype = obj.constructor; var temp = new newProto();' (3认同)
  • 对于包含对子部分(即对象网络)的引用的对象,这不起作用:如果两个引用指向同一个子对象,则该副本包含两个不同的副本.如果有递归引用,函数将永远不会终止(好吧,至少不是你想要它的方式:-)对于这些一般情况,你必须添加一个已经复制的对象的字典,并检查你是否已经复制它...当您使用简单的语言时,编程很复杂 (2认同)

Eugene Tiuri.. 149

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

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

  • 这不会递归复制,因此无法真正提供克隆对象问题的解决方案. (62认同)
  • op要求深度克隆.这不做深刻的克隆. (38认同)
  • @mwhite克隆和深度克隆之间存在差异.这个答案实际上是克隆,但它没有深度克隆. (10认同)
  • 这种方法很有效,虽然我测试了一些并且_.extend({},(obj))是最快的BY FAR:比JSON.parse快20倍,比Object.assign快60%.它很好地复制了所有子对象. (4认同)
  • 哇这么多的Object.assign响应,甚至没有阅读op的问题...... (2认同)
  • 它为什么被投票? (2认同)

Kamarey.. 94

码:

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

  • 我不明白这个功能.例如,假设`from.constructor`是`Date`.当第二个`if`测试成功并导致函数返回时,将如何达到第三个`if`测试(因为`Date!= Object && Date!= Array`)? (5认同)
  • 怎么样`var obj = {}`和`obj.a = obj` (3认同)

大智慧.. 86

这就是我正在使用的:

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

  • 这是由于javascript`typeof null>"object"中的另一个愚蠢的事情``但是`Object.keys(null)> TypeError:一个不是对象的值的请求键.将条件更改为`if(typeof(obj) [i])=="object"&& obj [i]!= null)` (12认同)
  • 这似乎不对.`cloneObject({name:null})`=>`{"name":{}}` (8认同)

tfmontague.. 73

按性能深度复制: 从最好到最差排名

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

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

当数组包含数字和字符串时 - 函数如.slice(),. concat(),. splice(),赋值运算符"="和Underscore.js的克隆函数; 将制作数组元素的深层副本.

重新分配的性能最快:

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具有更好的性能:

  • 你的所有例子都很浅,一层.这不是一个好的答案.该问题涉及*深*克隆,即至少两个级别. (3认同)

Zibri.. 60

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


大智慧.. 53

我知道这是一个老帖子,但我认为这可能对下一个偶然发现的人有所帮助.

只要您不将对象分配给任何内容,它就不会在内存中保留任何引用.因此,要创建一个您想要在其他对象之间共享的对象,您必须创建一个这样的工厂:

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

  • 这个答案并不真正相关,因为问题是:给定实例b如何创建副本c何时不知道工厂a或不想使用工厂a.可能不想使用工厂的原因是在实例化之后b可能已经用附加数据(例如用户输入)初始化. (15认同)
  • 确实,这不是问题的真正答案,但我认为重要的是它在这里,因为它是我怀疑许多来到这里的人真正有意义的问题的答案. (11认同)
  • 对不起伙计们,我真的不明白为什么这么多的赞成票.克隆一个对象是一个非常清晰的概念,你可以从另一个对象中对一个对象进行对比,并且它与使用工厂模式创建一个新对象没什么关系. (8认同)

pvorb.. 52

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

你也可以在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' } }

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

  • 对于克隆任意嵌套对象,npm clone对我来说非常宝贵.这是正确的答案. (2认同)

Alireza.. 52

Cloning 一个对象在JS中始终是一个问题,但它完全是在ES6之前,我在下面列出了在JavaScript中复制对象的不同方法,想象你有下面的Object,并希望有一个深层副本:

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

希望这些帮助......

  • `Object.assign`确实_not_深拷贝.示例:`var x = {a:{b:"c"}}; var y = Object.assign({},x); xab ="d"`.如果这是深拷贝,`yab`仍然是`c`,但它现在是'd`. (55认同)
  • Object.assign()只克隆第一级属性! (8认同)
  • 什么是cloneSO()函数? (4认同)
  • 下划线中的clone不是当前版本中的深度克隆 (2认同)

itsadok.. 48

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

var newObject = _.clone(oldObject);

  • lodash有一个cloneDeep方法,它还支持另一个克隆来实现它的深度:http://lodash.com/docs#clone和http://lodash.com/docs#cloneDeep (23认同)
  • @opensas同意了.Lodash通常优于下划线 (11认同)
  • 我主张删除这个以及所有其他答案,这些答案只是对实用程序库的`.clone(...)`方法的单行引用.每个主要的图书馆都会有它们,并且重复简短的非详细答案对大多数访问者没有用,他们不会使用该特定的图书馆. (4认同)

Matt Browne.. 36

这是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();
    };
}


大智慧.. 31

以下内容创建同一对象的两个实例.我发现它并且正在使用它.它简单易用.

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

  • 对于一个简单的对象,Chrome的速度比给定的答案慢约6倍,并且随着对象复杂性的增加而变得慢得多.它可以非常快速地扩展,并且可以非常快速地阻塞您的应用程序. (4认同)
  • 此外,此方法丢失方法(或JSON中不允许的任何内容),加上 - JSON.stringify将Date对象转换为字符串,...而不是相反;)保持这个解决方案. (2认同)

TinhNQ.. 28

在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.)

参考medium.com

JSBEN.CH Performance Benchmarking Playground 1~3 http://jsben.ch/KVQLd 性能深度复制JavaScript中的对象


Chris Broski.. 23

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

  • 克隆/复制和继承之间的区别在于 - 使用您的示例,当我更改oldObject的属性时,该属性也会在newObject中更改.如果您制作副本,则无需更改newObject即可使用oldObject执行所需操作. (49认同)
  • 这将打破hasOwnProperty检查,因此它是克隆对象的一种非常黑客的方式,并会给你意想不到的结果. (13认同)
  • 如果我错了,请纠正我,但这不是Crockford的原型继承功能吗?它如何适用于克隆? (9认同)
  • 是的,我害怕这个讨论:克隆,复制和原型继承之间的实际区别是什么,何时应该使用每个以及本页面上的哪些功能实际上在做什么?我通过Google搜索"javascript copy object"找到了这个SO页面.我真正想要的是上面的功能,所以我回来分享.我的猜测是提问者也在寻找这个. (3认同)

opensas.. 23

Lodash有一个很好的_.cloneDeep(value)方法:

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

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

  • 谷歌搜索克隆JS对象请参考这里.我正在使用Lodash所以这个答案对我来说很重要.让我们不要回答所有"维基百科删除者"的答案. (4认同)
  • 我主张删除这个以及所有其他答案,这些答案只是对实用程序库的`.clone(...)`方法的单行引用.每个主要的图书馆都会有它们,并且重复简短的非详细答案对大多数访问者没有用,他们不会使用该特定的图书馆. (2认同)

Mark Cidade.. 22

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

  • 方法的问题是,如果你在obj中有子对象,它们的引用将被克隆,而不是每个子对象的值. (15认同)
  • 是.这只是一个浅拷贝,因此克隆将指向原始对象指向的完全相同的对象. (3认同)

Maël Nison.. 20

浅拷贝单行(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

  • 这对于简单对象可能没问题,但它只复制属性值.它不会触及原型链,并且使用`Object.keys`它会跳过不可枚举和继承的属性.此外,它通过直接分配丢失属性描述符. (6认同)

Dan Atkinson.. 17

仅仅因为我没有看到AngularJS提到并认为人们可能想知道......

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

  • @Galvani:应该注意的是,`jQuery.extend`和`angular.extend`都是浅拷贝.`angular.copy`是一个深层拷贝. (2认同)

大智慧.. 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)

  • 正如其他人在对Resig的回答的评论中指出的那样,如果要克隆类似数组的对象,则在扩展调用中将{}更改为[],例如jQuery.extend(true,[],obj) (14认同)

大智慧.. 15

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

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

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

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

请注意,源对象必须是纯JSON对象.这就是说,它的所有嵌套属性都必须是标量(如boolean,string,array,object等).任何函数或特殊对象(如RegExp或Date)都不会被克隆.

它有效吗?哎呀.我们已经尝试了各种克隆方法,这种方法效果最好.我相信一些忍者可以想出一个更快的方法.但我怀疑我们谈论的是边际收益.

这种方法简单易行.将它包装成一个便利功能,如果你真的需要挤出一些收益,请稍后再去.

现在,对于非纯JavaScript对象,没有一个非常简单的答案.实际上,由于JavaScript函数和内部对象状态的动态特性,不可能存在.深入克隆具有内部函数的JSON结构需要重新创建这些函数及其内部上下文.而JavaScript根本没有标准化的方法.

再次执行此操作的正确方法是通过在代码中声明和重用的便捷方法.方便的方法可以让您对自己的对象有所了解,这样您就可以确保在新对象中正确地重新创建图形.

我们写的是自己的,但我在这里看到了最好的一般方法:

http://davidwalsh.name/javascript-clone

这是正确的想法.作者(David Walsh)评论了广义函数的克隆.根据您的使用情况,您可以选择这样做.

主要思想是你需要特殊地处理你的函数(或原型类,即可以说)的实例化.在这里,他提供了几个RegExp和Date的例子.

这段代码不仅简短,而且非常易读.这很容易扩展.

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

你去吧 两种方法.在我看来,两者都很有效.


neatonk.. 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


azerafati.. 11

AngularJS

好吧,如果你使用角度,你也可以做到这一点

var newObject = angular.copy(oldObject);


大智慧.. 10

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


prograhammer.. 10

在这里以最大的选票不同意答案.一个递归深克隆要快得多JSON.parse(JSON.stringify(OBJ))中提到的方法.

  • Jsperf在这里排名第一:https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
  • 从上面的答案更新的Jsben表明,递归的深度克隆击败了所有其他提到的: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
}


大智慧.. 8

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

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


Buzinas.. 8

对于想要使用该JSON.parse(JSON.stringify(obj))版本但不丢失Date对象的人,可以使用方法第二个参数parse将字符串转换回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(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}


Cody.. 6

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

var o = {};

var oo = Object.create(o);

(o === oo); // => false

观看传统浏览器!


andrew.. 6

只有当你可以使用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;
}


Barry Staes.. 6

使用今天的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指出的,这只是一个浅薄的副本.属性的属性被复制为引用:更改属性将更改另一个对象/实例中的值.

  • 这不涉及深度合并.https://gist.github.com/jimbol/5d5a3e3875c34abcf60a (24认同)
  • 哇,这个答案是错的.您的方法都执行一个级别的浅层副本.任何人都在看这个答案,继续前进. (14认同)

user3071643.. 6

我使用npm克隆库.显然它也适用于浏览器.

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

let a = clone(b)


Shishir Aror.. 5

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

const clone = (o) =>
  typeof o === 'object' && o !== null ?      // only clone objects
  (Array.isArray(o) ?                        // if cloning an array
    o.map(e => clone(e)) :                   // clone each of its elements
    Object.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);

  • 请提供代码块旁边的解释,以便具有类似问题的其他人可以轻松了解正在发生的事情.就目前而言,这个问题是在低质量的帖子审核队列中. (5认同)

Daniel Barde.. 5

Lodash有一个功能,可以为你处理.

var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};

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

阅读这里的文档.


Mayur Agarwa.. 5

我迟到了回答这个问题,但我还有另一种克隆对象的方法:

   function cloneObject(obj) {
        if (obj === null || typeof(obj) !== 'object')
            return obj;
        var temp = obj.constructor(); // changed
        for (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 copy
var newObject = jQuery.extend(true, {}, a);

我已对代码进行了基准测试,您可以在此处测试结果:

并分享结果: 在此输入图像描述 参考文献:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty


归档时间:

查看次数:

1718765 次

最近记录:

10 月 前