如何在javascript中深度克隆

How to Deep clone in javascript

提问人:Raynos 提问时间:12/16/2010 最后编辑:0x90Raynos 更新时间:9/29/2023 访问量:206053

问:

如何深度克隆 JavaScript 对象?

我知道有各种基于框架的功能,例如 和,但我不想使用这样的框架。JSON.parse(JSON.stringify(o))$.extend(true, {}, o)

创建深度克隆的最优雅或最有效的方法是什么。

我们确实关心像克隆阵列这样的边缘情况。不破坏原型链,处理自我参考。

我们不关心支持DOM对象的复制,因为出于这个原因而存在。.cloneNode

由于我主要想在使用 V8 引擎的 ES5 功能时使用深度克隆是可以接受的。node.js

[编辑]

在有人提出建议之前,让我提一下,通过原型继承对象来创建副本和克隆它之间存在明显区别。前者把原型链弄得一团糟。

[进一步编辑]

读完你的回答后,我发现克隆整个对象是一个非常危险和困难的游戏。以以下基于闭包的对象为例

var o = (function() {
     var magic = 42;

     var magicContainer = function() {
          this.get = function() { return magic; };
          this.set = function(i) { magic = i; };
     }

      return new magicContainer;
}());

var n = clone(o); // how to implement clone to support closures

有没有办法编写克隆函数来克隆对象,在克隆时具有相同的状态,但如果不在 JS 中编写 JS 解析器就无法更改状态。o

现实世界中应该不再需要这样的功能。这仅仅是学术兴趣。

JavaScript的

评论

3赞 Raynos 12/16/2010
在它被标记为重复之前,我查看了 stackoverflow.com/questions/122102/...,但没有找到任何处理所有边缘情况的答案。
0赞 trincot 9/27/2019
如果没有对象本身的“帮助”,就不可能实现“进一步编辑”部分中的要求,因为这些私有变量是真正的私有变量,因此无法通过泛型函数访问。所讨论的对象应该公开自己的定制方法。cloneclone
1赞 shreyasm-dev 5/2/2020
这回答了你的问题吗?在 JavaScript 中深度克隆对象的最有效方法是什么?
2赞 ruffin 9/1/2020
当你说“我知道有各种基于 JSON.parse(JSON.stringify(o)) 和 $.extend(true, {}, o) 等框架的函数,但我不想使用这样的框架时,”我很困惑。 当然是一个外部库(jQuery),但JSON是vanilla JavaScript的一部分。(如果你还记得的话)对使用的厌恶是什么?在 2010 年,也许是缺乏对 IE 6 和 7 的支持$JSON
1赞 ruffin 8/30/2023
@r3wt公平,但我认为我的观点是,最终所有这些方法似乎都存在边缘漏洞,它们的深度克隆过程不起作用,这意味着序列化为 json 并了解如何解除冻结对象模型的规则可能会产生“最佳准常规”结果。也就是说,我敢打赌,我们总是可以说,“X 策略不适用于需要 [m, n, o] 的 [某些对象子集]”,并特别想知道 OP 是如何失败的。JSON.stringify

答:

84赞 nemisj 12/16/2010 #1

