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

What is the most efficient way to deep clone an object in JavaScript?

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

我还看到了具有各种缺陷的递归复制函数。< BR>我很惊讶没有正则解存在。


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对象,您需要执行以下操作:

1
2
3
4
5
// Shallow copy
var newObject = jQuery.extend({}, oldObject);

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

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

我还想指出,深度复制实际上比上面显示的要聪明得多——它能够避免许多陷阱(例如,尝试深度扩展一个DOM元素)。它在jquery核心和插件中经常使用,效果非常好。


检查此基准:http://jsben.ch//bfk9

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

1
JSON.parse(JSON.stringify(obj))

要成为深度克隆对象的最快方法(它比jquery.extend快,深度标志设置为10-20%)。

当deep标志设置为false(shallow clone)时,jquery.extend速度相当快。这是一个很好的选择,因为它包含一些用于类型验证的额外逻辑,并且不会复制未定义的属性等,但是这也会使您的速度慢一点。

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

最后,如果您试图在热循环中克隆一个已知的对象结构,您可以通过简单地将克隆过程对齐并手动构造该对象来获得更多的性能。

javascript跟踪引擎在优化for..in循环和检查hasownproperty方面表现不佳,这也会减慢您的速度。必须在速度为绝对值时手动克隆。

1
2
3
4
var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

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

另外,请注意,至少在Chrome65中,本地克隆不是可行的方法。根据这个jspef,通过创建一个新的函数来执行本地克隆比使用json.stringify慢将近800倍,它在整个过程中都非常快。


假设对象中只有变量而没有任何函数,则可以使用:

1
var newObject = JSON.parse(JSON.stringify(oldObject));


结构化克隆

HTML标准包括一个内部结构化的克隆/序列化算法,可以创建对象的深度克隆。它仍然局限于某些内置类型,但是除了JSON支持的少数类型之外,它还支持日期、regexp、映射、集合、blob、文件列表、imagedatas、稀疏数组、类型数组,以及将来可能更多的类型。它还保留克隆数据中的引用,允许它支持可能导致JSON错误的循环和递归结构。

node.js中的支持:实验??

node.js中的v8模块目前(从node 11起)直接公开结构化序列化API,但该功能仍标记为"实验性",在未来版本中可能会更改或删除。如果您使用的是兼容的版本,那么克隆对象就非常简单:

1
2
3
4
5
const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

浏览器中的直接支持:也许最终????

浏览器目前不提供结构化克隆算法的直接接口,但在Github上的whatwg/html_793中讨论了全局structuredClone()功能。正如目前所提议的,将其用于大多数目的将简单如下:

1
const clone = structuredClone(original);

除非已发布,否则浏览器的结构化克隆实现只能间接公开。

异步解决方法:可用。???

使用现有API创建结构化克隆的开销较低的方法是通过消息通道的一个端口发布数据。另一个端口将发出一个message事件,附带一个附加.data的结构化克隆。不幸的是,监听这些事件必然是异步的,而同步替代方案则不太实用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

实例使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

同步解决方法:糟糕!???

同步创建结构化克隆没有好的选择。这里有一些不切实际的黑客。

history.pushState()history.replaceState()都创建了第一个参数的结构化克隆,并将该值赋给history.state。您可以使用它来创建这样的任何对象的结构化克隆:

1
2
3
4
5
6
7
const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

实例使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
 
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
 
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

虽然是同步的,但速度可能非常慢。它会产生与操作浏览器历史记录相关的所有开销。重复调用此方法会导致chrome暂时失去响应。

Notification构造函数创建其关联数据的结构化克隆。它还尝试向用户显示浏览器通知,但除非您请求了通知权限,否则将自动失败。如果您有其他用途的权限,我们将立即关闭我们创建的通知。

1
2
3
4
5
const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

实例使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
 
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
 
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}


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

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

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

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.

多读…

支持旧浏览器的polyfill:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
    }
  });
}


代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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;
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);


这是我正在使用的:

1
2
3
4
5
6
7
8
9
10
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;
}


按性能分列的深度复制:从好到坏排列

  • 重新分配"="(字符串数组、数字数组-仅限)
  • 切片(字符串数组、数字数组-仅限)
  • 串联(字符串数组、数字数组-仅限)
  • 自定义函数:用于循环或递归复制
  • jquery的$.extend
  • json.parse(字符串数组、数字数组、对象数组-仅限)
  • underline.js的克隆(仅字符串数组、数字数组)
  • Lo Dash的Clonedeep

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

当数组包含数字和字符串时,.slice()、.concat()、.splice()等函数、赋值运算符"="和下划线.js的clone函数将对数组元素进行深度复制。

如果重新分配具有最快的性能:

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

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

1
2
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

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

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

编写自定义函数(其性能比$.extend()或json.parse更快):

1
2
3
4
5
6
7
8
9
10
11
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);

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

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

其中jquery的$.extend具有更好的性能:

  • http://jspef.com/js-deep-copy/2
  • http://jspef.com/jquery-extend-vs-json-parse/2


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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});

我知道这是一个旧的帖子,但我认为这可能对下一个绊倒的人有所帮助。

