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

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

我已经尝试过像obj = JSON.parse(JSON.stringify(o));这样的方式,但质疑其效率。

我也看到了递归复制函数的各种缺陷。
我很惊讶没有一个权威的解决方案存在。

566
Eval本身并不可怕,使用不当才是。如果你害怕它的副作用,那么你就没有正确地使用它。你所担心的副作用正是使用Eval的原因。顺便问一句,有人真正回答了你的问题吗? - Tegra Detra
15
复制对象是一个棘手的问题,特别是对于任意集合的自定义对象而言。这可能就是为什么没有现成的方法来完成它的原因。 - b01
12
使用 eval() 通常是不明智的,因为许多JavaScript引擎的优化器必须在处理通过 eval() 设置的变量时关闭。仅仅使用 eval() 就可能导致代码性能更差。 - user56reinstatemonica8
12
请注意,JSON方法会丢失任何在JSON中没有等价的JavaScript类型。例如:JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))将生成{a: null, b: null, c: null, g: false} - oriadam
React社区已经推出了immutability-helper - Navid
67个回答

83

在ES6之前,JavaScript中克隆对象一直是个问题。我列出了下面不同的方式来复制一个JavaScript对象。假设你有以下对象,并希望进行深拷贝:

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

有几种方法可以复制该对象,而不改变其原始状态:

  1. ES5+,使用简单的函数来完成复制:

    function deepCopyObj(obj) {
        if (null == obj || "object" != typeof obj) return obj;
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0, len = obj.length; i < len; i++) {
                copy[i] = 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.");
    }
    
  2. 使用ES5+,使用JSON.parseJSON.stringify

  3. var deepCopyObj = JSON.parse(JSON.stringify(obj));
    
  4. Angular:

    var deepCopyObj = angular.copy(obj);
    
  5. jQuery:

    var deepCopyObj = jQuery.extend(true, {}, obj);
    
  6. Underscore.js和Lodash:

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

希望这些能有所帮助...


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

67

有一个名为“clone”的可以很好地完成这项任务。它提供了我所知道的最完整的递归克隆/复制任意对象的功能。它还支持循环引用,这是其他答案没有涉及到的。

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

(免责声明:我是这个库的作者。)

57

我知道这是一篇旧帖子,但我认为这可能对下一个遇到类似问题的人有所帮助。

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

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

这不是现有对象的深度克隆,而只是创建一个新对象。 - zkldi

50
如果您正在使用它,Underscore.js库有一个clone方法。
var newObject = _.clone(oldObject);

2
这将执行浅复制,而不是 OP 寻找的深复制。 - Diederik

48
这里是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 https://dev59.com/A3RB5IYBdhLWcg3wET5J#11621004
 */ 
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();
    };
}

33
以下创建了两个相同对象的实例。我已经找到并目前正在使用它。这很简单易用。
var objToCreate = JSON.parse(JSON.stringify(cloneThis));

31

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

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

23

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

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接