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

Posted

技术标签:

【中文标题】在 JavaScript 中深度克隆对象的最有效方法是啥?【英文标题】:What is the most efficient way to deep clone an object in JavaScript?在 JavaScript 中深度克隆对象的最有效方法是什么? 【发布时间】:2022-01-24 03:33:55 【问题描述】:

克隆 javascript 对象最有效的方法是什么?我见过obj = eval(uneval(o)); 被使用,但that's non-standard and only supported by Firefox。 我做过obj = JSON.parse(JSON.stringify(o)); 之类的事情,但质疑效率。 我还看到了具有各种缺陷的递归复制函数。 我很惊讶没有规范的解决方案存在。

【问题讨论】:

Eval 并不邪恶。使用 eval 不好是。如果你害怕它的副作用,那你就用错了。您担心的副作用是使用它的原因。顺便说一句,有没有人真正回答了你的问题? 克隆对象是一项棘手的工作,尤其是对于任意集合的自定义对象。这可能是为什么没有开箱即用的方法。 eval() 通常是个坏主意,因为many Javascript engine's optimisers have to turn off when dealing with variables that are set via eval。仅在代码中包含 eval() 会导致性能下降。 Most elegant way to clone a JavaScript object的可能重复 请注意,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 【参考方案1】:

原生深度克隆

现在有一个名为 "structured cloning" 的 JS 标准,它在 Node 11 及更高版本中试验性地工作,将登陆浏览器,并且具有 polyfills for existing systems。

structuredClone(value)

如果需要,先加载 polyfill:

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

更多详情请见this answer。

旧答案

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

​​>

如果您在对象中不使用Dates、函数、undefinedInfinity、RegExps、Maps、Sets、Blob、FileLists、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's answer。

使用库进行可靠克隆

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

lodash - cloneDeep;可以通过 lodash.clonedeep 模块单独导入,如果您尚未使用提供深度克隆功能的库,这可能是您的最佳选择 AngularJS - angular.copy jQuery - jQuery.extend(true, , oldObject); .clone() 只克隆 DOM 元素 只是图书馆-just-clone;零依赖 npm 模块库的一部分,它只做一件事。 适合各种场合的无罪实用程序。

ES6(复制)

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

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

【讨论】:

当心! var A = b: [ a: [ 1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] ] ; B = Object.assign( , A ); delete B.b[0].b; 它也会修改对象 A ! @Gabriel Hautclocq 这是因为A.bB.b 都指向内存中的同一个对象。如果A 的属性具有非对象值(如数字或字符串),它将被正常复制。但是,当复制包含对象值的属性时,它是按引用复制的,而不是按值复制的。另外,请记住,数组是 JS 中的对象。证明:typeof [] == 'object' && [] instanceof Array @Unicornist 是的,这就是为什么 Object.assign 没有回答以下问题:“在 JavaScript 中深度克隆对象的最有效方法是什么?”。所以至少它不应该作为 ES6 的深度克隆解决方案出现。标题“ES6”具有误导性,至少应该更改以反映这不是深度克隆方法。 “浅”这个词很容易被忽视,很多人只是采用他们在 Stack Overflow 中找到的最简单的解决方案,而没有阅读所有内容。依赖 Object.assign 进行对象克隆是危险的。因此我的评论。 我使用了一个名为真正快速深度克隆的库:github.com/davidmarkclements/rfdc 非常适合我。 @Ricardo 当然,在我写下我的评论之后,您可以看到答案的历史记录,看到在“ES6”之后添加了“(浅拷贝)”。现在更清楚这是一个浅拷贝。【参考方案2】:

查看此基准: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() 不会 转换回 @987654336 @ 目的。 See this answer for more details.

另外,请注意,至少在 Chrome 65 中,本机克隆不是要走的路。根据 JSPerf 的说法,通过创建一个新函数执行本机克隆比使用 JSON.stringify 慢近 800 倍,后者的速度非常快。

Update for ES6

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

Object.assign(, obj);

【讨论】:

请注意,您的工作台有两个错误:首先,它将一些浅克隆(lodash _.cloneObject.assign)与一些深度克隆(JSON.parse(JSON.stringify()))进行了比较。其次,它对 lodash 说的是“深度克隆”,但实际上是浅层克隆。【参考方案3】:

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

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

【讨论】:

对象有属性,而不是变量。 ;-) 函数日期以及 对具有循环属性的对象失败【参考方案4】:

结构化克隆

2022 年更新:structuredClone global function 已在 Firefox 94、Node 17 和 Deno 1.14 中可用

html 标准包括an internal structured cloning/serialization algorithm,可以创建对象的深层克隆。它仍然仅限于某些内置类型,但除了 JSON 支持的少数类型之外,它还支持 Dates、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、稀疏数组、类型化数组,未来可能还会更多.它还保留克隆数据中的引用,允许它支持会导致 JSON 错误的循环和递归结构。

Node.js 中的支持:

structuredClone global function 由 Node 17.0 提供:

const clone = structuredClone(original);

以前的版本:Node.js 中的 v8 模块(截至 Node 11)exposes the structured serialization API directly,但此功能仍标记为“实验性”,并且在未来版本中可能会更改或删除。如果您使用的是兼容版本,则克隆对象非常简单:

const v8 = require('v8');

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

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

structuredClone global function 很快将由所有主要浏览器提供(之前已在whatwg/html#793 on GitHub 中讨论过)。它看起来/将看起来像这样:

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

使用示例:

'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 constructor 创建其关联数据的结构化克隆。它还尝试向用户显示浏览器通知,但除非您请求通知权限,否则这将静默失败。如果您有其他用途的许可,我们将立即关闭我们创建的通知。

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

【讨论】:

这是大错特错!该 API 不适合以这种方式使用。 作为在 Firefox 中实现 pushState 的人,我对这次 hack 感到自豪和厌恶的奇怪混合。干得好,伙计们。 pushState 或 Notification hack 不适用于 Function 等某些对象类型 @ShishirArora 你说得对,我刚试了一下,它会抛出“未捕获的 DOMException:无法克隆对象。”通知黑客也是如此。 有谁知道原生 v8 结构化克隆是否容易受到原型污染?【参考方案5】:

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

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;

【讨论】:

【参考方案6】:

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

Object.assign 方法是 ECMAScript 2015 (ES6) 标准的一部分,可以满足您的需求。

var clone = Object.assign(, obj);

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

Read more...

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

【讨论】:

这不会递归复制,因此并不能真正解决克隆对象的问题。 这个方法有效,虽然我测试了一些并且 _.extend(, (obj)) 是迄今为止最快的:比 JSON.parse 快 20 倍,比 Object.assign 快 60%,例如。它很好地复制了所有子对象。 @mwhite 克隆和深度克隆是有区别的。这个答案实际上确实克隆了,但它没有深度克隆。 问题是关于递归副本。 Object.assign 以及给定的自定义分配,不会递归复制【参考方案7】:

这是我正在使用的:

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;

【讨论】:

尝试:var a = b: 1, c: 3, d: a: 10, g: 20, h: today: new Date() ;不为我工作。但是Object.assign(, a) 做到了。 更糟糕的是,试试let o = ; o.o = o; cloneObject(o);【参考方案8】:

代码:

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

【讨论】:

我不处理圆形结构【参考方案9】:

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

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

地点:

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

深拷贝原语数组:

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

要深度复制仅包含基元(即数字、字符串和布尔值)的数组,可以使用重新赋值、slice()concat() 和 Underscore 的 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(o) 
  let 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;


let arr3b = copy(arr3a);

或使用第三方实用功能:

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

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

js-deep-copy jquery-extend-vs-json-parse

【讨论】:

使用 for-in 循环,您应该使用 hasOwnProperty 排除继承的属性。我在Object.keys 上使用(可能更快)纯 for 循环。 在深拷贝中,您不想复制继承的属性吗?另外,请注意,调用 hasOwnProperty 方法会为每个键创建性能损失(将函数调用推入和推下堆栈,并执行方法代码)。【参考方案10】:

在 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 Performance Benchmarking Playground 1~3 http://jsben.ch/KVQLd

【讨论】:

嘿,你的最后一个例子是错误的。在我看来,你必须使用 _clone 而不是 _cloneDeep 作为错误的例子。 这个创建的方法 (2.) 不适用于数组,不是吗? 方法 #2 容易受到原型污染,类似于 lodash 的 defaultsDeep 所发生的情况。如果(i === '__proto__')不应该复制,如果(i === 'constuctor' &amp;&amp; typeof obj[i] === 'function')不应该复制。【参考方案11】:

在 JS 中克隆一个对象一直是一个问题,但这都是在 ES6 之前的事情,我在下面列出了在 JavaScript 中复制对象的不同方法,假设你有下面的 Object 并且想要对其进行深层复制:

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

在不改变原点的情况下复制这个对象的方法很少:

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

    ES5+,使用JSON.parseJSON.stringify

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

    角度:

    var deepCopyObj = angular.copy(obj);
    

    jQuery:

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

    Underscore.js 和 Lodash:

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

希望这些帮助……

【讨论】:

【参考方案12】:
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);

