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

时间:2020-03-06 14:36:32  来源:igfitidea点击:

克隆JavaScript对象的最有效方法是什么?我已经看到使用了obj = eval(uneval(o));,但这是非标准的,仅受Firefox支持。
我已经做了类似obj = JSON.parse(JSON.stringify(o));的事情,但是对效率提出了质疑。
我还看到了具有各种缺陷的递归复制功能。
我感到惊讶的是,不存在规范的解决方案。

解决方案

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

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

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

Note: This is a reply to another answer, not a proper response to this question. If you wish to have fast object cloning please follow Corban's advice in their answer to this question.

我想指出,jQuery中的.clone()方法仅克隆DOM元素。为了克隆JavaScript对象,我们可以执行以下操作:

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

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

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

我还想指出的是,深层副本实际上比上面显示的要聪明得多,它可以避免很多陷阱(例如,尝试深层扩展DOM元素)。它在jQuery核心和插件中经常使用,效果很好。

代码:

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

这就是我正在使用的:

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

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

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

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