只要您不为任何对象分配对象,它就不会在内存中维护引用。因此,要创建一个要与其他对象共享的对象,必须创建这样的工厂:

1
2
3
4
5
6
7
8
9
var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);


有一个库(称为"克隆"),可以很好地做到这一点。它提供了我所知道的任意对象的最完整的递归克隆/复制。它还支持循环引用,其他答案还没有涉及到循环引用。

你也可以在NPM上找到它。它可以用于浏览器和node.js。

下面是一个如何使用它的示例:

安装它

1
npm install clone

或者用安德包装。

1
ender build clone [...]

您还可以手动下载源代码。

然后您可以在源代码中使用它。

1
2
3
4
5
6
7
8
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' } }

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


Cloning一个对象在js中一直是一个关注点,但在es6之前,我在下面列出了用javascript复制一个对象的不同方法,假设您有下面的对象,并希望有一个深入的副本:

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

在不更改原点的情况下,复制此对象的方法很少:

1)es5+,使用简单的函数为您进行复制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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。

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

3)AngularJs:

1
var  deepCopyObj = angular.copy(obj);

4)jQuery:

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

5)下划线J&loadash:

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

希望这些帮助…


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

1
var newObject = _.clone(oldObject);


这里有一个版本的conroyp在上面的答案,即使构造函数有所需的参数也可以工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//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,现在它也支持循环引用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
 * 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 http://stackoverflow.com/a/11621004/560114
 */

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

在javascript中深度复制对象(我认为最好和最简单)

1。使用json.parse(json.stringify(object));

1
2
3
4
5
6
7
8
9
10
var obj = {
  a: 1,
  b: {
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }

2.使用创建方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = {
  a: 1,
  b: {
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }

三。使用lo dash的clonedeep link lo dash

1
2
3
4
5
6
7
8
9
10
11
var obj = {
  a: 1,
  b: {
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }

4。使用object.assign()方法

1
2
3
4
5
6
7
8
9
var obj = {
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }

但错当

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
  a: 1,
  b: {
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.使用underline.js.clone link underline.js

1
2
3
4
5
6
7
8
9
var obj = {
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }

但错当

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
  a: 1,
  b: {
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

参考媒体.com

jsben.ch绩效基准操场1~3 http://jsben.ch/kvqldPerformance Deep copying objects in JavaScript


The following creates two instances of the same object. I found it and am using it currently. It's simple and easy to use.

1
var objToCreate = JSON.parse(JSON.stringify(cloneThis));


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

1
2
3
4
5
6
7
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var newObject = object(oldObject);

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

编辑:

这是一种用于Object.create的多填充物,因此您也可以使用它。

1
var newObject = Object.create(oldObject);

注意:如果您使用其中的一部分,那么使用hasOwnProperty的某些迭代可能会出现问题。因为,create创建了继承oldObject的新空对象。但它对于克隆物体仍然是有用和实用的。

例如,如果oldObject.a = 5;

1
newObject.a; // is 5

但是:

1
2
oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false


Lodash有一个很好的clonedeep(value)方法:

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

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


1
2
3
4
5
6
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }


浅复制一行(ECMAScript第5版):

1
2
3
4
5
6
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年):

1
2
3
4
5
6
var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true


只是因为我没看到安古拉吉斯提到并认为人们可能想知道…

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


对于类似数组的对象,似乎还没有理想的深度克隆操作符。如下面的代码所示,JohnResig的jQueryCloner将具有非数字属性的数组转换为非数组的对象,Regdwight的json Cloner删除了非数字属性。以下测试说明了多个浏览器上的这些点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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) +
     "
Is the jQueryClone an array instance?"
+ (jQueryCopy instanceof Array) +
     "
What are the arrayLikeObj names?"
+ arrayLikeObj.names +
     "
And what are the JSONClone names?"
+ JSONCopy.names)


我有两个很好的答案,这取决于您的目标是否是克隆一个"普通的老javascript对象"。

我们还假设您的意图是创建一个完整的克隆,而不将原型引用返回到源对象。如果您对完整的克隆不感兴趣,那么可以使用其他一些答案(crockford模式)中提供的许多object.clone()例程。

对于普通的老javascript对象,在现代运行时克隆对象的一种行之有效的好方法非常简单:

1
var clone = JSON.parse(JSON.stringify(obj));

请注意,源对象必须是纯JSON对象。也就是说,它的所有嵌套属性都必须是标量(如布尔、字符串、数组、对象等)。任何函数或特殊对象(如regexp或date)都不会被克隆。

它有效率吗?是的。我们已经尝试了各种克隆方法,这是最有效的。我相信一些忍者能想出一个更快的方法。但我怀疑我们谈论的是边际收益。

这种方法简单易行。把它包装成一个方便的函数,如果你真的需要挤出一些收益,以后再继续。

现在,对于非纯JavaScript对象,没有真正简单的答案。事实上,由于JavaScript函数的动态特性和内部对象状态,不可能存在。深入克隆包含函数的JSON结构需要重新创建这些函数及其内部上下文。而JavaScript没有一种标准化的方法来实现这一点。

要做到这一点,正确的方法是通过一个方便的方法,在代码中声明并重用该方法。这个方便的方法可以赋予您对自己对象的一些理解,这样您就可以确保在新对象中正确地重新创建图形。

我们是自己写的,但我看到的最好的一般方法是:

Clone Anything with JavaScript

这是正确的想法。作者(DavidWalsh)评论了广义函数的克隆。这是您可以选择做的事情,具体取决于您的用例。

主要的想法是,您需要在每种类型的基础上专门处理函数(或者说原型类)的实例化。这里,他为regexp和date提供了一些示例。

这段代码不仅简短,而且可读性很强。很容易扩展。

这有效率吗?是的。假设目标是生成一个真正的深度复制克隆,那么您必须遍历源对象图的成员。使用这种方法,您可以精确地调整要处理的子成员以及如何手动处理自定义类型。

那就这样吧。两种方法。在我看来,两者都是有效的。


这通常不是最有效的解决方案,但它可以满足我的需要。下面的简单测试用例…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair;

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t ==="object" || t ==="function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned;
        } else {
            if (t ==="object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

循环数组测试…

1
2
3
4
5
6
7
a = []
a.push("b","c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

功能测试…

1
2
3
4
5
f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

我不同意这里投票最多的回答。递归深度克隆比前面提到的json.parse(json.stringify(obj))方法快得多。

  • jspef在这里排名第一:https://jspef.com/deep-copy-vs-json-stringify-json-parse/5
  • 上述答案中的jsben更新后显示,递归深度克隆优于所有其他提到的克隆:http://jsben.ch/13ykq

以下是快速参考的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}


角晶状体

好吧,如果你用角形,你也可以这样做。

1
var newObject = angular.copy(oldObject);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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;
};

对于希望使用JSON.parse(JSON.stringify(obj))版本但不丢失日期对象的用户,可以使用parse方法的第二个参数将字符串转换回日期:

1
2
3
4
5
6
7
8
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;
  });
}

