如何在 javascript 中进行深度克隆

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4459928/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 12:29:58  来源:igfitidea点击:

How to Deep clone in javascript

javascript

提问by Raynos

How do you deep clone a Javascript object?

你如何深度克隆一个 Javascript 对象?

I know there are various functions based on frameworks like JSON.parse(JSON.stringify(o))and $.extend(true, {}, o)but I don't want to use a framework like that.

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

What is the most elegant or efficient way to create a deep clone.

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

We do care about edge cases like cloning array's. Not breaking prototype chains, dealing with self reference.

我们确实关心像克隆数组这样的边缘情况。不破坏原型链,处理自引用。

We don't care about supporting copying of DOM objects and like because .cloneNodeexists for that reason.

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

As I mainly want to use deep clones in node.jsusing ES5 features of the V8 engine is acceptable.

因为我主要想在node.js使用 V8 引擎的 ES5 特性时使用深度克隆是可以接受的。

[Edit]

[编辑]

Before anyone suggests let me mention there is a distinct difference between creating a copy by prototypically inheriting from the object and cloningit. The former makes a mess of the prototype chain.

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

[Further Edit]

[进一步编辑]

After reading your answer I came to the annoying discovery that cloning entire objects is a very dangerous and difficult game. Take for example the following closure based object

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

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

Is there any way to write a clone function that clones the object, has the same state at time of cloning but cannot alter the state of owithout writing a JS parser in JS.

有没有办法编写一个克隆对象的克隆函数,在克隆时具有相同的状态,但如果不在oJS 中编写 JS 解析器就不能改变状态。

There should be no real world need for such a function anymore. This is mere academic interest.

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

采纳答案by nemisj

It really depends what you would like to clone. Is this a truly JSON object or just any object in JavaScript? If you would like to do any clone, it might get you into some trouble. Which trouble? I will explain it below, but first, a code example which clones object literals, any primitives, arrays and DOM nodes.

这真的取决于你想克隆什么。这是一个真正的 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";
            }   
        }
    ]
})

And now, let's talk about problems you might get when start cloning REAL objects. I'm talking now, about objects which you create by doing something like

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

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

Of course you can clone them, it's not a problem, every object expose constructor property, and you can use it to clone objects, but it will not always work. You also can do simple for inon this objects, but it goes to the same direction - trouble. I have also included clone functionality inside the code, but it's excluded by if( false )statement.

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

So, why cloning can be a pain? Well, first of all, every object/instance might have some state. You never can be sure that your objects doesn't have for example an private variables, and if this is the case, by cloning object, you just break the state.

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

Imagine there is no state, that's fine. Then we still have another problem. Cloning via "constructor" method will give us another obstacle. It's an arguments dependency. You never can be sure, that someone who created this object, did not did, some kind of

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

new User({
   bike : someBikeInstance
});

If this is the case, you are out of luck, someBikeInstance was probably created in some context and that context is unkown for clone method.

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

So what to do? You still can do for insolution, and treat such objects like normal object literals, but maybe it's an idea not to clone such objects at all, and just pass the reference of this object?

那么该怎么办?您仍然可以for in解决问题,并将此类对象视为普通对象文字,但也许根本不克隆此类对象是一个想法,而只是传递此对象的引用?

Another solution is - you could set a convention that all objects which must be cloned should implement this part by themselves and provide appropriate API method ( like cloneObject ). Something what cloneNodeis doing for DOM.

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

You decide.

你决定。

回答by G. Ghez

Very simple way, maybe too simple:

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

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

回答by tfmontague

The JSON.parse(JSON.stringify())combination to deep copy Javascript objects is an ineffective hack, as it was meant for JSON data. It does not support values of undefinedor function () {}, and will simply ignore them (or nullthem) when "stringifying" (marshalling) the Javascript object into JSON.

JSON.parse(JSON.stringify())深度复制 Javascript 对象的组合是一种无效的黑客攻击,因为它适用于 JSON 数据。它不支持undefinedor 的值function () {},并且在将nullJavascript 对象“串化”(编组)为 JSON 时将简单地忽略它们(或它们)。