这真的取决于你想克隆什么。这是一个真正的 JSON 对象还是 JavaScript 中的任何对象?如果你想做任何克隆,它可能会给你带来一些麻烦。哪个麻烦?我将在下面解释它,但首先,一个克隆对象字面量、任何基元、数组和 DOM 节点的代码示例。

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testing that this is DOM
            if (item.nodeType && typeof item.cloneNode == "function") {
                result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                if (item instanceof Date) {
                    result = new Date(item);
                } else {
                    // it is an object literal
                    result = {};
                    for (var i in item) {
                        result[i] = clone( item[i] );
                    }
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}

var copy = clone({
    one : {
        'one-one' : new String("hello"),
        'one-two' : [
            "one", "two", true, "four"
        ]
    },
    two : document.createElement("div"),
    three : [
        {
            name : "three-one",
            number : new Number("100"),
            obj : new function() {
                this.name = "Object test";
            }   
        }
    ]
})

现在,让我们谈谈在开始克隆真实对象时可能遇到的问题。我现在说的是,你通过做类似的事情来创建的对象

var User = function(){}
var newuser = new User();

当然你可以克隆它们,这不是问题,每个对象都公开构造函数属性,你可以用它来克隆对象,但它并不总是有效。你也可以在这个对象上做简单的事情,但它会朝着同一个方向发展——麻烦。我还在代码中包含了克隆功能,但它被语句排除在外。for inif( false )

那么,为什么克隆会很痛苦呢?首先,每个对象/实例都可能有一些状态。例如,你永远无法确定你的对象没有私有变量,如果是这种情况,通过克隆对象,你只是破坏了状态。

想象一下没有状态,这很好。然后我们还有另一个问题。通过“构造函数”方法克隆会给我们带来另一个障碍。这是一个参数依赖关系。你永远无法确定,创造这个对象的人没有做过,某种

new User({
   bike : someBikeInstance
});

如果是这种情况,那么您就不走运了,someBikeInstance 可能是在某个上下文中创建的,并且该上下文对于克隆方法来说是未知的。

那该怎么办呢?你仍然可以做解决方案,并将这些对象视为普通的对象文字,但也许根本不克隆这些对象,而只是传递这个对象的引用是一个想法?for in

另一种解决方案是 - 您可以设置一个约定,即所有必须克隆的对象都应该自己实现这部分并提供适当的 API 方法(如 cloneObject )。正在为 DOM 做些什么。cloneNode

由您决定。

评论

0赞 Raynos 12/16/2010
我自己遇到了处理使用闭包来隐藏状态的对象的障碍。如何克隆一个对象及其整个状态,但仍然确保克隆不能自行改变原始状态。不好的一点是,给定构造函数和 item 对象,您应该能够 RE 传递给构造函数的任何参数。result = new item.constructor();
8赞 Michiel Kalkman 12/16/2010
@Raynos :如果对象使用闭包来隐藏状态,则无法克隆它们。因此,术语“关闭”。正如 nemisj 在最后所说,最好的方法是实现一个用于克隆(或序列化/反序列化)的 API 方法,如果这是一个选项的话。
0赞 Raynos 12/16/2010
@MichielKalkman我有一种感觉,情况就是这样。尽管有人可能对此有一个非常聪明的解决方案。
2赞 Gui Imamura 10/7/2015
@GabrielPetrovay 从功能的角度来看,这是“无用的”,因为它永远不会运行,但它的学术目的是展示一个假设的实现,人们可能会尝试使用,由于后面解释的原因,作者没有建议。所以,是的,每次评估条件时,它都会触发子句,但代码存在是有原因的。ifelse
1赞 sgrtho 9/19/2017
@nemisj:归一化将失败,因为 .不是吗?Booleannew Boolean(new Boolean(false)) => [Boolean: true]
4赞 Lawrence Dol 9/20/2014 #2

正如其他人在这个问题和类似问题上所指出的那样,在一般意义上克隆一个“对象”在 JavaScript 中是可疑的。

但是,有一类对象,我称之为“数据”对象,即那些仅从文字和/或简单的属性赋值构造的对象,或者从JSON反序列化的对象,对于这些对象,想要克隆是合理的。就在今天,我想人为地将从服务器接收的数据膨胀 5 倍,以测试大型数据集会发生什么,但对象(数组)及其子对象必须是不同的对象才能正常运行。克隆允许我这样做来增加我的数据集:{ ... }

return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));

我经常克隆数据对象的另一个地方是将数据提交回主机,在发送数据之前,我想从数据模型中的对象中剥离状态字段。例如,我可能希望在克隆对象时从对象中删除所有以“_”开头的字段。

这是我最终编写的代码,用于通用地执行此操作,包括支持数组和选择要克隆的成员的选择器(它使用“路径”字符串来确定上下文):

function clone(obj,sel) {
    return (obj ? _clone("",obj,sel) : obj);
    }

function _clone(pth,src,sel) {
    var ret=(src instanceof Array ? [] : {});

    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }

        var val=src[key], sub;

        if(sel) {
            sub+=pth+"/"+key;
            if(!sel(sub,key,val)) { continue; }
            }

        if(val && typeof(val)=='object') {
            if     (val instanceof Boolean) { val=Boolean(val);        }
            else if(val instanceof Number ) { val=Number (val);        }
            else if(val instanceof String ) { val=String (val);        }
            else                            { val=_clone(sub,val,sel); }
            }
        ret[key]=val;
        }
    return ret;
    }

假设根对象为非空且没有成员选择,最简单合理的深度克隆解决方案是:

function clone(src) {
    var ret=(src instanceof Array ? [] : {});
    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }
        var val=src[key];
        if(val && typeof(val)=='object') { val=clone(val);  }
        ret[key]=val;
        }
    return ret;
    }
203赞 G. Ghez 1/30/2015 #3

很简单的方法,也许太简单了:

var cloned = JSON.parse(JSON.stringify(objectToClone));

评论

26赞 matthoiland 8/3/2015
除非对象值是一个函数,否则很好,此时您必须使用类似于已接受答案的东西。或者使用像 Lodash 那样的辅助函数。cloneDeep
45赞 Jos de Jong 9/20/2015
如果对象值是函数,则该对象不是 JSON。
8赞 G. Ghez 3/10/2016
什么用例可以证明克隆函数而不仅仅是使用它是合理的?
4赞 Peter 7/11/2017
如果我没记错的话,这也将日期转换为字符串。
5赞 Peter 7/11/2017
@G.Ghez:如果克隆包含函数的对象,则该函数将丢失。
13赞 svarog 5/28/2015 #4

Underscore.js contrib 库有一个称为快照的函数,用于深度克隆对象

来自源代码的片段:

snapshot: function(obj) {
  if(obj == null || typeof(obj) != 'object') {
    return obj;
  }

  var temp = new obj.constructor();

  for(var key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = _.snapshot(obj[key]);
    }
  }

  return temp;
}

将库链接到您的项目后,只需使用

_.snapshot(object);

评论

5赞 Ronen Ness 10/21/2015
很好的解决方案,只是要记住一点:克隆和原始共享相同的原型。如果这是一个问题,可以在“return temp”的正上方添加“temp.__proto__ = .snapshot(obj.__proto_);”,并支持属性标记为“no enumerate”的内置类,您可以迭代getOwnPropertyNames()而不是“for(obj中的var键)”
1赞 Danyu 8/15/2021
这不会处理以下情况:(1) props 是 Symbol (2) 循环引用
53赞 tim-montague 1/6/2016 #5

深度复制 Javascript 对象的组合是一种无效的黑客攻击,因为它是针对 JSON 数据的。它不支持 或 的值,并且在将 Javascript 对象“字符串化”(编组)为 JSON 时会忽略它们(或它们)。JSON.parse(JSON.stringify())undefinedfunction () {}null

更好的解决方案是使用深度复制功能。下面的函数深度复制对象,不需要第三方库(jQuery、LoDash 等)。

function copy(aObject) {
  // Prevent undefined objects
  // if (!aObject) return aObject;

  let bObject = Array.isArray(aObject) ? [] : {};

  let value;
  for (const key in aObject) {

    // Prevent self-references to parent object
    // if (Object.is(aObject[key], aObject)) continue;
    
    value = aObject[key];

    bObject[key] = (typeof value === "object") ? copy(value) : value;
  }

  return bObject;
}

注意:此代码可以检查简单的自引用(取消注释该部分),但您还应尽可能避免创建具有自引用的对象。请参阅:https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references// Prevent self-references to parent object

评论

10赞 Déjà vu 10/14/2017
除非 aObject(或它包含的另一个对象)包含对自身的自我引用......堆栈溢出™!
5赞 Déjà vu 10/16/2017
var o = { a:1, b:2 } ; o["oo"] = { c:3, m:o };
6赞 David Kirkland 11/20/2017
我喜欢这个解决方案。对我来说,唯一的解决方法是处理空值:bObject[k] = (v === null) ? null : (typeof v === "object") ? copy(v) : v;
2赞 icc97 4/19/2018
这个功能简单易懂,几乎可以捕捉到所有情况。在 JavaScript 世界中,这几乎是你能得到的完美。
1赞 noffle 3/6/2021
@tfmontague imo 来说,这正是 git 和 npm 的用途,而不是试图在问答网站上迭代代码片段
1赞 Dmitriy Pichugin 9/22/2016 #6

我注意到Map应该需要特殊处理,因此对于此线程中的所有建议,代码将是:

function deepClone( obj ) {
    if( !obj || true == obj ) //this also handles boolean as true and false
        return obj;
    var objType = typeof( obj );
    if( "number" == objType || "string" == objType ) // add your immutables here
        return obj;
    var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
    if( obj instanceof Map )
        for( var key of obj.keys() )
            result.set( key, deepClone( obj.get( key ) ) );
    for( var key in obj )
        if( obj.hasOwnProperty( key ) )
            result[key] = deepClone( obj[ key ] );
    return result;
}
35赞 trincot 10/28/2016 #7