这里有一个全面的clone()方法,可以克隆任何javascript对象。它可以处理几乎所有的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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;
};


只有当你可以使用ecmascript 6或蒸腾器时。

特征:

  • 复制时不会触发getter/setter。
  • 保留getter/setter。
  • 保留原型信息。
  • 同时使用对象文本和函数OO编写样式。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}

我使用NPM克隆库。显然,它也在浏览器中工作。

https://www.npmjs.com/package/clone

1
let a = clone(b)

使用今天的javascript克隆对象:ecmascript 2015(以前称为ecmascript 6)

1
2
3
4
5
6
7
var original = {a: 1};

// Method 1: New object with original assigned.
var copy1 = Object.assign({}, original);

// Method 2: New object with spread operator assignment.
var copy2 = {...original};

旧浏览器可能不支持EcmaScript 2015。一个常见的解决方案是使用Javascript-to-Javascript编译器(如Babel)输出Javascript代码的EcmaScript 5版本。

正如@jim hall所指出的,这只是一个肤浅的副本。属性的属性被复制为引用:更改一个属性会更改另一个对象/实例中的值。


我通常使用var newObj = JSON.parse( JSON.stringify(oldObje) );,但这里有一个更合适的方法:

1
2
3
4
5
var o = {};

var oo = Object.create(o);

(o === oo); // => false

观看传统浏览器!


单行ECMAScript 6解决方案(特殊对象类型,如日期/regex未处理):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const clone = (o) =>
  typeof o === 'object' && o !== null ?      // only clone objects
  (Array.isArray(o) ?                        // if cloning an array
    o.map(e => clone(e)) :                   // clone each of its elements
    Object.keys(o).reduce(                   // otherwise reduce every key in the object
      (r, k) => (r[k] = clone(o[k]), r), {}  // and save its cloned value into a new object
    )
  ) :
  o;                                         // return non-objects as is

var x = {
  nested: {
    name: 'test'
  }
};

var y = clone(x);

console.log(x.nested !== y.nested);


我回答这个问题迟到了,但我有另一种克隆物体的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   function cloneObject(obj) {
        if (obj === null || typeof(obj) !== 'object')
            return obj;
        var temp = obj.constructor(); // changed
        for (var key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                obj['isActiveClone'] = null;
                temp[key] = cloneObject(obj[key]);
                delete obj['isActiveClone'];
            }
        }
        return temp;
    }



var b = cloneObject({"a":1,"b":2});   // calling

比这更好更快:

1
2
var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));

1
2
3
4
var a = {"a":1,"b":2};

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

我已经对代码进行了基准标记,您可以在这里测试结果:

分享结果:enter image description here参考:https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/object/hasownpropy


罗达什有一个你喜欢的功能。

1
2
3
4
var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};

var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}}

在这里阅读文档。


这是我创建的最快的方法,它不使用原型,因此它将在新对象中维护hasownProperty。

解决方案是迭代原始对象的顶级属性,复制两份,从原始对象中删除每个属性,然后重置原始对象并返回新副本。它只需要迭代顶级属性的次数。这将保存所有if条件,以检查每个属性是否是函数、对象、字符串等,并且不必迭代每个子属性。