【讨论】:

【参考方案13】:

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

你也可以find it on 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'  

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

【讨论】:

【参考方案14】:

我知道这是一篇旧帖子,但我认为这可能对下一个绊倒的人有所帮助。

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

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

【讨论】:

【参考方案15】:

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

var newObject = _.clone(oldObject);

【讨论】:

这是一个浅拷贝,而不是像 OP 正在寻找的深拷贝。【参考方案16】:

这是上面 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://***.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();
    ;

【讨论】:

【参考方案17】:

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

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

【讨论】:

【参考方案18】:

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

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


var newObject = object(oldObject);

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


编辑:

这是 Object.create 的 polyfill,所以你也可以使用它。

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

【讨论】:

【参考方案19】:

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

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

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

【讨论】:

【参考方案20】:
function clone(obj)
  var clone = ;
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 

【讨论】:

【参考方案21】:

浅拷贝单行(ECMAScript 5th edition):

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 6th edition, 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

【讨论】:

【参考方案22】:

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

【讨论】:

【参考方案23】:

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

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

【讨论】:

或者可以和jQiery一样使用extend:angular.extend(,obj); @Galvani:需要注意的是jQuery.extendangular.extend都是浅拷贝。 angular.copy 是深拷贝。【参考方案24】:

根据您的目标是否是克隆“普通的旧 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 的示例。

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

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

所以你去。两种方法。在我看来,两者都很有效。

【讨论】:

【参考方案25】:

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

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

我已经对代码进行了基准测试,您可以测试结果here:

并分享结果: 参考文献:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

【讨论】:

这很有趣,但是当我运行你的测试时,它实际上让我觉得方法 1 是最慢的一种 和我一样,block 1最低! 唯一对我有用的解决方案!必须深度克隆一个对象,该对象包含具有函数属性的其他对象。完美。 为什么设置obj['isActiveClone'] = null然后删除呢?你为什么不打电话给obj.hasOwnProperty(key)【参考方案26】:

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

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

【讨论】:

【参考方案27】:

对于想要使用JSON.parse(JSON.stringify(obj)) 版本但又不会丢失Date 对象的人,您可以使用second argument of parse method 将字符串转换回Date:

function clone(obj) 
  var regExp = /^\d4-\d2-\d2T\d2:\d2:\d2\.\d3Z$/;
  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)

【讨论】:

不是 100% 的克隆【参考方案28】:

仅当您可以使用ECMAScript 6 或transpilers 时。

特点:

复制时不会触发 getter/setter。 保留 getter/setter。 保留原型信息。 适用于 object-literalfunctional 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;

【讨论】:

Date等数据类型存在问题 如果与具有null 原型的对象一起使用,这将创建对同一对象实例的引用( 深复制它),因为Object.create(null) instanceof Object 为假.【参考方案29】:

我不同意票数最高的答案here。 递归深度克隆比提到的 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

【讨论】:

我喜欢这种方法,但它不能正确处理日期;考虑在检查 null 后添加类似 if(o instanceof Date) return new Date(o.valueOf()); 的内容` 循环引用崩溃。 在最新的稳定版 Firefox 中,这比 Jsben.ch 链接上的其他策略要长一个数量级或更多。它在错误的方向上击败了其他人。【参考方案30】:

这是一个全面的 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;
;

【讨论】:

它将原语转换为包装对象,在大多数情况下不是一个好的解决方案。 @DanubianSailor - 我不认为它会......它似乎从一开始就立即返回原语,并且似乎没有对它们做任何将它们变成包装器对象的事情当它们被退回时。

以上是关于在 JavaScript 中深度克隆对象的最有效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

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

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

Stackoverflow热门问题

具有属性子集的对象数组克隆

在 Ruby 中深度复制对象的最有效方法是啥?

26.JavaScript实现对象混合与对象浅度克隆和对象的深度克隆