现在,Web API 中有 structuredClone,它也适用于循环引用。


上一个答案

下面是一个 ES6 函数,它也适用于具有循环引用的对象:

function deepClone(obj, hash = new WeakMap()) {
    if (Object(obj) !== obj) return obj; // primitives
    if (hash.has(obj)) return hash.get(obj); // cyclic reference
    const result = obj instanceof Set ? new Set(obj) // See note about this!
                 : obj instanceof Map ? new Map(Array.from(obj, ([key, val]) => 
                                        [key, deepClone(val, hash)])) 
                 : obj instanceof Date ? new Date(obj)
                 : obj instanceof RegExp ? new RegExp(obj.source, obj.flags)
                 // ... add here any specific treatment for other classes ...
                 // and finally a catch-all:
                 : obj.constructor ? new obj.constructor() 
                 : Object.create(null);
    hash.set(obj, result);
    return Object.assign(result, ...Object.keys(obj).map(
        key => ({ [key]: deepClone(obj[key], hash) }) ));
}

// Sample data
var p = {
  data: 1,
  children: [{
    data: 2,
    parent: null
  }]
};
p.children[0].parent = p;

var q = deepClone(p);

console.log(q.children[0].parent.data); // 1

关于集合和贴图的说明

如何处理 Sets 和 Maps 的键是值得商榷的:这些键通常是基元(在这种情况下没有争议),但它们也可以是对象。在这种情况下,问题就变成了:是否应该克隆这些密钥?

有人可能会争辩说,应该这样做,这样,如果这些对象在副本中发生了突变,则原始对象不会受到影响,反之亦然。

另一方面,人们会希望,如果 Set/Map 一个键,这在原始和副本中都应该是正确的——至少在对它们中的任何一个进行任何更改之前。如果副本是一个 Set/Map,其中包含以前从未发生过的键(因为它们是在克隆过程中创建的),那就太奇怪了:当然,这对于任何需要知道给定对象是否是该 Set/Map 中的键的代码都不是很有用。has

正如你所注意到的,我更倾向于第二种意见:Sets 和 Maps 的键是应该保持不变的值(可能是引用)。

这种选择通常也会与其他(可能是自定义)对象一起出现。没有通用的解决方案,因为很大程度上取决于克隆对象在特定情况下的预期行为方式。

评论

1赞 mkeremguc 11/9/2017
不处理日期和正则表达式
1赞 trincot 11/9/2017
@mkeremguc,感谢您的评论。我更新了代码以支持日期和正则表达式。
1赞 Robert Biggs 10/6/2018
如果我没记错的话,您可以通过以下方式添加对 Sets 的支持:if (object instanceof Set) Array.from(object, val => result.add(deepClone(val, hash)));
1赞 trincot 10/16/2018
@RobertBiggs,这是一种可能性,但在我看来,如果一个 Set 具有某个密钥,那么在该 Set 的克隆版本中也应该如此。使用您建议的代码,如果键是对象,则不成立。因此,我建议不要克隆密钥 - 我真的认为它会表现得更符合预期。请看我在这方面的答复的更新。
1赞 Robert Biggs 10/17/2018
没错,但如果你正在使用需要不可变数据的反应式代码,那么你也需要深度克隆的 Set。我不得不承认,我有时确实想要一个带有指向远程对象的指针的对象,这样我就可以从那里改变它们,而不必联系原始对象。也就是说,随着时间的流逝,我似乎越来越不机智地这样做了。在更多情况下,拥有不可变数据比相反更有意义。
3赞 Stitch 9/15/2017 #8

这是我使用的深度克隆方法,我认为是这样 太好了,希望你能提出建议

function deepClone (obj) {
    var _out = new obj.constructor;

    var getType = function (n) {
        return Object.prototype.toString.call(n).slice(8, -1);
    }

    for (var _key in obj) {
        if (obj.hasOwnProperty(_key)) {
            _out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
        }
    }
    return _out;
}
1赞 Eric Grotke 12/20/2017 #9

这适用于数组、对象和基元。在两种遍历方法之间切换的双重递归算法:

const deepClone = (objOrArray) => {

  const copyArray = (arr) => {
    let arrayResult = [];
    arr.forEach(el => {
        arrayResult.push(cloneObjOrArray(el));
    });
    return arrayResult;
  }

  const copyObj = (obj) => {
    let objResult = {};
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        objResult[key] = cloneObjOrArray(obj[key]);
      }
    }
    return objResult;
  }

  const cloneObjOrArray = (el) => {
    if (Array.isArray(el)) {
      return copyArray(el);
    } else if (typeof el === 'object') {
      return copyObj(el);
    } else {
      return el;
    }
  }

  return cloneObjOrArray(objOrArray);
}
0赞 No one 3/11/2018 #10

我们可以利用递归来制作 deepCopy。它可以创建数组、对象、对象数组、具有函数的对象的副本。 如果需要,您可以为其他类型的数据结构(如 Map 等)添加函数。

function deepClone(obj) {
         var retObj;
        _assignProps = function(obj, keyIndex, retObj) {
               var subType = Object.prototype.toString.call(obj[keyIndex]);
               if(subType === "[object Object]" || subType === "[object Array]") {
                    retObj[keyIndex] = deepClone(obj[keyIndex]);
               }
               else {
                     retObj[keyIndex] = obj[keyIndex];
               }
        };

        if(Object.prototype.toString.call(obj) === "[object Object]") {
           retObj = {};
           for(key in obj) {
               this._assignProps(obj, key, retObj);
           }
        }
        else if(Object.prototype.toString.call(obj) == "[object Array]") {
           retObj = [];
           for(var i = 0; i< obj.length; i++) {
              this._assignProps(obj, i, retObj);
            }
        };

        return retObj;
    };
4赞 CPHPython 3/26/2018 #11

Lo-Dash 现在是 Underscore.js 的超集,具有几个深度克隆函数:

作者本人的回答

lodash underscore提供 build 以确保与最新稳定版本的 Underscore 兼容。

评论

1赞 Femi Oni 11/24/2019
问题说“我不想使用库”
0赞 CPHPython 11/26/2019
@FemiOni这个问题没有任何关于库的内容(即使在它的旧编辑中)......这里的其他一些答案也使用一个或另一个库。
1赞 CPHPython 11/27/2019
@FemiOni昨天的回答被否决了。我想知道...无论如何,这是一个学习的地方,以防万一有人真的要实现深度克隆本身,lodash 源代码库克隆可能会提供一些想法。
0赞 CPHPython 11/27/2019
@FemiOni JSON 对象既不是库也不是框架......如果你打算实现这个功能,我建议你查看一个开源库,并使用你需要的部分(许多已经测试了多年)。从长远来看,它将避免错误和遗漏的考虑因素。
2赞 icc97 4/23/2018 #12

现实世界中应该不再需要这样的功能。这仅仅是学术兴趣。

作为纯粹的练习,这是一种更实用的方式。这是@tfmontague答案的延伸,因为我建议在那里添加一个防护块。但是,鉴于我不得不使用 ES6 并将所有东西功能化,这是我的拉皮条版本。它使逻辑复杂化,因为您必须映射数组并减少对象,但它避免了任何突变。

const cloner = (x) => {
    const recurseObj = x => (typeof x === 'object') ? cloner(x) : x
    const cloneObj = (y, k) => {
        y[k] = recurseObj(x[k])
        return y
    }
    // Guard blocks
    // Add extra for Date / RegExp if you want
    if (!x) {
        return x
    }
    if (Array.isArray(x)) {
        return x.map(recurseObj)
    }
    return Object.keys(x).reduce(cloneObj, {})
}
const tests = [
    null,
    [],
    {},
    [1,2,3],
    [1,2,3, null],
    [1,2,3, null, {}],
    [new Date('2001-01-01')], // FAIL doesn't work with Date
    {x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]},
    {
        obj : new function() {
            this.name = "Object test";
        }
    } // FAIL doesn't handle functions
]
tests.map((x,i) => console.log(i, cloner(x)))

2赞 Ronald C 12/21/2018 #13

我对所有答案的补充

function deepCopy(arr) {
  if (typeof arr !== 'object') return arr
  if (Array.isArray(arr)) return [...arr].map(deepCopy)
  for (const prop in arr) 
    copy[prop] = deepCopy(arr[prop])
  return copy
}
0赞 Alexander Ladonin 3/15/2019 #14

使用 immutableJS

import { fromJS } from 'immutable';

// An object we want to clone
let objA = { 
   a: { deep: 'value1', moreDeep: {key: 'value2'} } 
};