唯一的缺点是必须为原始对象提供其原始创建的命名空间,以便重置它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
copyDeleteAndReset:function(namespace,strObjName){
    var obj = namespace[strObjName],
    objNew = {},objOrig = {};
    for(i in obj){
        if(obj.hasOwnProperty(i)){
            objNew[i] = objOrig[i] = obj[i];
            delete obj[i];
        }
    }
    namespace[strObjName] = objOrig;
    return objNew;
}

var namespace = {};
namespace.objOrig = {
    '0':{
        innerObj:{a:0,b:1,c:2}
    }
}

var objNew = copyDeleteAndReset(namespace,'objOrig');
objNew['0'] = 'NEW VALUE';

console.log(objNew['0']) === 'NEW VALUE';
console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};

ES 2017示例:

1
2
3
4
let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy


为了将来参考,ECMAScript 6的当前草稿引入了object.assign作为克隆对象的一种方法。示例代码为:

1
2
3
var obj1 = { a: true, b: 1 };
var obj2 = Object.assign(obj1);
console.log(obj2); // { a: true, b: 1 }

在编写的时候,浏览器中对firefox 34的支持是有限的,所以目前还不能在生产代码中使用(当然,除非您正在编写firefox扩展)。


下面是我使用ES2015默认值和spread运算符深度克隆对象的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
 const makeDeepCopy = (obj, copy = {}) => {
  for (let item in obj) {
    if (typeof obj[item] === 'object') {
      makeDeepCopy(obj[item], copy)
    }
    if (obj.hasOwnProperty(item)) {
      copy = {
        ...obj
      }
    }
  }
  return copy
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const testObj = {
 "type":"object",
 "properties": {
   "userId": {
     "type":"string",
     "chance":"guid"
    },
   "emailAddr": {
     "type":"string",
     "chance": {
       "email": {
         "domain":"fake.com"
        }
      },
     "pattern":"[email protected]"
    }
  },
 "required": [
   "userId",
   "emailAddr"
  ]
}

const makeDeepCopy = (obj, copy = {}) => {
  for (let item in obj) {
    if (typeof obj[item] === 'object') {
      makeDeepCopy(obj[item], copy)
    }
    if (obj.hasOwnProperty(item)) {
      copy = {
        ...obj
      }
    }
  }
  return copy
}

console.log(makeDeepCopy(testObj))


In JavaScript, you can write your deepCopy method like

1
2
3
4
5
6
7
8
9
10
11
12
function deepCopy(src) {
  let target = Array.isArray(src) ? [] : {};
  for (let prop in src) {
    let value = src[prop];
    if(value && typeof value === 'object') {
      target[prop] = deepCopy(value);
  } else {
      target[prop] = value;
  }
 }
    return target;
}

如果你想推广你的对象克隆算法,我认为这是最好的解决方案。它可以与jquery一起使用,也可以不与jquery一起使用,不过如果您希望克隆的对象具有与原始对象相同的"类",我建议将jquery的extend方法保留在外面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function clone(obj){
    if(typeof(obj) == 'function')//it's a simple function
        return obj;
    //of it's not an object (but could be an array...even if in javascript arrays are objects)
    if(typeof(obj) !=  'object' || obj.constructor.toString().indexOf('Array')!=-1)
        if(JSON != undefined)//if we have the JSON obj
            try{
                return JSON.parse(JSON.stringify(obj));
            }catch(err){
                return JSON.parse('"'+JSON.stringify(obj)+'"');
            }
        else
            try{
                return eval(uneval(obj));
            }catch(err){
                return eval('"'+uneval(obj)+'"');
            }
    // I used to rely on jQuery for this, but the"extend" function returns
    //an object similar to the one cloned,
    //but that was not an instance (instanceof) of the cloned class
    /*
    if(jQuery != undefined)//if we use the jQuery plugin
        return jQuery.extend(true,{},obj);
    else//we recursivley clone the object
    */

    return (function _clone(obj){
        if(obj == null || typeof(obj) != 'object')
            return obj;
        function temp () {};
        temp.prototype = obj;
        var F = new temp;
        for(var key in obj)
            F[key] = clone(obj[key]);
        return F;
    })(obj);            
}

对于浅拷贝,ECMAScript2018标准中引入了一种非常简单的方法。它涉及到使用排列运算符:

1
2
3
let obj = {a :"foo", b:"bar" , c:10 , d:true , e:[1,2,3] };

let objClone = { ...obj };

我已经在Chrome浏览器中测试过了它,两个对象都存储在不同的位置,因此更改其中任何一个中的直接子值都不会更改另一个。尽管(在示例中)更改e中的值将同时影响两个副本。

这项技术非常简单和直接。对于这个问题,我认为这是一次真正的最佳实践。


因为递归对于JavaScript来说太昂贵了,我发现大多数答案都是使用递归,而JSON方法将跳过非JSON可转换部分(函数等)。所以我做了一点研究,发现了蹦床技术来避免它。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
 * Trampoline to avoid recursion in JavaScript, see:
 *     http://www.integralist.co.uk/posts/js-recursion.html
 */

function trampoline() {
    var func = arguments[0];
    var args = [];
    for (var i = 1; i < arguments.length; i++) {
        args[i - 1] = arguments[i];
    }

    var currentBatch = func.apply(this, args);
    var nextBatch = [];

    while (currentBatch && currentBatch.length > 0) {
        currentBatch.forEach(function(eachFunc) {
            var ret = eachFunc();
            if (ret && ret.length > 0) {
                nextBatch = nextBatch.concat(ret);
            }
        });

        currentBatch = nextBatch;
        nextBatch = [];
    }
};

/*
 *  Deep clone an object using the trampoline technique.
 *
 *  @param target {Object} Object to clone
 *  @return {Object} Cloned object.
 */

function clone(target) {
    if (typeof target !== 'object') {
        return target;
    }
    if (target == null || Object.keys(target).length == 0) {
        return target;
    }

    function _clone(b, a) {
        var nextBatch = [];
        for (var key in b) {
            if (typeof b[key] === 'object' && b[key] !== null) {
                if (b[key] instanceof Array) {
                    a[key] = [];
                }
                else {
                    a[key] = {};
                }
                nextBatch.push(_clone.bind(null, b[key], a[key]));
            }
            else {
                a[key] = b[key];
            }
        }
        return nextBatch;
    };

    var ret = target instanceof Array ? [] : {};
    (trampoline.bind(null, _clone))(target, ret);
    return ret;
};

另请参见以下要点:https://gist.github.com/seanoceanhu/7594caffab682f790eb


那么Promise所做的异步对象克隆呢?

1
2
3
4
5
6
7
8
async function clone(thingy /**/)
{
    if(thingy instanceof Promise)
    {
        throw Error("This function cannot clone Promises.");
    }
    return thingy;
}

有很多方法可以实现这一点,但是如果您想在没有任何库的情况下实现这一点,可以使用以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const cloneObject = (oldObject) => {
  let newObject = oldObject;
  if (oldObject && typeof oldObject === 'object') {
    if(Array.isArray(oldObject)) {
      newObject = [];
    } else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {
      newObject = new Date(oldObject.getTime());
    } else {
      newObject = {};
      for (let i in oldObject) {
        newObject[i] = cloneObject(oldObject[i]);
      }
    }

  }
  return newObject;
}

告诉我你的想法。


有很多答案,但没有一个能达到我想要的效果。我想利用jquery的深度复制功能…但是,当它运行到一个数组中时,只需复制对该数组的引用,并深度复制其中的项。为了解决这个问题,我做了一个很好的递归函数,它将自动创建一个新的数组。

(如果你愿意,它甚至会检查kendo.data.observableray!但是,如果希望数组再次可见,请确保您调用kendo.observable(newitem)。

因此,要完全复制现有项目,只需执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var newItem = jQuery.extend(true, {}, oldItem);
createNewArrays(newItem);


function createNewArrays(obj) {
    for (var prop in obj) {
        if ((kendo != null && obj[prop] instanceof kendo.data.ObservableArray) || obj[prop] instanceof Array) {
            var copy = [];
            $.each(obj[prop], function (i, item) {
                var newChild = $.extend(true, {}, item);
                createNewArrays(newChild);
                copy.push(newChild);
            });
            obj[prop] = copy;
        }
    }
}

这是我的对象克隆器版本。这是jquery方法的独立版本,只有很少的调整和调整。看看小提琴。我已经使用了很多jquery,直到有一天我意识到大部分时间我只使用这个函数x_x。

用法与jquery API中描述的相同:

  • 非深克隆:extend(object_dest, object_source);
  • 深克隆:extend(true, object_dest, object_source);

另外一个函数用于定义对象是否适合克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
 * This is a quasi clone of jQuery's extend() function.
 * by Romain WEEGER for wJs library - www.wexample.com
 * @returns {*|{}}
 */

function extend() {
    // Make a copy of arguments to avoid JavaScript inspector hints.
    var to_add, name, copy_is_array, clone,

    // The target object who receive parameters
    // form other objects.
    target = arguments[0] || {},

    // Index of first argument to mix to target.
    i = 1,

    // Mix target with all function arguments.
    length = arguments.length,

    // Define if we merge object recursively.
    deep = false;

    // Handle a deep copy situation.
    if (typeof target === 'boolean') {
        deep = target;

        // Skip the boolean and the target.
        target = arguments[ i ] || {};

        // Use next object as first added.
        i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== 'object' && typeof target !== 'function') {
        target = {};
    }

    // Loop trough arguments.
    for (false; i < length; i += 1) {

        // Only deal with non-null/undefined values
        if ((to_add = arguments[ i ]) !== null) {

            // Extend the base object.
            for (name in to_add) {

                // We do not wrap for loop into hasOwnProperty,
                // to access to all values of object.
                // Prevent never-ending loop.
                if (target === to_add[name]) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays.
                if (deep && to_add[name] && (is_plain_object(to_add[name]) || (copy_is_array = Array.isArray(to_add[name])))) {
                    if (copy_is_array) {
                        copy_is_array = false;
                        clone = target[name] && Array.isArray(target[name]) ? target[name] : [];
                    }
                    else {
                        clone = target[name] && is_plain_object(target[name]) ? target[name] : {};
                    }

                    // Never move original objects, clone them.
                    target[name] = extend(deep, clone, to_add[name]);
                }

                // Don't bring in undefined values.
                else if (to_add[name] !== undefined) {
                    target[name] = to_add[name];
                }
            }
        }
    }
    return target;
}

/**
 * Check to see if an object is a plain object
 * (created using"{}" or"new Object").
 * Forked from jQuery.
 * @param obj
 * @returns {boolean}
 */

function is_plain_object(obj) {
    // Not plain objects:
    // - Any object or value whose internal [[Class]] property is not"[object Object]"
    // - DOM nodes
    // - window
    if (obj === null || typeof obj !=="object" || obj.nodeType || (obj !== null && obj === obj.window)) {
        return false;
    }
    // Support: Firefox <20
    // The try/catch suppresses exceptions thrown when attempting to access
    // the"constructor" property of certain host objects, i.e. |window.location|
    // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
    try {
        if (obj.constructor && !this.hasOwnProperty.call(obj.constructor.prototype,"isPrototypeOf")) {
            return false;
        }
    }
    catch (e) {
        return false;
    }

    // If the function hasn't returned already, we're confident that
    // |obj| is a plain object, created by {} or constructed with new Object
    return true;
}


为了将来参考,可以使用此代码

ES6:

1
2
3
4
5
6
7
8
9
10
11
_clone: function(obj){
    let newObj = {};
    for(let i in obj){
        if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
            newObj[i] = clone(obj[i]);
        } else{
            newObj[i] = obj[i];
        }
    }
    return Object.assign({},newObj);
}

ES5:

1
2
3
4
5
6
7
8
9
10
function clone(obj){
let newObj = {};
for(let i in obj){
    if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
        newObj[i] = clone(obj[i]);
    } else{
        newObj[i] = obj[i];
    }
}
return Object.assign({},newObj);

}

