javascript - 深克隆js - 深克隆和浅克隆js




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

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

我做过像obj = JSON.parse(JSON.stringify(o)); 但质疑效率。

我也看到了具有各种缺陷的递归复制功能。
我很惊讶没有规范的解决方案。


注意:这是对另一个答案的回复,而不是对此问题的正确回答。 如果您希望快速克隆对象,请在回答此问题时遵循Corban的建议 。

我想要注意jQuery中的.clone()方法只能克隆DOM元素。 为了克隆JavaScript对象,您可以:

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

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

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

我还要注意,深层副本实际上比上面显示的更加智能 - 它可以避免许多陷阱(例如,尝试深度扩展DOM元素)。 它经常在jQuery核心和插件中使用,效果很好。


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

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

var clone = Object.assign({}, obj);

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

Object.assign

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

AngularJS

好吧,如果你使用角度,你也可以做到这一点

var newObject = angular.copy(oldObject);

Cloning一个对象在JS中始终是一个问题,但是在ES6之前就已经完成了,我在下面列出了在JavaScript中复制对象的不同方法,想象你有下面的Object,并希望有一个深层副本:

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] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2)ES5 +,使用JSON.parse和JSON.stringify。

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

3)AngularJs:

var  deepCopyObj = angular.copy(obj);

4)jQuery:

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

5)UnderscoreJs和Loadash:

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

希望这些帮助......


按性能深度复制:从最佳到最差排名

  • 重新分配“=”(字符串数组,仅数字数组)
  • 切片(字符串数组,数字数组 - 仅)
  • 连接(字符串数组,数字数组 - 仅)
  • 自定义函数:for循环或递归复制
  • jQuery的$ .extend
  • JSON.parse(字符串数组,数字数组,对象数组 - 仅)
  • Underscore.js的_.clone(字符串数组,仅数字数组)
  • Lo-Dash的_.cloneDeep

深层复制一个字符串或数字数组(一个级别 - 没有引用指针):

当数组包含数字和字符串时 - 函数如.slice(),. concat(),. splice(),赋值运算符“=”和Underscore.js的克隆函数; 将制作数组元素的深层副本。

重新分配的地方表现最快:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

并且.slice()的性能优于.concat(), http://jsperf.com/duplicate-array-slice-vs-concat/3 //jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

深层复制一个对象数组(两个或多个级别 - 引用指针):

var arr1 = [{object:'a'}, {object:'b'}];

编写自定义函数(具有比$ .extend()或JSON.parse更快的性能):

function copy(o) {
   var 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;
}

copy(arr1);

使用第三方实用程序功能:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

jQuery的$ .extend具有更好的性能:


Crockford建议(我更喜欢)使用此功能:

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

var newObject = object(oldObject);

这很简洁,按预期工作,你不需要图书馆。

编辑:

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

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

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

var newObject = _.clone(oldObject);

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

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

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

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

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


查看此基准: http://jsben.ch/#/bWfk9http://jsben.ch/#/bWfk9

在我之前的测试中,速度是我发现的一个主要问题

JSON.parse(JSON.stringify(obj))

成为深度克隆对象的最快方法(它将jQuery.extend与深度标志设置为10-20%)。

当深度标志设置为false(浅层克隆)时,jQuery.extend非常快。 这是一个很好的选择,因为它包含一些额外的类型验证逻辑,不会复制未定义的属性等,但这也会让你慢下来。

如果您知道要尝试克隆的对象的结构,或者可以避免深层嵌套数组,则可以在检查hasOwnProperty时编写一个简单的for (var i in obj)循环来克隆对象,它将比jQuery快得多。

最后,如果您尝试在热循环中克隆已知对象结构,只需嵌入克隆过程并手动构建对象,即可获得更多性能。

JavaScript跟踪引擎在优化for..in循环时很for..in并且检查hasOwnProperty也会减慢你的速度。 当速度是绝对必须时手动克隆。

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

注意在Date对象上使用JSON.parse(JSON.stringify(obj))方法 - JSON.stringify(new Date())返回ISO格式的日期的字符串表示形式, JSON.parse() 转换回到Date对象。 有关详细信息,请参阅此答案

此外,请注意,至少在Chrome 65中,本机克隆是不可取的。 根据这个JSPerf ,通过创建一个新函数来执行本机克隆比使用JSON.stringify慢了近800倍,而JSON.stringify在整个过程中都非常快。


码:

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

Lodash有一个很好的lodash.com/docs#cloneDeep方法:

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

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

仅仅因为我没有看到AngularJS提到并认为人们可能想知道......

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


对于想要使用该JSON.parse(JSON.stringify(obj))版本但不丢失Date对象的人,可以使用method第二个参数parse将字符串转换回Date:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}

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

浅拷贝单行(ECMAScript第5版):

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第6版,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

这是一个可以克隆任何JavaScript对象的综合clone()方法。它几乎处理所有情况:

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

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

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




clone