let immB = fromJS(objA); // Create immutable Map
let objB = immB.toJS(); // Convert to plain JS object

console.log(objA); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
console.log(objB); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }

// objA and objB are equalent, but now they and their inner objects are undependent
console.log(objA === objB); // false
console.log(objA.a === objB.a); // false
console.log(objA.moreDeep === objB.moreDeep); // false

或者 lodash/merge

import merge from 'lodash/merge'

var objA = {
    a: [{ 'b': 2 }, { 'd': 4 }]
};
// New deeply cloned object:
merge({}, objA ); 

// We can also create new object from several objects by deep merge:
var objB = {
    a: [{ 'c': 3 }, { 'e': 5 }]
};
merge({}, objA , objB ); // Object { a: [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
0赞 Tom Lecoz 7/17/2019 #15

这个,使用循环引用,对我有用

 //a test-object with circular reference :
 var n1 = {   id:0,   text:"aaaaa",   parent:undefined} 
 var n2 = {  id:1,   text:"zzzzz",   parent:undefined } 
 var o = { arr:[n1,n2],   parent:undefined } 
 n1.parent = n2.parent = o;
 var obj = {   a:1,   b:2,   o:o }
 o.parent = obj;

 function deepClone(o,output){ 

     if(!output) output = {};  
     if(o.______clone) return o.______clone;
     o.______clone = output.______clone = output;

   for(var z in o){

     var obj = o[z];
     if(typeof(obj) == "object") output[z] = deepClone(obj)
     else output[z] = obj; 
    }

   return output;
}

console.log(deepClone(obj));
3赞 Kooldandy 8/5/2019 #16

以下函数是深度克隆 javascript 对象的最有效方法。

function deepCopy(obj){
    if (!obj || typeof obj !== "object") return obj;

    var retObj = {};

    for (var attr in obj){
        var type = obj[attr];

        switch(true){
            case (type instanceof Date):
                var _d = new Date();
                _d.setDate(type.getDate())
                retObj[attr]= _d;
                break;

            case (type instanceof Function):
                retObj[attr]= obj[attr];
                break;

            case (type instanceof Array):
                var _a =[];
                for (var e of type){
                    //_a.push(e);
                    _a.push(deepCopy(e));
                }
                retObj[attr]= _a;
                break;

            case (type instanceof Object):
                var _o ={};
                for (var e in type){
                    //_o[e] = type[e];
                    _o[e] = deepCopy(type[e]);
                }
                retObj[attr]= _o;
                break;

            default:
                retObj[attr]= obj[attr];
        }
    }
    return retObj;
}

var obj = {
    string: 'test',
    array: ['1'],
    date: new Date(),
    object:{c: 2, d:{e: 3}},
    function: function(){
        return this.date;
    }
};

var copyObj = deepCopy(obj);

console.log('object comparison', copyObj === obj); //false
console.log('string check', copyObj.string === obj.string); //true
console.log('array check', copyObj.array === obj.array); //false
console.log('date check', copyObj2.date === obj.date); //false
console.log('object check', copyObj.object === obj.object); //false
console.log('function check', copyObj.function() === obj.function()); //true

评论

2赞 Fabian von Ellerts 8/14/2019
你有什么论据支持你的说法吗?
0赞 Kooldandy 8/14/2019
我在函数下面给出了一个示例。你有什么疑问吗?
0赞 Fabian von Ellerts 8/14/2019
这些例子表明该函数正在工作,这很酷,但为什么它是“最有效的方式”?
0赞 Kooldandy 8/14/2019
因为它在单个循环中以递归方式复制对象的属性。此外,日期、函数、对象、数组、数字、字符串都得到妥善处理。你还有其他办法吗?
0赞 Eli O. 5/25/2023
为什么许多测试的检查是错误的?
0赞 Tejas Pawar 3/13/2020 #17

var newDate = 新日期(this.oldDate); 我将oldDate传递给函数并从this.oldDate生成newDate,但它也更改了this.oldDate。所以我使用了那个解决方案,它奏效了。

0赞 pbatey 4/5/2020 #18

此解决方案将避免使用 [...target] 或 {...目标}

function shallowClone(target) {
  if (typeof a == 'array') return [...target]
  if (typeof a == 'object') return {...target}
  return target
}