例如

1
2
3
4
5
6
7
8
var obj ={a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}

xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}


这是一个递归的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
obj = {
  a: { b: { c: { d: ['1', '2'] } } },
  e: 'Saeid'
}
const Clone = function (obj) {
 
  const container = Array.isArray(obj) ? [] : {}
  const keys  = Object.keys(obj)
   
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if(typeof obj[key] == 'object') {
      container[key] = Clone(obj[key])
    }
    else
      container[key] = obj[key].slice()
  }
 
  return container
}
 console.log(Clone(obj))


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Handler {
  static deepCopy (obj) {
    if (Object.prototype.toString.call(obj) === '[object Array]') {
      const result = [];
     
      for (let i = 0, len = obj.length; i < len; i++) {
        result[i] = Handler.deepCopy(obj[i]);
      }
      return result;
    } else if (Object.prototype.toString.call(obj) === '[object Object]') {
      const result = {};
      for (let prop in obj) {
        result[prop] = Handler.deepCopy(obj[prop]);
      }
      return result;
    }
    return obj;
  }
}


根据我的经验,递归版本的性能远远优于JSON.parse(JSON.stringify(obj))。这里有一个现代化的递归深度对象复制函数,它可以放在一行上:

1
2
3
4
5
function deepCopy(obj) {
  return Object.keys(obj).reduce((v, d) => Object.assign(v, {
    [d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]
  }), {});
}