A better solution is to use a deep copy function. The function below deep copies objects, and does not require a 3rd party library (jQuery, LoDash, etc).

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

function copy(aObject) {
  if (!aObject) {
    return aObject;
  }

  let v;
  let bObject = Array.isArray(aObject) ? [] : {};
  for (const k in aObject) {
    v = aObject[k];
    bObject[k] = (typeof v === "object") ? copy(v) : v;
  }

  return bObject;
}

回答by trincot

Here is an ES6 function that will also work for objects with cyclic references:

这是一个 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

A note about Sets and Maps

关于 Sets 和 Maps 的说明

How to deal with the keys of Sets and Maps is debatable: those keys are often primitives (in which case there is no debate), but they canalso be objects. In that case the question becomes: should those keys be cloned?

如何处理设置和地图的钥匙是值得商榷的:这些键通常是原语(在这种情况下,没有辩论),但他们可以也是对象。在这种情况下,问题就变成了:应该克隆这些密钥吗?

One could argue that this should be done, so that if those objects are mutated in the copy, the objects in the original are not affected, and vice versa.

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

On the other hand one would want that if a Set/Map hasa key, this should be true in both the original and the copy -- at least before any change is made to either of them. It would be strange if the copy would be a Set/Map that has keys that never occurred before (as they were created during the cloning process): surely that is not very useful for any code that needs to know whether a given object is a key in that Set/Map or not.

另一方面,如果 Set/Maphas是一个键,那么这在原始和副本中都应该是正确的——至少在对它们中的任何一个进行任何更改之前都是如此。如果副本是一个 Set/Map 的键值以前从未出现过(因为它们是在克隆过程中创建的),那将会很奇怪:对于需要知道给定对象是否为是否键入该 Set/Map。

As you notice, I am more of the second opinion: the keys of Sets and Maps are values(maybe references) that should remain the same.

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

Such choices will often also surface with other (maybe custom) objects. There is no general solution, as much depends on how the cloned object is expected to behave in your specific case.

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

回答by svarog

The Underscore.js contrib librarylibrary has a function called snapshotthat deep clones an object

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

snippet from the source:

源代码片段:

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

once the library is linked to your project, invoke the function simply using

一旦库链接到您的项目,只需使用调用该函数

_.snapshot(object);

回答by Lawrence Dol

As others have noted on this and similar questions, cloning an "object", in the general sense, is dubious in JavaScript.

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

However, there is a class of objects, which I call "data" objects, that is, those constructed simply from { ... }literals and/or simple property assignments or deserialized from JSON for which it is reasonable to want to clone. Just today I wanted to artificially inflate data received from a server by 5x to test what happens for a large data set, but the object (an array) and its children had to be distinct objects for things to function correctly. Cloning allowed me to do this to multiply my data set:

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

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

The other place I often end up cloning data objects is for submitting data back to the host where I want to strip state fields from the object in the data model before sending it. For example, I might want to strip all fields starting with "_" from the object as it is cloned.

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

This is the code I ended up writing to do this generically, including supporting arrays and a selector to choose which members to clone (which uses a "path" string to determine context):

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

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

The simplest reasonable deep-clone solution, assuming a non-null root object and with no member selection is:

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

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

回答by Stitch

This is the deep cloning method I use, I think it Great, hope you make suggestions

这是我用的深度克隆方法,我觉得很好,希望大家多提建议

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

回答by Kooldandy

The below function is most efficient way to deep clone javascript objects.

下面的函数是深度克隆 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

回答by CPHPython

Lo-Dash, now a superset of Underscore.js, has a couple of deep clone functions:

罗短跑,现在的超Underscore.js,有几个深克隆功能:

From an answer of the authorhimself:

来自作者本人的回答

lodash underscorebuild is provided to ensure compatibility with the latest stable version of Underscore.

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

回答by icc97

There should be no real world need for such a function anymore. This is mere academic interest.

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

As purely an exercise, this is a more functional way of doing it. It's an extension of @tfmontague's answeras I'd suggestedadding a guard block there. But seeing as I feel compelled to ES6 and functionalise all the things, here's my pimped version. It complicates the logic as you have to map over the array and reduce over the object, but it avoids any mutations.

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

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