/* set skipRecursion to avoid throwing an exception on recursive references */
/* no need to specify refs, or path -- they are used interally */
function deepClone(target, skipRecursion, refs, path) {
  if (!refs) refs = []
  if (!path) path = ''
  if (refs.indexOf(target) > -1) {
    if (skipRecursion) return null
    throw('Recursive reference at ' + path)
  }
  refs.push(target)
  let clone = shallowCopy(target)
  for (i in target) target[i] = deepClone(target, refs, path + '.' + i)
  return clone
}
3赞 Mamé 8/29/2020 #19

避免使用此方法

let cloned = JSON.parse(JSON.stringify(objectToClone));

为什么?此方法会将 'function,undefined' 转换为null

const myObj = [undefined, null, function () {}, {}, '', true, false, 0, Symbol];

const IsDeepClone = JSON.parse(JSON.stringify(myObj));

console.log(IsDeepClone); //[null, null, null, {…}, "", true, false, 0, null]

尝试使用 deepClone 函数。以上有几个

2赞 U.A 12/6/2020 #20

我的解决方案是深度克隆对象、数组和函数。

let superClone = (object) => {
  let cloning = {};

  Object.keys(object).map(prop => {
     if(Array.isArray(object[prop])) {
        cloning[prop] = [].concat(object[prop])
    } else if(typeof  object[prop] === 'object') {
      cloning[prop] = superClone(object[prop])
    } else cloning[prop] = object[prop]
  })

  return cloning
}

let obj = {
  a: 'a',
  b: 'b',
  c: {
    deep: 'try and copy me',
    d: {
      deeper: 'try me again',
      callDeeper() {
       return this.deeper
     }
    },
    arr: [1, 2, 3]
  },
  hi() {
    return this.a
  }
};


const cloned = superClone(obj)
obj.a = 'A' 
obj.c.deep = 'i changed'
obj.c.arr = [45,454]
obj.c.d.deeper = 'i changed'

console.log(cloned) // unchanged object

如果您的对象包含的方法不使用 JSON 进行深度克隆,则 JSON 深度克隆不会克隆方法。

如果你看一下这个,object 只克隆名称,而不是 的 greet 方法。person2person1


const person1 = {
  name: 'John',
  greet() {
    return `HI, ${this.name}`
  }
}
 
const person2 = JSON.parse(JSON.stringify(person1))
 
console.log(person2)  // { name: 'John' }

45赞 razzkumar 11/16/2021 #21

我们可以通过使用 structuredClone() 来实现深度克隆

const original = { name: "stack overflow" };


// Clone it
const clone = structuredClone(original);

评论

2赞 Suraj Rao 11/16/2021
确定。。目前仅受 Firefox 支持
1赞 razzkumar 11/17/2021
并且还受 NodeJS 17+ 支持
0赞 Kaiido 1/19/2022
请注意,这可以通过 History API 在浏览器中进行 polyfill,但是这种结构化克隆算法不会处理所有类型的输入,例如函数或 DOM 元素确实抛出。function structuredClone(val) { const { href } = location; const prev = history.state; history.replaceState(val, "", href); const res = history.state; history.replaceState(prev, "", href); return res; }
1赞 3dGrabber 11/29/2022
截至本评论发布时,Can_I_use报告的可用性为 89.7%。
0赞 romanu 3 1/18/2022 #22

您好,我只是想发布我的答案,因为我认为它更具可读性。注意:这不包括类,因为我不使用它们,但您可以轻松地为此添加条件

/** Copies any type of object/array of objects 
 * @param obj The object to be copied
 * @param customKeys A list of keys that are to be excluded from deepCopy (optional)
*/
export function deepCopyObject(obj: any, customKeys?: Array<string|number|symbol>) {
    if (obj == undefined)
        return;
    if (typeof obj !== 'object')
        return obj;
    if (typeof obj === 'function')
        return obj;
    
    const isArray = obj.length > -1;
    if (isArray)
        return copyArray(obj);
    
    const isObjectDate = obj instanceof Date;
    if(isObjectDate)
        return new Date(obj);
    
    const isDOM = obj.nodeType && typeof obj.cloneNode == "function";
    if (isDOM)
        return obj.cloneNode(true);
    
    const isHtmlComponent = obj.$$typeof != undefined; // you can pass html/react components and maybe setup a custom function to copy them
    if (isHtmlComponent)
        return obj;

    const newObject = <typeof obj>{};
    const keys = Object.keys(obj);
    keys.forEach((key: keyof (typeof obj)) => {
        newObject[key] = copyKeysOfTypeObject(obj, key, customKeys);
    })

    const cantAccessObjectKeys = keys.lenght ==0; // ex: window.navigator
    if (cantAccessObjectKeys)
        return obj;
    
    return newObject
}