这比JSON.parse...方法快40倍。


需要新的ISH浏览器,但…

让我们扩展原生对象,得到一个真实的.extend()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Object.defineProperty(Object.prototype, 'extend', {
    enumerable: false,
    value: function(){
        var that = this;

        Array.prototype.slice.call(arguments).map(function(source){
            var props = Object.getOwnPropertyNames(source),
                i = 0, l = props.length,
                prop;

            for(; i < l; ++i){
                prop = props[i];

                if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
                    that[prop] = that[prop].extend(source[prop]);
                }else{
                    Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
                }
            }
        });

        return this;
    }
});

只需在对对象使用.extend()的任何代码之前将其弹出即可。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var obj1 = {
    node1: '1',
    node2: '2',
    node3: 3
};

var obj2 = {
    node1: '4',
    node2: 5,
    node3: '6'
};

var obj3 = ({}).extend(obj1, obj2);

console.log(obj3);
// Object {node1:"4", node2: 5, node3:"6
<div class="suo-content">[collapse title=""]<ul><li>改变原型通常被认为是不好的做法,唯一的例外是垫片。</li></ul>[/collapse]</div><p><center>[wp_ad_camp_5]</center></p><hr><P>在不接触原型继承的情况下,可以按如下方式加深孤立对象和数组;</P><P>[cc lang="javascript"]function objectClone(o){
  var ot = Array.isArray(o);
  return o !== null && typeof o ==="object" ? Object.keys(o)
                                                     .reduce((r,k) => o[k] !== null && typeof o[k] ==="object" ? (r[k] = objectClone(o[k]),r)
                                                                                                                : (r[k] = o[k],r), ot ? [] : {})
                                             : o;
}
var obj = {a: 1, b: {c: 2, d: {e: 3, f: {g: 4, h: null}}}},
    arr = [1,2,[3,4,[5,6,[7]]]],
    nil = null,
  clobj = objectClone(obj),
  clarr = objectClone(arr),
  clnil = objectClone(nil);