function copyArray(arr: any) {
    const newArr = new Array(0);
    arr.forEach((obj: any) => {
        newArr.push(deepCopyObject(obj));
    })
    return newArr;
}

function copyKeysOfTypeObject(obj: any, key: string | number | symbol, customKeys?: Array<string | number | symbol>) {
    if (!key)
        return;
    if (customKeys && customKeys.includes(key))
        return obj[key];
    return deepCopyObject(obj[key]);
}
3赞 Rohìt Jíndal 5/9/2022 #23

对象的深度克隆可以通过多种方式完成,但每种方法都有自己的局限性,如下所述。因此,我建议您使用结构化克隆算法。

  • JSON.parse(JSON.stringify(object))- 不会复制函数、日期、未定义等等。

const obj = {
  name: 'alpha',
  printName: function() {
    console.log(this.name);
  }
};

console.log(JSON.parse(JSON.stringify(obj))); // function not copied

  • _.cloneDeep(object)- 这是一个不错的选择,但需要 lodash。

const obj = {
  name: 'alpha',
  printName: function() {
    console.log(this.name);
  }
};

filteredArray = _.cloneDeep(obj);
console.log(filteredArray)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.2.1/lodash.min.js"></script>

  • structuredClone(object)- 浏览器原生 API(最好使用 JSON.parse() 和 JSON.stringify() 不会序列化循环对象或 Map、Set、Date、RegEx 等内容)

const a = { x: 20, date: new Date() };
a.c = a;

console.log(structuredClone(a)); // { x: 20, date: <date object>, c: <circular ref> }

console.log(JSON.parse(JSON.stringify(a))); // throwing a TypeError

0赞 Nishant Baranwal 6/27/2022 #24
let obj1 = {
    a: 100,
    b: {
        c: 200,
        d: [1, { f: 5 }, 3],
        e: () => {}
    }
}

function deepClone(obj) {
    let newObj = {};

    for (let key in obj) {
        let val = obj[key];

        if (val instanceof Array) {
            //newObj[key] = [...val]
          newObj[key] = [];
          val.forEach((value, index) => {
            newObj[key][index] = (typeof value === 'object' ? deepClone(value) : value);
          });
          
        } else if (val instanceof Date) {
            newObj[key] = new Date(val)
        } else if (typeof val === 'object') {
            newObj[key] = deepClone(val)
        } else {
            newObj[key] = val;
        }
    }
    return newObj;
}

obj2 = deepClone(obj1);

obj1.b.d[1].f = 2;

console.log(obj1);
console.log(obj2);

评论

0赞 Suraj Rao 6/27/2022
newObj[key] = [...val]这将浅层克隆一个对象数组
0赞 Nishant Baranwal 4/18/2023
@SurajRao感谢您指出这一点。也做了几行更改来处理嵌套数组元素。
0赞 Suraj Rao 4/18/2023
真。它适用于嵌套数组。不过,它可能会绊倒其他对象,例如日期......
2赞 Sheldon Oliveira 10/20/2022 #25

structuredClone现在大多数浏览器都支持

它的主要局限性是关于 DONT 应对功能。手动复制/移动它需要一些额外的工作。

我们至少可以通过稍后添加原型来以简单的方式复制

const proto = Object.getPrototypeOf(object)
const newObject = structuredClone(object)
Object.setPrototypeOf(newObject, proto)
0赞 KęstutisV 9/29/2023 #26

简单变体。并非涵盖所有情况。

let deepClone = function(obj) {
    let newObj = {...obj};
    for(property in newObj) {
        if(typeof newObj[property] === "object") {
            newObj[property] = deepClone(newObj[property]);
        }
    }
    return newObj;
}
0赞 Epitaph 12/18/2023 #27

对于包含数组/对象的数组和对象,以基元为字段,可以使用此处多次发布的值。JSON.parse(JSON.stringify(structure))

但是对于作为 React 组件的现代字段值,它将不起作用,因为转换只会破坏包含许多特殊字符的 React 对象的敏感内容。JSON.parse

在这种情况下,很好用(不是 _.cloneDeep,因为它破坏了摇树)lodash/cloneDeep