console.log(clobj, obj === clobj);
console.log(clarr, arr === clarr);
console.log(clnil, nil === clnil);
clarr[2][2][2] ="seven";
console.log(arr, clarr);


使用Object.create()获得prototypeinstanceof的支持,使用for()循环获得可枚举键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function cloneObject(source) {
    var key,value;
    var clone = Object.create(source);

    for (key in source) {
        if (source.hasOwnProperty(key) === true) {
            value = source[key];

            if (value!==null && typeof value==="object") {
                clone[key] = cloneObject(value);
            } else {
                clone[key] = value;
            }
        }
    }

    return clone;
}


如果您发现自己经常做这种事情(例如创建撤销重做功能),那么可能值得研究immutable.js。

1
2
3
4
const map1 = Immutable.fromJS( { a: 1, b: 2, c: { d: 3 } } );
const map2 = map1.setIn( [ 'c', 'd' ], 50 );

console.log( `${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }` ); //"3 vs 50"

https://codepen.io/anon/pen/obpqne?编辑= 1111


由于这个问题对诸如object.assign或deep clone的自定义代码等内置功能有很大的关注和解答,所以我想与deep clone共享一些库,

1。埃斯克隆

NPM安装--savedev esclone https://www.npmjs.com/package/esclone

ES6中的示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import esclone from"esclone";

const rockysGrandFather = {
  name:"Rockys grand father",
  father:"Don't know :("
};
const rockysFather = {
  name:"Rockys Father",
  father: rockysGrandFather
};

const rocky = {
  name:"Rocky",
  father: rockysFather
};

const rockyClone = esclone(rocky);

ES5中的示例用法:

1
2
3
4
5
var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)

2。深拷贝

NPM安装深拷贝网址:https://www.npmjs.com/package/deep-copy

例子:

1
2
3
4
5
6
7
var dcopy = require('deep-copy')

// deep copy object
var copy = dcopy({a: {b: [{c: 5}]}})

// deep copy array
var copy = dcopy([1, 2, {a: {b: 5}}])

三。克隆深

$NPM安装--保存克隆深度网址:https://www.npmjs.com/package/clone-deep

例子:

1
2
3
4
5
6
7
8
9
10
11
12
var cloneDeep = require('clone-deep');

var obj = {a: 'b'};
var arr = [obj];

var copy = cloneDeep(arr);
obj.c = 'd';

console.log(copy);
//=> [{a: 'b'}]

console.log(arr);

通过这个长长的答案列表,几乎所有的解决方案都被涵盖了,除了我知道的一个。这是深入克隆对象的普通JS方法列表。

  • json.parse(json.stringify(obj));

  • 通过history.state和pushstate或replaceState

  • Web通知API,但这有向用户请求权限的缺点。

  • 通过对象执行自己的递归循环来复制每个级别。

  • 我没看到答案->使用ServiceWorkers。在页面和ServiceWorker脚本之间来回传递的消息(对象)将是任何对象的深度克隆。


  • 当您的对象被嵌套并且包含数据对象、其他结构化对象或某些属性对象等时,使用JSON.parse(JSON.stringify(object))Object.assign({}, obj)$.extend(true, {}, obj)将不起作用。在这种情况下,使用罗达什。它简单易行。

    1
    2
    var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };
    var A = _.cloneDeep(obj);

    现在a将是您新克隆的obj,没有任何参考。


    希望这有帮助。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    function deepClone(obj) {
        /*
         * Duplicates an object
         */


        var ret = null;
        if (obj !== Object(obj)) { // primitive types
            return obj;
        }
        if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
            ret = obj; // for ex: obj = new String("Spidergap")
        } else if (obj instanceof Date) { // date
            ret = new obj.constructor();
        } else
            ret = Object.create(obj.constructor.prototype);

        var prop = null;
        var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also


        var props = {};
        for (var i in allProps) {
            prop = allProps[i];
            props[prop] = false;
        }

        for (i in obj) {
            props[i] = i;
        }

        //now props contain both enums and non enums
        var propDescriptor = null;
        var newPropVal = null; // value of the property in new object
        for (i in props) {
            prop = obj[i];
            propDescriptor = Object.getOwnPropertyDescriptor(obj, i);

            if (Array.isArray(prop)) { //not backward compatible
                prop = prop.slice(); // to copy the array
            } else
            if (prop instanceof Date == true) {
                prop = new prop.constructor();
            } else
            if (prop instanceof Object == true) {
                if (prop instanceof Function == true) { // function
                    if (!Function.prototype.clone) {
                        Function.prototype.clone = function() {
                            var that = this;
                            var temp = function tmp() {
                                return that.apply(this, arguments);
                            };
                            for (var ky in this) {
                                temp[ky] = this[ky];
                            }
                            return temp;
                        }
                    }
                    prop = prop.clone();

                } else // normal object
                {
                    prop = deepClone(prop);
                }

            }

            newPropVal = {
                value: prop
            };
            if (propDescriptor) {
                /*
                 * If property descriptors are there, they must be copied
                 */

                newPropVal.enumerable = propDescriptor.enumerable;
                newPropVal.writable = propDescriptor.writable;

            }
            if (!ret.hasOwnProperty(i)) // when String or other predefined objects
                Object.defineProperty(ret, i, newPropVal); // non enumerable

        }
        return ret;
    }

    https://github.com/jinujd/javascript-deep-clone


    在某些浏览器(引用)的较新版本上支持的新方法对象.fromEntries()的建议中。我想为下一个递归方法做出贡献:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const obj = {
      key1: {key11:"key11", key12:"key12", key13: {key131: 22}},
      key2: {key21:"key21", key22:"key22
    <hr>
    <p>
    If you are using es6, you can simply do this using spread operator.
    </p>

    [cc lang="
    javascript"]let a = {id:1, name:'sample_name'}
    let b = {...a};


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    function clone(obj) {
        var copy;

        // Handle the 3 simple types, and null or undefined
        if (null == obj ||"object" != typeof obj) return obj;

        // Handle Date
        if (obj instanceof Date) {
            copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            copy = [];
            for (var i = 0, len = obj.length; i < len; i++) {
                copy[i] = clone(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
            }
            return copy;
        }

        throw new Error("Unable to copy obj! Its type isn't supported.");
    }

    使用以下方法代替JSON.parse(JSON.stringify(obj)),因为比以下方法慢

    如何正确克隆javascript对象?


    如何将对象的键与其值合并?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function deepClone(o) {
        var keys = Object.keys(o);
        var values = Object.values(o);

        var clone = {};

        keys.forEach(function(key, i) {
            clone[key] = typeof values[i] == 'object' ? Object.create(values[i]) : values[i];
        });

        return clone;
    }

    注:此方法不一定是浅复制,但只复制一个内部对象的深度,也就是说,当你得到像{a: {b: {c: null}}}这样的东西时,它只克隆直接在里面的对象,所以deepClone(a.b).c在技术上是对a.b.c的引用,而deepClone(a).b是克隆,而不是引用。总工程师。


    In 2019 I'm using :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    deepCopy(object) {
      const getCircularReplacer = () => {
        const seen = new WeakSet();
        return (key, value) => {
          if(typeof value === 'object' && value !== null) {
            if(seen.has(value)) return;
            seen.add(value);
          }
          return value;
        };
      };
      return JSON.parse(JSON.stringify(object, getCircularReplacer()));
    }

    const theCopy = deepCopy(originalObject);


    保持超级简单(亲吻):

    如果有一个object,它(理想情况下)应该有一个constructor。如果你没有一个constructor作为一个object;我觉得,你应该先创建一个constructor

    好,现在克隆对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function SampleObjectConstructor(config) {
        this.greet = config.greet;
    }
    // custom-object's prototype method
    SampleObjectConstructor.prototype.showGreeting = function(){
      alert(this.greet);
    }

    var originalObject = new SampleObjectConstructor({greet: 'Hi!'});
    var clonedObj = new SampleObjectConstructor(originalObject);

    console.log(originalObject); // SampleObjectConstructor&nbsp;{greet:"Hi!
    <hr><P>精益和清洁ES6代码;</P>[cc lang="javascript"]function cloneObj(obj, newObj = {}) {
       if (obj === null || typeof obj !== 'object') return obj;

       if (obj instanceof Date) return new Date(obj.getTime());
       if (Array.isArray(obj)) return [...obj];
       if (obj instanceof Object) {
           for (let [key, value] of Object.entries(obj)) {
               newObj[key] = (typeof value === 'object') ? cloneObj(value) : value;
           }
       }
    }


    执行此克隆的最佳和最新方法如下:

    使用"…"ES6排列运算符例子:

    1
    var clonedObjArray = [...oldObjArray];

    通过这种方式,我们将数组分散到各个值中,并使用[]运算符将其放入新的数组中。

    下面是一个较长的例子,展示了它的不同工作方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let objArray = [ {a:1} , {b:2} ];

    let refArray = objArray; // this will just point to the objArray
    let clonedArray = [...objArray]; // will clone the array

    console.log("before:" );
    console.log("obj array" , objArray );
    console.log("ref array" , refArray );
    console.log("cloned array" , clonedArray );

    objArray[0] = {c:3};

    console.log("after:" );
    console.log("obj array" , objArray ); // [ {c:3} , {b:2} ]
    console.log("ref array" , refArray ); // [ {c:3} , {b:2} ]
    console.log("cloned array" , clonedArray ); // [ {a:1} , {b:2} ]


    出于我的目的,从现有对象(克隆)创建新对象的最优雅的方法是使用javascript的"assign"对象函数。

    1
    2
    3
    foo = {bar: 10, baz: {quox: 'batman'}};

    clonedObject = Object.assign(foo);

    clonedObject现在是foo的副本。我不知道细节是如何工作的,或"深度"拷贝是什么,但我也使用它将对象的属性与其他对象结合起来。似乎也适用于克隆。