繁体   English   中英

如何正确克隆 JavaScript object?

[英]How do I correctly clone a JavaScript object?

我有一个 object x 我想将其复制为 object y ,这样对y的更改不会修改x 我意识到复制从内置 JavaScript 对象派生的对象将导致额外的不需要的属性。 这不是问题,因为我正在复制我自己的文字构造对象之一。

如何正确克隆 JavaScript object?

2022 年更新

有一个称为结构化克隆的新 JS 标准。 它适用于所有浏览器:

const clone = structuredClone(object);

旧答案

对 JavaScript 中的任何对象执行此操作都不会简单或直接。 您将遇到错误地从对象原型中获取属性的问题,这些属性应该留在原型中而不是复制到新实例中。 例如,如果您要向Object.prototype添加一个clone方法,正如一些答案所描述的那样,您将需要显式跳过该属性。 但是,如果在Object.prototype或其他中间原型中添加了您不知道的其他附加方法怎么办? 在这种情况下,您将复制不应该复制的属性,因此您需要使用hasOwnProperty方法检测不可预见的非本地属性。

除了不可枚举的属性之外,当您尝试复制具有隐藏属性的对象时,您还会遇到更棘手的问题。 例如, prototype是函数的隐藏属性。 此外,对象的原型由属性__proto__引用,该属性也是隐藏的,并且不会被迭代源对象属性的 for/in 循环复制。 我认为__proto__可能特定于 Firefox 的 JavaScript 解释器,并且在其他浏览器中可能有所不同,但你明白了。 并非所有事物都是可枚举的。 如果您知道它的名称,您可以复制隐藏的属性,但我不知道有什么方法可以自动发现它。

寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。 如果您的源对象的原型是Object ,那么只需使用{}创建一个新的通用对象就可以了,但如果源的原型是Object的某个后代,那么您将丢失该原型中使用跳过的其他成员hasOwnProperty过滤器,或者在原型中,但一开始就无法枚举。 一种解决方案可能是调用源对象的constructor属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。 例如,一个Date对象将其数据存储为隐藏成员:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

d1的日期字符串将比d2晚 5 秒。 使一个Date与另一个 Date 相同的一种方法是调用setTime方法,但这是特定于Date类的。 我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!

当我不得不实现一般的深度复制时,我最终妥协了,假设我只需要复制一个普通的ObjectArrayDateStringNumberBoolean 最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。 我进一步假设ObjectArray中包含的任何元素也将是该列表中的 6 种简单类型之一。 这可以通过如下代码来完成:

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

只要对象和数组中的数据形成树形结构,上述函数就可以充分适用于我提到的 6 种简单类型。 也就是说,对象中对相同数据的引用不超过一个。 例如:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

它不能处理任何 JavaScript 对象,但它可能足以满足许多目的,只要你不认为它只适用于你扔给它的任何东西。

如果您不在对象中使用Date s、函数、未定义、regExp 或 Infinity,则一个非常简单的衬里是JSON.parse(JSON.stringify(object))

 const a = { string: 'string', number: 123, bool: false, nul: null, date: new Date(), // stringified undef: undefined, // lost inf: Infinity, // forced to 'null' } console.log(a); console.log(typeof a.date); // Date object const clone = JSON.parse(JSON.stringify(a)); console.log(clone); console.log(typeof clone.date); // result of .toISOString()

这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。

另请参阅这篇关于浏览器的结构化克隆算法的文章,该算法在向工作人员发送消息和从工作人员发送消息时使用。 它还包含深度克隆功能。

使用 jQuery,您可以使用extend进行浅拷贝

var copiedObject = jQuery.extend({}, originalObject)

copiedObject的后续更改不会影响originalObject ,反之亦然。

或者制作一个深拷贝

var copiedObject = jQuery.extend(true, {}, originalObject)

在 ECMAScript 6 中有Object.assign方法,它将所有可枚举自身属性的值从一个对象复制到另一个对象。 例如:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

但请注意,这是一个浅拷贝- 嵌套对象仍被复制为引用。

每个MDN

  • 如果你想要浅拷贝,使用Object.assign({}, a)
  • 对于“深度”复制,使用JSON.parse(JSON.stringify(a))

不需要外部库,但您需要先检查浏览器兼容性

一种在一行代码中克隆 Javascript 对象的优雅方法

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

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

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

有很多答案,但没有一个提到 ECMAScript 5 中的Object.create ,它诚然没有给你一个精确的副本,而是将源设置为新对象的原型。

因此,这不是问题的确切答案,但它是一种单线解决方案,因此很优雅。 它最适合两种情况:

  1. 这种继承有用的地方(呃!)
  2. 源对象不会被修改的地方,因此这两个对象之间的关系不成问题。

例子:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

为什么我认为这个解决方案更好? 它是原生的,因此没有循环,没有递归。 但是,较旧的浏览器将需要一个 polyfill。

互联网上的大多数解决方案都存在几个问题。 所以我决定进行跟进,其中包括为什么不应该接受已接受的答案。

起始情况

我想深度复制一个 Javascript Object及其所有子对象及其子对象等等。 但由于我不是一个普通的开发人员,我的Object具有普通propertiescircular structures甚至nested objects

所以让我们先创建一个circular structure和一个nested object

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

让我们将所有内容放在一个名为aObject中。

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

接下来,我们要将a复制到一个名为b的变量中并对其进行变异。

var b = a;

b.x = 'b';
b.nested.y = 'b';

您知道这里发生了什么,因为如果不是,您甚至不会遇到这个好问题。

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

现在让我们找到解决方案。

JSON

我尝试的第一次尝试是使用JSON

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

不要在上面浪费太多时间,你会得到TypeError: Converting circular structure to JSON

递归副本(接受的“答案”)

让我们看看接受的答案。

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

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

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

    // Handle Object
    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! Its type isn't supported.");
}

看起来不错吧? 它是对象的递归副本,也可以处理其他类型,例如Date ,但这不是必需的。

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

递归和circular structures不能很好地协同工作...... RangeError: Maximum call stack size exceeded

本机解决方案

与同事争吵后,我的老板问我们发生了什么事,他在谷歌搜索后找到了一个简单的解决方案 它被称为Object.create

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

这个解决方案是前段时间添加到 Javascript 中的,甚至可以处理circular structure

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

...你看,它不适用于内部的嵌套结构。

原生解决方案的 polyfill

IE 8 一样,在旧版浏览器中有一个Object.create的 polyfill。它类似于 Mozilla 推荐的东西,当然,它并不完美,会导致与原生解决方案相同的问题。

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

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

我把F放在了范围之外,所以我们可以看看instanceof告诉我们什么。

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

与本机解决方案相同的问题,但输出稍差一些。

更好(但不完美)的解决方案

在挖掘时,我发现了一个类似的问题( 在 Javascript 中,执行深层复制时,由于属性是“this”,我如何避免循环? ),但有一个更好的解决方案。

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

让我们看看输出......

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

需求是匹配的,但还是有一些小问题,包括将nestedcircinstance改为Object

共享叶子的树的结构不会被复制,它们将成为两个独立的叶子:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

结论

最后一个使用递归和缓存的解决方案可能不是最好的,但它是对象的真正深层副本。 它处理简单的propertiescircular structuresnested object ,但在克隆时会弄乱它们的实例。

jsfiddle

如果你对浅拷贝没问题,underscore.js 库有一个克隆方法。

y = _.clone(x);

或者你可以像这样扩展它

copiedObject = _.extend({},originalObject);

好的,假设你在下面有这个对象并且你想克隆它:

let obj = {a:1, b:2, c:3}; //ES6

或者

var obj = {a:1, b:2, c:3}; //ES5

答案主要取决于您使用的ECMAscript ,在ES6+中,您可以简单地使用Object.assign进行克隆:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

或像这样使用扩展运算符:

let cloned = {...obj}; //new {a:1, b:2, c:3};

但是如果你使用ES5 ,你可以使用一些方法,但JSON.stringify ,只要确保你不使用大量数据来复制,但在许多情况下它可能是一种方便的方式,如下所示:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

2020 年 7 月 6 日更新

有三 (3) 种方法可以在 JavaScript 中克隆对象。 由于 JavaScript 中的对象是引用值,因此您不能简单地使用 = 进行复制。

方法是:

const food = { food: 'apple', drink: 'milk' }


// 1. Using the "Spread"
// ------------------

{ ...food }


// 2. Using "Object.assign"
// ------------------

Object.assign({}, food)


// 3. "JSON"
// ------------------

JSON.parse(JSON.stringify(food))

// RESULT:
// { food: 'apple', drink: 'milk' }

这可以作为参考总结。

一种特别不优雅的解决方案是使用 JSON 编码来制作没有成员方法的对象的深层副本。 该方法是对您的目标对象进行 JSON 编码,然后通过对其进行解码,您可以获得所需的副本。 您可以根据需要进行多次解码,制作尽可能多的副本。

当然,函数不属于 JSON,所以这只适用于没有成员方法的对象。

这种方法非常适合我的用例,因为我将 JSON blob 存储在键值存储中,当它们在 JavaScript API 中作为对象公开时,每个对象实际上都包含对象原始状态的副本,所以我们可以在调用者改变暴露的对象后计算增量。

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

您可以简单地使用扩展属性来复制没有引用的对象。 但要小心(见评论),“副本”只是在最低的对象/数组级别。 嵌套属性仍然是引用!


完整克隆:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

使用二级引用克隆:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript 实际上本身并不支持深度克隆。 使用效用函数。 例如拉姆达:

http://ramdajs.com/docs/#clone

const objClone = { ...obj };

请注意,嵌套对象仍被复制为引用。

来自这篇文章:Brian Huisman如何在 Javascript 中复制数组和对象

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

对于那些使用 AngularJS 的人,也有直接的方法来克隆或扩展这个库中的对象。

var destination = angular.copy(source);

或者

angular.copy(source, destination);

更多在 angular.copy文档中......

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

A.Levy 的回答几乎是完整的,这是我的一点贡献:有一种方法如何处理递归引用,请参见这一行

if(this[attr]==this) copy[attr] = copy;

如果对象是 XML DOM 元素,我们必须使用cloneNode代替

if(this.cloneNode) return this.cloneNode(true);

受 A.Levy 详尽的研究和 Calvin 的原型制作方法的启发,我提供了以下解决方案:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

另请参阅答案中的安迪·伯克 (Andy Burke) 的注释。

表现

今天 2020.04.30 我在 MacOs High Sierra v10.13.6 上在 Chrome v81.0、Safari v13.1 和 Firefox v75.0 上执行所选解决方案的测试。

我专注于复制 DATA 的速度(具有简单类型字段的对象,而不是方法等)。 解AI只能做浅拷贝,解JU可以做深拷贝。

浅拷贝的结果

  • 解决方案{...obj} (A) 在 chrome 和 firefox 上最快,在 safari 上中等速度
  • 基于Object.assign (B) 的解决方案在所有浏览器上都很快
  • jQuery (E) 和 lodash (F,G,H) 解决方案中等/相当快
  • 解决方案JSON.parse/stringify (K) 相当慢
  • 解决方案 D 和 U 在所有浏览器上都很慢

在此处输入图像描述

深拷贝的结果

  • 解决方案 Q 在所有浏览器上最快
  • jQuery (L) 和 lodash (J) 中等速度
  • 解决方案JSON.parse/stringify (K) 相当慢
  • 解决方案 U 在所有浏览器上最慢
  • lodash (J) 和解决方案 U crash on Chrome for 1000 level deep object

在此处输入图像描述

细节

对于选择的解决方案: A B C(my) D E F G H I J K L M N O P Q R S T U ,我执行 4 次测试

  • 浅小:具有 10 个非嵌套字段的对象 - 您可以在此处运行它
  • 浅大:具有 1000 个非嵌套字段的对象 - 您可以在此处运行它
  • deep-small:具有 10 个级别嵌套字段的对象 - 您可以在此处运行它
  • deep-big:具有 1000 个级别嵌套字段的对象 - 您可以在此处运行它

测试中使用的对象显示在下面的代码片段中

 let obj_ShallowSmall = { field0: false, field1: true, field2: 1, field3: 0, field4: null, field5: [], field6: {}, field7: "text7", field8: "text8", } let obj_DeepSmall = { level0: { level1: { level2: { level3: { level4: { level5: { level6: { level7: { level8: { level9: [[[[[[[[[['abc']]]]]]]]]], }}}}}}}}}, }; let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,{}); let obj_DeepBig = genDeepObject(1000); // ------------------ // Show objects // ------------------ console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall)); console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall)); console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig)); console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig)); // ------------------ // HELPERS // ------------------ function getField(k) { let i=k%10; if(i==0) return false; if(i==1) return true; if(i==2) return k; if(i==3) return 0; if(i==4) return null; if(i==5) return []; if(i==6) return {}; if(i>=7) return "text"+k; } function genDeepObject(N) { // generate: {level0:{level1:{...levelN: {end:[[[...N-times...['abc']...]]] }}}...}}} let obj={}; let o=obj; let arr = []; let a=arr; for(let i=0; i<N; i++) { o['level'+i]={}; o=o['level'+i]; let aa=[]; a.push(aa); a=aa; } a[0]='abc'; o['end']=arr; return obj; }

下面的代码片段展示了经过测试的解决方案并显示了它们之间的差异

 function A(obj) { return {...obj} } function B(obj) { return Object.assign({}, obj); } function C(obj) { return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {}) } function D(obj) { let copyOfObject = {}; Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj)); return copyOfObject; } function E(obj) { return jQuery.extend({}, obj) // shallow } function F(obj) { return _.clone(obj); } function G(obj) { return _.clone(obj,true); } function H(obj) { return _.extend({},obj); } function I(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; } function J(obj) { return _.cloneDeep(obj,true); } function K(obj) { return JSON.parse(JSON.stringify(obj)); } function L(obj) { return jQuery.extend(true, {}, obj) // deep } function M(obj) { if(obj == null || typeof(obj) != 'object') return obj; var temp = new obj.constructor(); for(var key in obj) temp[key] = M(obj[key]); return temp; } function N(obj) { let EClone = function(obj) { var newObj = (obj instanceof Array) ? [] : {}; for (var i in obj) { if (i == 'EClone') continue; if (obj[i] && typeof obj[i] == "object") { newObj[i] = EClone(obj[i]); } else newObj[i] = obj[i] } return newObj; }; return EClone(obj); }; function O(obj) { if (obj == null || typeof obj != "object") return obj; if (obj.constructor != Object && obj.constructor != Array) return obj; if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function || obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean) return new obj.constructor(obj); let to = new obj.constructor(); for (var name in obj) { to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name]; } return to; } function P(obj) { 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; } return clone({},obj); } function Q(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] = Q(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } function R(obj) { const gdcc = "__getDeepCircularCopy__"; if (obj !== Object(obj)) { return obj; // primitive value } var set = gdcc in obj, cache = obj[gdcc], result; if (set && typeof cache == "function") { return cache(); } // else obj[gdcc] = function() { return result; }; // overwrite if (obj instanceof Array) { result = []; for (var i=0; i<obj.length; i++) { result[i] = R(obj[i]); } } else { result = {}; for (var prop in obj) if (prop != gdcc) result[prop] = R(obj[prop]); else if (set) result[prop] = R(cache); } if (set) { obj[gdcc] = cache; // reset } else { delete obj[gdcc]; // unset again } return result; } function S(obj) { const cache = new WeakMap(); // Map of old - new references function copy(object) { if (typeof object !== 'object' || object === null || object instanceof HTMLElement ) return object; // primitive value or HTMLElement if (object instanceof Date) return new Date().setTime(object.getTime()); if (object instanceof RegExp) return new RegExp(object.source, object.flags); if (cache.has(object)) return cache.get(object); const result = object instanceof Array ? [] : {}; cache.set(object, result); // store reference to object before the recursive starts if (object instanceof Array) { for(const o of object) { result.push(copy(o)); } return result; } const keys = Object.keys(object); for (const key of keys) result[key] = copy(object[key]); return result; } return copy(obj); } function T(obj){ var clonedObjectsArray = []; var originalObjectsArray = []; //used to remove the unique ids when finished var next_objid = 0; function objectId(obj) { if (obj == null) return null; if (obj.__obj_id == undefined){ obj.__obj_id = next_objid++; originalObjectsArray[obj.__obj_id] = obj; } return obj.__obj_id; } function cloneRecursive(obj) { if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0; i < obj.length; ++i) { copy[i] = cloneRecursive(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { if (clonedObjectsArray[objectId(obj)] != undefined) return clonedObjectsArray[objectId(obj)]; var copy; if (obj instanceof Function)//Handle Function copy = function(){return obj.apply(this, arguments);}; else copy = {}; clonedObjectsArray[objectId(obj)] = copy; for (var attr in obj) if (attr != "__obj_id" && obj.hasOwnProperty(attr)) copy[attr] = cloneRecursive(obj[attr]); return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } var cloneObj = cloneRecursive(obj); //remove the unique ids for (var i = 0; i < originalObjectsArray.length; i++) { delete originalObjectsArray[i].__obj_id; }; return cloneObj; } function U(obj) { /* Deep copy objects by value rather than by reference, exception: `Proxy` */ const seen = new WeakMap() return clone(obj) function defineProp(object, key, descriptor = {}, copyFrom = {}) { const { configurable: _configurable, writable: _writable } = Object.getOwnPropertyDescriptor(object, key) || { configurable: true, writable: true } const test = _configurable // Can redefine property && (_writable === undefined || _writable) // Can assign to property if (!test || arguments.length <= 2) return test const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key) || { configurable: true, writable: true } // Custom… || {}; // …or left to native default settings ["get", "set", "value", "writable", "enumerable", "configurable"] .forEach(attr => descriptor[attr] === undefined && (descriptor[attr] = basisDesc[attr]) ) const { get, set, value, writable, enumerable, configurable } = descriptor return Object.defineProperty(object, key, { enumerable, configurable, ...get || set ? { get, set } // Accessor descriptor : { value, writable } // Data descriptor }) } function clone(object) { if (object !== Object(object)) return object /* —— Check if the object belongs to a primitive data type */ if (object instanceof Node) return object.cloneNode(true) /* —— Clone DOM trees */ let _object // The clone of object switch (object.constructor) { case Array: case Object: _object = cloneObject(object) break case Date: _object = new Date(+object) break case Function: const fnStr = String(object) _object = new Function("return " + (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr) ? "function " : "" ) + fnStr )() copyPropDescs(_object, object) break case RegExp: _object = new RegExp(object) break default: switch (Object.prototype.toString.call(object.constructor)) { // // Stem from: case "[object Function]": // `class` case "[object Undefined]": // `Object.create(null)` _object = cloneObject(object) break default: // `Proxy` _object = object } } return _object } function cloneObject(object) { if (seen.has(object)) return seen.get(object) /* —— Handle recursive references (circular structures) */ const _object = Array.isArray(object) ? [] : Object.create(Object.getPrototypeOf(object)) /* —— Assign [[Prototype]] for inheritance */ seen.set(object, _object) /* —— Make `_object` the associative mirror of `object` */ Reflect.ownKeys(object).forEach(key => defineProp(_object, key, { value: clone(object[key]) }, object) ) return _object } function copyPropDescs(target, source) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source) ) } } // ------------------------ // Test properties // ------------------------ console.log(` shallow deep func circ undefined date RegExp bigInt`) log(A); log(B); log(C); log(D); log(E); log(F); log(G); log(H); log(I); log(J); log(K); log(L); log(M); log(N); log(O); log(P); log(Q); log(R); log(S); log(T); log(U); console.log(` shallow deep func circ undefined date RegExp bigInt ---- LEGEND: shallow - solution create shallow copy deep - solution create deep copy func - solution copy functions circ - solution can copy object with circular references undefined - solution copy fields with undefined value date - solution can copy date RegExp - solution can copy fields with regular expressions bigInt - solution can copy BigInt `) // ------------------------ // Helper functions // ------------------------ function deepCompare(obj1,obj2) { return JSON.stringify(obj1)===JSON.stringify(obj2); } function getCase() { // pure data case return { undef: undefined, bool: true, num: 1, str: "txt1", e1: null, e2: [], e3: {}, e4: 0, e5: false, arr: [ false, 2, "txt3", null, [], {}, [ true,4,"txt5",null, [], {}, [true,6,"txt7",null,[],{} ], {bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false} ], {bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false} ], obj: { bool: true, num: 12, str: "txt13", e1: null, e2: [], e3: {}, e4: 0, e5: false, arr: [true,14,"txt15",null,[],{} ], obj: { bool: true, num: 16, str: "txt17", e1: null, e2: [], e3: {}, e4: 0, e5: false, arr: [true,18,"txt19",null,[],{} ], obj: {bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false} } } }; } function check(org, copy, field, newValue) { copy[field] = newValue; return deepCompare(org,copy); } function testFunc(f) { let o = { a:1, fun: (i,j)=> i+j }; let c = f(o); let val = false try{ val = c.fun(3,4)==7; } catch(e) { } return val; } function testCirc(f) { function Circ() { this.me = this; } var o = { x: 'a', circ: new Circ(), obj_circ: null, }; o.obj_circ = o; let val = false; try{ let c = f(o); val = (o.obj_circ == o) && (o.circ == o.circ.me); } catch(e) { } return val; } function testRegExp(f) { let o = { re: /a[0-9]+/, }; let val = false; try{ let c = f(o); val = (String(c.re) == String(/a[0-9]+/)); } catch(e) { } return val; } function testDate(f) { let o = { date: new Date(), }; let val = false; try{ let c = f(o); val = (+new Date(c.date) == +new Date(o.date)); } catch(e) { } return val; } function testBigInt(f) { let val = false; try{ let o = { big: 123n, }; let c = f(o); val = o.big == c.big; } catch(e) { } return val; } function log(f) { let o = getCase(); // orginal object let oB = getCase(); // "backup" used for shallow valid test let c1 = f(o); // copy 1 for reference let c2 = f(o); // copy 2 for test shallow values let c3 = f(o); // copy 3 for test deep values let is_proper_copy = deepCompare(c1,o); // shoud be true // shallow changes let testShallow = [ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',{}] ] .reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true ); // should be true (original object shoud not have changed shallow fields) let is_valid = deepCompare(o,oB); // deep test (intruduce some change) if (c3.arr[6]) c3.arr[6][7].num = 777; let diff_shallow = !testShallow; // shoud be true (shallow field was copied) let diff_deep = !deepCompare(c1,c3); // shoud be true (deep field was copied) let can_copy_functions = testFunc(f); let can_copy_circular = testCirc(f); let can_copy_regexp = testRegExp(f); let can_copy_date = testDate(f); let can_copy_bigInt = testBigInt(f); let has_undefined = 'undef' in c1; // field with undefined value is copied? let is_ok = is_valid && is_proper_copy; let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string testFunc(f); if(is_ok) { console.log(`${f.name} ${b(diff_shallow)} ${b(diff_deep)} ${b(can_copy_functions)} ${b(can_copy_circular)} ${b(has_undefined)} ${b(can_copy_date)} ${b(can_copy_regexp)} ${b(can_copy_bigInt)}`) } else { console.log(`${f.name}: INVALID ${is_valid} ${is_proper_copy}`,{c1}) } }
 <script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script> This snippet only presents tested solutions and show differences between them (but it no make performence tests)

下面是 Chrome 用于浅大对象的示例结果

在此处输入图像描述

使用 Lodash:

var y = _.clone(x, true);

在 ES-6 中,您可以简单地使用 Object.assign(...)。 前任:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

一个很好的参考在这里: https ://googlechrome.github.io/samples/object-assign-es6/

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(json_original));

来源: 如何不通过引用将 JavaScript 对象复制到新变量?

您可以使用一行代码克隆一个对象并从前一个对象中删除任何引用。 只需这样做:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

对于当前不支持 Object.create 的浏览器/引擎,您可以使用这个 polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

ES6 解决方案,如果你想(浅)克隆一个类实例而不仅仅是一个属性对象。

老问题的新答案! 如果您有幸使用 ECMAScript 2016 (ES6) 和Spread Syntax ,这很容易。

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

这为对象的浅拷贝提供了一种干净的方法。 制作深层副本,即为每个递归嵌套对象中的每个值创建一个新副本,需要上述较重的解决方案。

JavaScript 不断发展。

结构化克隆

2022 年更新: structuredClone()全局函数已在 Node 17、Deno 1.14 和大多数主要浏览器中可用(请参阅我可以使用)。

可以使用 HTML 标准包含的相同结构化克隆机制在领域之间发送数据。

const clone = structuredClone(original);

有关更多详细信息,请参阅其他答案

我认为有一个简单而有效的答案。 在深度复制中,有两个问题:

  1. 保持属性相互独立。
  2. 并使方法在克隆对象上保持活动状态。

所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行分配以复制函数。

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

虽然这个问题有很多答案,但我希望这个也有帮助。

对于深拷贝和克隆,JSON.stringify 然后 JSON.parse 对象:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

(以下主要整合了@Maciej Bukowski 、@ A. Levy 、@ Jan Turoň @Redu 的回答,以及@ LeviRoberts 、@ RobG的评论,多谢他们!!!)

深拷贝 - 是的! (大多);
浅拷贝 - 不! Proxy除外)。

真诚欢迎大家测试clone()
此外, defineProp()旨在轻松快速地(重新)定义或复制任何类型的描述符。

功能

function clone(object) {
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(object)


  function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        _object = copyFn(object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                                  // Stem from:
          case "[object Function]":
            switch (object[Symbol.toStringTag]) {
              case undefined:
                _object = cloneObject(object) // `class`
                break

              case "AsyncFunction":
              case "GeneratorFunction":
              case "AsyncGeneratorFunction":
                _object = copyFn(object)
                break

              default:
                _object = object
            }
            break

          case "[object Undefined]":          // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:
            _object = object                  // `Proxy`
        }
    }

    return _object
  }


  function cloneObject(object) {
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key, { value: clone(object[key]) }, object)
    )

    return _object
  }
}


function copyPropDescs(target, source) {
  Object.defineProperties(target,
    Object.getOwnPropertyDescriptors(source)
  )
}


function convertFnToStr(fn) {
  let fnStr = String(fn)
  if (fn.name.startsWith("[")) // isSymbolKey
    fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
  fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr)
    ? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
  return fnStr
}

function copyFn(fn) {
  const newFn = new Function(`return ${convertFnToStr(fn)}`)()
  copyPropDescs(newFn, fn)
  return newFn
}



function defineProp(object, key, descriptor = {}, copyFrom = {}) {
  const { configurable: _configurable, writable: _writable }
    = Object.getOwnPropertyDescriptor(object, key)
    || { configurable: true, writable: true }

  const test = _configurable // Can redefine property
    && (_writable === undefined || _writable) // Can assign to property

  if (!test || arguments.length <= 2) return test

  const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
    || { configurable: true, writable: true } // Custom…
    || {}; // …or left to native default settings

  ["get", "set", "value", "writable", "enumerable", "configurable"]
    .forEach(attr =>
      descriptor[attr] === undefined &&
      (descriptor[attr] = basisDesc[attr])
    )

  const { get, set, value, writable, enumerable, configurable }
    = descriptor

  return Object.defineProperty(object, key, {
    enumerable, configurable, ...get || set
      ? { get, set } // Accessor descriptor
      : { value, writable } // Data descriptor
  })
}

// 测试

const obj0 = {
  u: undefined,
  nul: null,
  t: true,
  num: 9,
  str: "",
  sym: Symbol("symbol"),
  [Symbol("e")]: Math.E,
  arr: [[0], [1, 2]],
  d: new Date(),
  re: /f/g,
  get g() { return 0 },
  o: {
    n: 0,
    o: { f: function (...args) { } }
  },
  f: {
    getAccessorStr(object) {
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    },
    f0: function f0() { },
    f1: function () { },
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params) { return param => param + params },
    f5: (a, b) => ({ c = 0 } = {}) => a + b + c
  }
}

defineProp(obj0, "s", { set(v) { this._s = v } })
defineProp(obj0.arr, "tint", { value: { is: "non-enumerable" } })
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", { set(v) { this._s = v + 1 } })
defineProp(obj1.re, "multiline", { value: true })

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - More kinds of functions")

const fnsForTest = {
  f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  async function() { },
  async asyncFunc() { },
  aFn: async function () { },
  *gen() { },
  async *asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  *[Symbol.iterator]() { yield* Object.keys(this) }
}

console.log(Reflect.ownKeys(fnsForTest).map(k =>
  `${String(k)}:
  ${fnsForTest[k].name}-->
    ${String(fnsForTest[k])}`
).join("\n"))

const normedFnsStr = `{
  f: function f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  function: async function() { },
  asyncFunc: async function asyncFunc() { },
  aFn: async function () { },
  gen: function* gen() { },
  asyncGen: async function* asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  [Symbol.iterator]: function* () { yield* Object.keys(this) }
}`

const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`(${normedFnsStr})`))
console.log("Comparison of fnsForTest and its clone:",
  Reflect.ownKeys(fnsForTest).map(k =>
    [k, fnsForTest[k] === copiedFnsForTest[k]]
  )
)

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0's recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person {
  constructor(name) {
    this.name = name
  }
}

class Boy extends Person { }
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

参考

  1. Object.create() | MDN
  2. Object.defineProperties() | MDN
  3. 属性的可枚举性和所有权 | MDN
  4. TypeError: 循环对象值 | MDN

使用的语言技巧

  1. 有条件地向对象添加道具

使用 lodash _.cloneDeep()。

浅拷贝:lodash _.clone()

可以通过简单地复制引用来进行浅拷贝。

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
//{"a":4,"b":{"c":4,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
//{"a":0,"b":{"c":4,"e":{"f":100}}}

浅拷贝:lodash _.clone()

深拷贝:lodash _.cloneDeep()

字段被取消引用:而不是对被复制对象的引用

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
{"a":100,"b":{"c":100,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
{"a":0,"b":{"c":0,"e":{"f":0}}}

深拷贝:lodash _.cloneDeep()

最正确的复制对象是使用Object.create

Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

这样的符号将使相同的对象具有正确的原型和隐藏属性。

这是对 A. Levy 的代码的改编,也可以处理函数的克隆和多个/循环引用 - 这意味着如果被克隆的树中的两个属性是同一对象的引用,则克隆的对象树将具有这些属性指向被引用对象的同一个克隆。 这也解决了循环依赖的情况,如果不加以处理,就会导致无限循环。 算法的复杂度为 O(n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

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

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

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


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



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

一些快速测试

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

我只是想在这篇文章中添加到所有Object.create解决方案中,这不适用于 nodejs。

在 Firefox 中的结果

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

{test:"test"}

在nodejs中是

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

由于mindeavor声明要克隆的对象是“文字构造”对象,因此解决方案可能是简单地多次生成对象而不是克隆对象的实例:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();

我已经编写了自己的实现。 不确定它是否算作更好的解决方案:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

以下是实现:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}

使用来自npmdeepcopy 作为npm module...在浏览器和node中工作......

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

let a = deepcopy(b)

使用 ( ... ) 复制对象

//bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2,c: 3 }

//good
const originalObj = { id: 5, name: 'San Francisco'};
const copyObject = {...originalObj, pincode: 4444};
console.log(copyObject)  //{ id: 5, name: 'San Francisco', pincode: 4444 }

同样可用于将数组从一个复制到另一个

const itemsCopy = [...items];

克隆对象的简单递归方法。 也可以使用 lodash.clone。

 let clone = (obj) => { let obj2 = Array.isArray(obj) ? [] : {}; for(let k in obj) { obj2[k] = (typeof obj[k] === 'object' ) ? clone(obj[k]) : obj[k]; } return obj2; } let w = { name: "Apple", types: ["Fuji", "Gala"]}; let x = clone(w); w.name = "Orange"; w.types = ["Navel"]; console.log(x); console.log(w);

原生 JS:

const shallowClone = {...originalObj};
const deepClone = JSON.parse(JSON.stringify(originalObj));

使用库:

// Lodash
const shallowClone = _.clone(originalObj);
const deepClone = _. cloneDeep(originalObj);

// JQuery
const shallowClone = jQuery.extend({}, originalObj);
const deepClone = jQuery.extend(true, {}, originalObj);

// Angular
const deepClone = angular.copy(originalObj);

Jan Turoň 上面的答案非常接近,由于兼容性问题,可能最好在浏览器中使用,但它可能会导致一些奇怪的枚举问题。 例如,执行:

for ( var i in someArray ) { ... }

在遍历数组的元素后将 clone() 方法分配给 i 。 这是避免枚举并与 node.js 一起使用的改编:

Object.defineProperty( Object.prototype, "clone", {
    value: function() {
        if ( this.cloneNode )
        {
            return this.cloneNode( true );
        }

        var copy = this instanceof Array ? [] : {};
        for( var attr in this )
        {
            if ( typeof this[ attr ] == "function" || this[ attr ] == null || !this[ attr ].clone )
            {
                copy[ attr ] = this[ attr ];
            }
            else if ( this[ attr ] == this )
            {
                copy[ attr ] = copy;
            }
            else
            {
                copy[ attr ] = this[ attr ].clone();
            }
        }
        return copy;
    }
});

Object.defineProperty( Date.prototype, "clone", {
    value: function() {
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    }
});

Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

这避免了使 clone() 方法可枚举,因为 defineProperty() 默认可枚举为 false。

请参阅http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data了解 W3C 的“结构化数据安全传递”算法,该算法旨在实现通过浏览器将数据传递给例如网络工作者。 但是,它有一些限制,因为它不处理函数。 有关更多信息,请参阅https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm ,包括 JS 中的一种替代算法,它可以让您部分实现。

根据template克隆对象。 如果您不想要一个精确的副本,但您确实想要某种可靠克隆操作的稳健性但您只想要克隆位,或者您想确保您可以控制每个属性值的存在或格式,您会怎么做克隆?

我贡献这个是因为它对我们有用,我们创建它是因为我们找不到类似的东西。 您可以使用它来克隆基于template对象的对象,该模板对象指定我要克隆的对象的哪些属性,并且模板允许函数将这些属性转换为不同的东西,如果它们不存在于源对象上,或者但是你想处理克隆。 如果它没有用,我相信有人可以删除这个答案。

   function isFunction(functionToCheck) {
       var getType = {};
       return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
   }

   function cloneObjectByTemplate(obj, tpl, cloneConstructor) {
       if (typeof cloneConstructor === "undefined") {
           cloneConstructor = false;
       }
       if (obj == null || typeof (obj) != 'object') return obj;

       //if we have an array, work through it's contents and apply the template to each item...
       if (Array.isArray(obj)) {
           var ret = [];
           for (var i = 0; i < obj.length; i++) {
               ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
           }
           return ret;
       }

       //otherwise we have an object...
       //var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values.
       var temp = cloneConstructor ? new obj.constructor() : {};

       for (var key in tpl) {
           //if we are provided with a function to determine the value of this property, call it...
           if (isFunction(tpl[key])) {
               temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
           } else {
               //if our object has this property...
               if (obj[key] != undefined) {
                   if (Array.isArray(obj[key])) {
                       temp[key] = [];
                       for (var i = 0; i < obj[key].length; i++) {
                           temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
                       }
                   } else {
                       temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
                   }
               }
           }
       }

       return temp;
   }

一个简单的调用方法是这样的:

var source = {
       a: "whatever",
       b: {
           x: "yeah",
           y: "haha"
       }
   };
   var template = {
       a: true, //we want to clone "a"
       b: {
           x: true //we want to clone "b.x" too
       }
   }; 
   var destination = cloneObjectByTemplate(source, template);

如果您想使用函数来确保返回属性或确保它是特定类型,请使用这样的模板。 我们提供了一个函数,而不是使用{ ID: true } ,它仍然只是复制源对象的ID attribute ,但它确保它是一个数字,即使它不存在于源对象上。

 var template = {
    ID: function (srcObj) {
        if(srcObj.ID == undefined){ return -1; }
        return parseInt(srcObj.ID.toString());
    }
}

数组可以很好地克隆,但如果您愿意,您也可以让自己的函数处理这些单独的属性,并执行以下特殊操作:

 var template = {
    tags: function (srcObj) {
        var tags = [];
        if (process.tags != undefined) {
            for (var i = 0; i < process.tags.length; i++) {

                tags.push(cloneObjectByTemplate(
                  srcObj.tags[i],
                  { a : true, b : true } //another template for each item in the array
                );
            }
        }
        return tags;
    }
 }

所以在上面,我们的模板只是复制源对象的tags属性(如果它存在)(假设它是一个数组),并且对于该数组中的每个元素,调用 clone 函数以基于第二个模板单独克隆它它只是复制每个标签元素的ab属性。

如果您将对象进出节点,并且您想控制这些对象的哪些属性被克隆,那么这是在node.js中控制它的好方法,并且代码也可以在浏览器中运行。

这是它的使用示例:http: //jsfiddle.net/hjchyLt1/

根据 404 位贡献者的Airbnb JavaScript 风格指南

将对象扩展运算符优于 Object.assign 用于浅拷贝对象。 使用 object rest 运算符获取省略某些属性的新对象。

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

另外我想警告您,即使 Airbnb 几乎不推荐对象传播运算符方法。 请记住,Microsoft Edge 仍然不支持此 2018 功能。

ES2016+兼容表>>

如此链接所说,使用此代码:

let clone = Object.create(Object.getPrototypeOf(obj),
 Object.getOwnPropertyDescriptors(obj));

简单的

var restore = { name:'charlesi',
age:9}
var prev_data ={
name: 'charles'
age : 10
}

var temp = JSON.stringify(prev_data)
restore = JSON.parse(temp)

restore = {
name:'charlie',
age : 12}

输出 prev_data:

{
name: 'charles'
age : 10
} 

在我的代码中,我经常定义一个function (_)来处理副本,以便我可以将by value传递给函数。 此代码创建一个深层副本,但保持继承。 它还跟踪子副本,以便可以在没有无限循环的情况下复制自引用对象。 随意使用它。

它可能不是最优雅的,但它还没有让我失望。

_ = function(oReferance) {
  var aReferances = new Array();
  var getPrototypeOf = function(oObject) {
    if(typeof(Object.getPrototypeOf)!=="undefined") return Object.getPrototypeOf(oObject);
    var oTest = new Object();
    if(typeof(oObject.__proto__)!=="undefined"&&typeof(oTest.__proto__)!=="undefined"&&oTest.__proto__===Object.prototype) return oObject.__proto__;
    if(typeof(oObject.constructor)!=="undefined"&&typeof(oTest.constructor)!=="undefined"&&oTest.constructor===Object&&typeof(oObject.constructor.prototype)!=="undefined") return oObject.constructor.prototype;
    return Object.prototype;
  };
  var recursiveCopy = function(oSource) {
    if(typeof(oSource)!=="object") return oSource;
    if(oSource===null) return null;
    for(var i=0;i<aReferances.length;i++) if(aReferances[i][0]===oSource) return aReferances[i][1];
    var Copy = new Function();
    Copy.prototype = getPrototypeOf(oSource);
    var oCopy = new Copy();
    aReferances.push([oSource,oCopy]);
    for(sPropertyName in oSource) if(oSource.hasOwnProperty(sPropertyName)) oCopy[sPropertyName] = recursiveCopy(oSource[sPropertyName]);
    return oCopy;
  };
  return recursiveCopy(oReferance);
};

// Examples:
Wigit = function(){};
Wigit.prototype.bInThePrototype = true;
A = new Wigit();
A.nCoolNumber = 7;
B = _(A);
B.nCoolNumber = 8; // A.nCoolNumber is still 7
B.bInThePrototype // true
B instanceof Wigit // true

您可以使用功能闭包来获得深拷贝的所有好处,而无需深拷贝。 这是一个非常不同的范例,但效果很好。 与其尝试复制现有对象,不如在需要时使用一个函数来实例化一个新对象。

首先,创建一个返回对象的函数

function template() {
  return {
    values: [1, 2, 3],
    nest: {x: {a: "a", b: "b"}, y: 100}
  };
}

然后创建一个简单的浅拷贝函数

function copy(a, b) {
  Object.keys(b).forEach(function(key) {
    a[key] = b[key];
  });
}

创建一个新对象,并将模板的属性复制到它上面

var newObject = {}; 
copy(newObject, template());

但是上面的复制步骤不是必须的。 您需要做的就是:

var newObject = template();

现在你有了一个新对象,测试一下它的属性是什么:

console.log(Object.keys(newObject));

这显示:

["values", "nest"]

是的,这些是 newObject 自己的属性,而不是对另一个对象的属性的引用。 让我们检查一下:

console.log(newObject.nest.x.b);

这显示:

"b"

newObject 获得了模板对象的所有属性,但没有任何依赖链。

http://jsbin.com/ISUTIpoC/1/edit?js,console

我添加了这个例子来鼓励一些辩论,所以请添加一些评论:)

我认为,在没有库的情况下,使用缓存重复是我们可以做到的最好的方法。

而被低估的WeakMap会遇到循环问题,其中存储对新旧对象的引用对可以帮助我们轻松地重新创建整个树。

我阻止了 DOM 元素的深度克隆,可能你不想克隆整个页面 :)

function deepCopy(object) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(obj) {
        if (typeof obj !== 'object' ||
            obj === null ||
            obj instanceof HTMLElement
        )
            return obj; // primitive value or HTMLElement

        if (obj instanceof Date) 
            return new Date().setTime(obj.getTime());

        if (obj instanceof RegExp) 
            return new RegExp(obj.source, obj.flags);

        if (cache.has(obj)) 
            return cache.get(obj);

        const result = obj instanceof Array ? [] : {};

        cache.set(obj, result); // store reference to object before the recursive starts

        if (obj instanceof Array) {
            for(const o of obj) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(obj); 

        for (const key of keys)
            result[key] = copy(obj[key]);

        return result;
    }

    return copy(object);
}

一些测试:

// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference

var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).

// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true

// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;

注意:我使用常量、for of 循环、=> 运算符和 WeakMaps 来创建更重要的代码。 今天的浏览器支持这种语法(ES6)

解决方案JSON.parse(JSON.stringify(orig_obj)正如许多同行所说的 deep_cloning 有几个我发现的问题,它们在下面列出:

  1. 它在复制原始对象中undefined的值时丢弃条目,
  2. 如果有InfinityNaN等一些值,它们将在复制时转换为null
  3. 如果原始对象中有Date类型,它将在克隆对象中进行字符串化( typeof date_entry --> string )。

找到了一种克隆对象的有效方法,它在各种情况下都对我很有效。 请看下面的代码,因为它解决了上面提到的JSON.parse(...)的所有缺陷,但导致正确的深度克隆:

var orig_obj = {
  string: 'my_str',
  number: 123,
  bool: false,
  nul: null,
  nested : {
    value : true
  },
  nan : NaN,
  date: new Date(), 
  undef: undefined,
  inf: Infinity,
}
console.log("original_obj before modification: ", orig_obj, "\n");
console.log(typeof orig_obj.date, "\n");

var clone_obj = Object.assign({}, orig_obj);

//this below loop will help in deep cloning and solving above issues
for(let prop in orig_obj) {
    if(typeof orig_obj[prop] === "object") {
        if(orig_obj[prop] instanceof Date)
            clone_obj[prop] = orig_obj[prop];
        else {
            clone_obj[prop] = JSON.parse(JSON.stringify(orig_obj[prop]));
        }
    }
}

console.log("cloned_obj before modification: ", orig_obj, "\n");

clone_obj.bool = true;
clone_obj.nested.value = "false";

console.log("original_obj post modification: ", orig_obj, "\n");
console.log("cloned_obj post modification: ", clone_obj, "\n");
console.log(typeof clone_obj.date);

我已经在标量对象的情况下尝试过这个,它对我有用:

function binder(i) {
  return function () {
    return i;
  };
}

a=1;
b=binder(a)(); // copy value of a into b

alert(++a); // 2
alert(b); // still 1

问候。

这是一个现代解决方案,它没有Object.assign()的缺陷(不通过引用复制):

const cloneObj = (obj) => {
    return Object.keys(obj).reduce((dolly, key) => {
        dolly[key] = (obj[key].constructor === Object) ?
            cloneObj(obj[key]) :
            obj[key];
        return dolly;
    }, {});
};

使用默认值(历史上特定于 nodejs,但现在可以从浏览器中使用,这要归功于现代 JS):

import defaults from 'object.defaults';

const myCopy = defaults({}, myObject);

为了更好地理解对象的复制,这个说明性的 jsbin 可能很有价值

class base {
  get under(){return true}
}

class a extends base {}

const b = {
  get b1(){return true},
  b: true
}

console.log('Object assign')
let t1 = Object.create(b)
t1.x = true
const c = Object.assign(t1, new a())
console.log(c.b1 ? 'prop value copied': 'prop value gone')
console.log(c.x ? 'assigned value copied': 'assigned value gone')
console.log(c.under ? 'inheritance ok': 'inheritance gone')
console.log(c.b1 ? 'get value unchanged' : 'get value lost')
c.b1 = false
console.log(c.b1? 'get unchanged' : 'get lost')
console.log('-----------------------------------')
console.log('Object assign  - order swopped')
t1 = Object.create(b)
t1.x = true
const d = Object.assign(new a(), t1)
console.log(d.b1 ? 'prop value copied': 'prop value gone')
console.log(d.x ? 'assigned value copied': 'assigned value gone')
console.log(d.under ? 'inheritance n/a': 'inheritance gone')
console.log(d.b1 ? 'get value copied' : 'get value lost')
d.b1 = false
console.log(d.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e = { ...t1, ...t2 }
console.log(e.b1 ? 'prop value copied': 'prop value gone')
console.log(e.x ? 'assigned value copied': 'assigned value gone')
console.log(e.under ? 'inheritance ok': 'inheritance gone')
console.log(e.b1 ? 'get value copied' : 'get value lost')
e.b1 = false
console.log(e.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator on getPrototypeOf')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e1 = { ...Object.getPrototypeOf(t1), ...Object.getPrototypeOf(t2) }
console.log(e1.b1 ? 'prop value copied': 'prop value gone')
console.log(e1.x ? 'assigned value copied': 'assigned value gone')
console.log(e1.under ? 'inheritance ok': 'inheritance gone')
console.log(e1.b1 ? 'get value copied' : 'get value lost')
e1.b1 = false
console.log(e1.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('keys, defineProperty, getOwnPropertyDescriptor')
f = Object.create(b)
t2 = new a()
f.x = 'a'
Object.keys(t2).forEach(key=> {
  Object.defineProperty(f,key,Object.getOwnPropertyDescriptor(t2, key))
})
console.log(f.b1 ? 'prop value copied': 'prop value gone')
console.log(f.x ? 'assigned value copied': 'assigned value gone')
console.log(f.under ? 'inheritance ok': 'inheritance gone')
console.log(f.b1 ? 'get value copied' : 'get value lost')
f.b1 = false
console.log(f.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('defineProperties, getOwnPropertyDescriptors')
let g = Object.create(b)
t2 = new a()
g.x = 'a'
Object.defineProperties(g,Object.getOwnPropertyDescriptors(t2))
console.log(g.b1 ? 'prop value copied': 'prop value gone')
console.log(g.x ? 'assigned value copied': 'assigned value gone')
console.log(g.under ? 'inheritance ok': 'inheritance gone')
console.log(g.b1 ? 'get value copied' : 'get value lost')
g.b1 = false
console.log(g.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')

我已经完成了上述所有解决方案,它们都很好。 但是,您可以使用另一种方法来克隆对象(使用值不引用)。 对象.assign

let x = {
    a: '1',
    b: '2'
}

let y = Object.assign({}, x)
y.a = "3"

console.log(x)

输出将是

{ a: '1', b: '2' }

此外,您还可以使用相同的方法克隆数组。

clonedArray = Object.assign([], array)

使用扩展语法执行对象的浅拷贝。 这意味着没有任何嵌套对象实例被克隆,正如您在下面的示例中看到的嵌套对象child对象一样

 const user1 = { name: 'Alex', address: '15th Park Avenue', age: 43, child:{ name: 'John' } } const user2 = {...user1}; user1.child.name = 'chris'; console.log(user1); console.log(user2);

为了解决这个嵌套对象问题并执行深拷贝,我们可以使用JSON.parse(JSON.stringify(someObject))

 const user1 = { name: 'Alex', address: '15th Park Avenue', age: 43, child:{ name: 'John' } } const user2 = JSON.parse(JSON.stringify(user1)); user1.child.name = 'chris'; console.log(user1); console.log(user2);

好的,所以这可能是浅拷贝的最佳选择。 if 遵循了许多使用 assign 的示例,但它也保留了继承和原型。 它也很简单,适用于大多数类数组和对象,除了那些具有构造函数要求或只读属性的对象。 但这意味着它对于 TypedArrays、RegExp、Date、Maps、Sets 和 Object 版本的原语(布尔值、字符串等)会惨遭失败。

function copy ( a ) { return Object.assign( new a.constructor, a ) }

其中a可以是任何 Object 或 class 构造的实例,但对于使用专门的 getter 和 setter 或具有构造函数要求的东西也不可靠,但对于更简单的情况,它会摇摆不定。 它也适用于论点。

您也可以将它应用于原语以获得奇怪的结果,但是......除非它最终成为一个有用的黑客,谁在乎。

结果来自基本的内置对象和数组...

> a = { a: 'A', b: 'B', c: 'C', d: 'D' }
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> b = copy( a )
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> a = [1,2,3,4]
[ 1, 2, 3, 4 ]
> b = copy( a )
[ 1, 2, 3, 4 ]

并且由于平均 get/setter、构造函数所需的参数或只读属性以及对父亲的犯罪而失败。

> a = /\w+/g
/\w+/g
> b = copy( a )  // fails because source and flags are read-only
/(?:)/
> a = new Date ( '1/1/2001' )
2000-12-31T16:00:00.000Z
> b = copy( a )  // fails because Date using methods to get and set things
2017-02-04T14:44:13.990Z
> a = new Boolean( true )
[Boolean: true]
> b = copy( a )  // fails because of of sins against the father
[Boolean: false]
> a = new Number( 37 )
[Number: 37]
> b = copy( a )  // fails because of of sins against the father
[Number: 0]
> a = new String( 'four score and seven years ago our four fathers' )
[String: 'four score and seven years ago our four fathers']
> b = copy( a )  // fails because of of sins against the father
{ [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' } 

我不知道这不适用于哪些情况,但它给了我一个数组的副本。 我觉得它很可爱:) 希望它有帮助

copiedArr = origArr.filter(function(x){return true})

如果您的对象是一个类(例如https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes ):

var copiedObject = jQuery.extend(true, {}, originalObject);
copiedObject.__proto__ = originalObject.__proto__;

然后在copiedObject你有一个originalObject对象类的深度复制实例及其所有方法。

如果您使用的是 TypeScript,需要支持较旧的 Web 浏览器(因此不能使用Object.assign ),并且没有使用内置克隆方法的库,您可以在几行代码中让自己成为一个combine助手代码。 它结合了对象,如果你只有一个,就克隆它。

/** Creates a new object that combines the properties of the specified objects. */
function combine(...objs: {}[]) {
    const combined = {};
    objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));
    return combined;
}

您可以在不修改父对象的情况下克隆您的对象 -

    /** [Object Extend]*/
    ( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) {
        for ( var property in source )
            destination[property] = source[property];
        return destination;
    } ) );
    /** [/Object Extend]*/
    /** [Object clone]*/
    ( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) {
        return this.extend( {}, object );
    } ) );
    /** [/Object clone]*/

    let myObj = {
        a:1, b:2, c:3, d:{
            a:1, b:2, c:3
        }
    };

    let clone = Object.clone( myObj );

    clone.a = 10;

    console.log('clone.a==>', clone.a); //==> 10

    console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here

    let clone2 = Object.clone( clone );

    clone2.a = 20;

    console.log('clone2.a==>', clone2.a); //==> 20

    console.log('clone.a==>', clone.a); //==> 10 // object not modified here

我正在为这个问题提供一个答案,因为我在这里看不到任何可以解决DOM元素问题的本机递归实现。

问题在于<element>具有parent属性和child属性,它们链接到具有parent值和child值的其他元素,这些值指向原始<element> ,从而导致无限递归循环冗余

如果您的对象是安全且简单的,例如

{
    '123':456
}

...那么这里的任何其他答案都可能有效。

但如果你有...

{
    '123':<reactJSComponent>,
    '456':document.createElement('div'),
}

...那么你需要这样的东西:

    // cloneVariable() : Clone variable, return null for elements or components.
var cloneVariable = function (args) {
    const variable = args.variable;

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

    if(typeof(variable) === 'object') {
            if(variable instanceof HTMLElement || variable.nodeType > 0) {
                    return null;
            }

            if(Array.isArray(variable)) {
                    var arrayclone = [];

                    variable.forEach((element) => {
                            arrayclone.push(cloneVariable({'variable':element}));
                    });

                    return arrayclone;
            }

            var objectclone = {};

            Object.keys(variable).forEach((field) => {
                    objectclone[field] = cloneVariable({'variable':variable[field]});
            });

            return objectclone;
    }

    return variable;
}
var x = {'e': 2, 'd': 8, 'b': 5};

const y = {};
for(let key in x) {
    y[key] = x[key];
}
console.log(y); // =>>> {e: 2, d: 8, b: 5}

const z = {};
Object.keys(x).forEach(key => {
    z[key] = x[key];
});
console.log(z); // =>>> {e: 2, d: 8, b: 5}

const w = {};
for(let i = 0; i < Object.keys(x).length; i++) {
    w[Object.keys(x)[i]] = x[Object.keys(x)[i]];
}
console.log(w); // =>>> {e: 2, d: 8, b: 5}

const v = {};
for(let key of Object.keys(x)) {
    v[key] = x[key];
}
console.log(v); // =>>> {e: 2, d: 8, b: 5}

x['q'] = 100;   // Altering x will not affect the other objects

console.log(x); // =>>> {e: 2, d: 8, b: 5, q: 100}
console.log(y); // =>>> {e: 2, d: 8, b: 5}
console.log(z); // =>>> {e: 2, d: 8, b: 5}
console.log(w); // =>>> {e: 2, d: 8, b: 5}
console.log(v); // =>>> {e: 2, d: 8, b: 5}

在 JavaScript 中复制对象的方法

  1. 使用展开 ( ... ) 语法
  2. 使用Object.assign()方法
  3. 使用JSON.stringify()JSON.parse()方法
const person = {
    firstName: 'John',
    lastName: 'Doe'
};

// using spread ...
let p1 = {
    ...person
};

// using  Object.assign() method
let p2 = Object.assign({}, person);

// using JSON
let p3 = JSON.parse(JSON.stringify(person));

您可以使用 rest 运算符来克隆数组或对象

let myObj = {1: 100, 'a': 200};

let clone = {...myObj}; 

clone.a = 300;

console.log(clone.a) // Output :- 300
console.log(myObj.a) // Output :- 200

这会生成您的obj新副本(不仅仅是参考)。

let myCopy = JSON.parse(JSON.stringify(obj)); 

..比_.cloneDeep(obj)更有效。

如果您的对象中没有循环依赖项,我建议使用其他答案之一或jQuery 的复制方法,因为它们看起来都非常有效。

如果存在循环依赖关系(即两个子对象相互链接),那么您就有点搞砸了,因为(从理论上) 没有办法优雅地解决这个问题

//
// creates 'clone' method on context object
//
//  var 
//     clon = Object.clone( anyValue );
//
!((function (propertyName, definition) {
    this[propertyName] = definition();
}).call(
    Object,
    "clone",
    function () {
        function isfn(fn) {
            return typeof fn === "function";
        }

        function isobj(o) {
            return o === Object(o);
        }

        function isarray(o) {
            return Object.prototype.toString.call(o) === "[object Array]";
        }

        function fnclon(fn) {
            return function () {
                fn.apply(this, arguments);
            };
        }

        function owns(obj, p) {
            return obj.hasOwnProperty(p);
        }

        function isemptyobj(obj) {
            for (var p in obj) {
                return false;
            }
            return true;
        }

        function isObject(o) {
            return Object.prototype.toString.call(o) === "[object Object]";
        }
        return function (input) {
            if (isfn(input)) {
                return fnclon(input);
            } else if (isobj(input)) {
                var cloned = {};
                for (var p in input) {
                    owns(Object.prototype, p)
                    || (
                        isfn(input[p])
                        && ( cloned[p] = function () { return input[p].apply(input, arguments); } )
                        || ( cloned[p] = input[p] )
                    );
                }
                if (isarray(input)) {
                    cloned.length = input.length;
                    "concat every filter forEach indexOf join lastIndexOf map pop push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift"
                    .split(" ")
                    .forEach(
                      function (methodName) {
                        isfn( Array.prototype[methodName] )
                        && (
                            cloned[methodName] =
                            function () {
                                return Array.prototype[methodName].apply(cloned, arguments);
                            }
                        );
                      }
                    );
                }
                return isemptyobj(cloned)
                       ? (
                          isObject(input)
                          ? cloned
                          : input
                        )
                       : cloned;
            } else {
                return input;
            }
        };
    }
));
//

由于同样的问题,我来到了这个页面,但我既没有使用 JQuery,也没有一个克隆方法适用于我自己的对象。

我知道我的回答与这个问题并没有太大的关系,因为它是一种不同的方法。 我不使用克隆函数,而是使用创建函数。 它为以下(不幸地限制)目的对我有用:

  1. 我主要使用 JSP 生成的 Javascript
  2. 我一开始就知道必须生成哪个对象(在我的情况下,它是来自数据库的信息,它被获取一次并且需要在 JS 中更频繁地部署。

首先,我这样定义我的对象:

var obj= new Object();
obj.Type='Row';
obj.ID=1;
obj.Value='Blah blah';

现在我移动了所有东西,比如:

function getObjSelektor(id_nummer,selected){
var obj = document.createElement("select");
obj.setAttribute("id","Selektor_"+id_nummer);
obj.setAttribute("name","Selektor");
obj.setAttribute("size","1");

var obj_opt_1 = document.createElement("option");
obj_opt_1.setAttribute("value","1");
if(1==selected)
    posopval_opt_1.setAttribute("selected","selected");
obj_opt_1.innerHTML="Blah blah";
obj.appendChild(obj_opt_1);

var obj_opt_2 = document.createElement("option");
obj_opt_2.setAttribute("value","2");
if(2==selected)
    obj_opt_2.setAttribute("selected","selected");
obj_opt_2.innerHTML="2nd Row";
obj.appendChild(obj_opt_2);

...

return obj;
}

并在常规代码中调用该函数:

myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));

如前所述,这是一种不同的方法,它为我的目的解决了我的问题。

如果你有一个带函数的对象,你可以用 JSONfn 来做,见http://www.eslinstructor.net/jsonfn/

var obj= {
    name:'Marvin',
    getName :  function(){
      return this.name;
    }
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));

复制最终可能指向自身的对象的问题可以通过简单的检查来解决。 添加此检查,每次有复制操作。 它可能很,但它应该可以工作。

我使用toType()函数显式地返回对象类型。 我也有自己的copyObj()函数,它在逻辑上非常相似,它回答了所有三个 Object()、Array() 和 Date() 情况。

我在 NodeJS 中运行它。

尚未测试。

// Returns true, if one of the parent's children is the target.
// This is useful, for avoiding copyObj() through an infinite loop!
function isChild(target, parent) {
  if (toType(parent) == '[object Object]') {
    for (var name in parent) {
      var curProperty = parent[name];

      // Direct child.
      if (curProperty = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {
        if (isChild(target, curProperty)) return true;
      }
    }
  } else if (toType(parent) == '[object Array]') {
    for (var i=0; i < parent.length; i++) {
      var curItem = parent[i];

      // Direct child.
      if (curItem = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {
        if (isChild(target, curItem)) return true;
      }
    }
  }

  return false;     // Not the target.
}

要处理JSON.stringify无法处理的循环对象,您可以引入一个名为JSOG的库,它将任意图形序列化和反序列化为 JSON 格式。

var clone = JSOG.parse(JSOG.stringify(original));

尝试用这个技巧修补 JSOG 以进行克隆也可能很有趣(目前没有时间,但如果有人想试一试......):

序列化一个简单的函数:

foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"

反序列化一个函数:

eval("foo.f = " + stringForm)

需要一些约定(可能以属性的名称)来识别函数与常规字符串(可能是@func_f )。

当然,如果该函数调用第二个函数,则第二个函数将需要像原来的那样存在。

但是,如果您要接受来自不受信任来源的序列化形式,上述内容是非常危险的,但是接受来自不受信任来源的任何形式的任何函数都是危险的,因此如果您对克隆函数感兴趣,则必须已经建立信任(或者您已经打算编写安全漏洞!)。

免责声明:我没有测试过 JSOG stringify/parse vs JSON stringify/parse 的速度,但它确实适用于我测试过的简单(圆形)对象。

好的,我知道它有很多答案,但没有人指出,EcmaScript5 有分配方法,适用于 FF 和 Chrome,它复制可枚举和自己的属性和符号。

对象分配

所以只是添加一些简单的东西,如果你想创建一个灯箱或类似的东西,上面的大多数答案都是不必要的复杂,简单地复制源属性和其他任何必要的东西会更容易。

简短而甜蜜:

let clone = Object.fromEntries(Object.entries(obj));

演示:

 let obj = {a: 'b'}; let clone = Object.fromEntries(Object.entries(obj)); clone.a = 'c'; console.log(obj, clone);

不同的

仅复制顶层: {...object}Object.assign({}, object)

 let objA = { a: "keyA", b: { c: "keyC", } } let objB = Object.assign({}, objA); // or {...objB} // change objB objB.a = "Change objA.a (top)" console.log("objA.a (top) No Change:\n" + JSON.stringify(objA, false, 2)); objB.bc = "change should be only for objB.bc but it in objA.bc" console.log("objA.ac second level has Change:\n" + JSON.stringify(objA, false, 2));

对于深拷贝,对旧浏览器使用structuredClone() 2022 或JSON.parse(JSON.stringify(object)) ,无需破解即可轻松使用。

 let objA = { a: "keyA", b: { c: "keyC", } } let objB = typeof structuredClone == 'function' ? structuredClone(objA) : JSON.parse(JSON.stringify(objA)); // change objB objB.a = "Change objA.a (top)" objB.bc = "change should be only for objB.c but it in objA.c" console.log("objA has no Change:\n" + JSON.stringify(objA, false, 2));

来自Apple JavaScript 编码指南

// Create an inner object with a variable x whose default
// value is 3.
function innerObj()
{
        this.x = 3;
}
innerObj.prototype.clone = function() {
    var temp = new innerObj();
    for (myvar in this) {
        // this object does not contain any objects, so
        // use the lightweight copy code.
        temp[myvar] = this[myvar];
    }
    return temp;
}

// Create an outer object with a variable y whose default
// value is 77.
function outerObj()
{
        // The outer object contains an inner object.  Allocate it here.
        this.inner = new innerObj();
        this.y = 77;
}
outerObj.prototype.clone = function() {
    var temp = new outerObj();
    for (myvar in this) {
        if (this[myvar].clone) {
            // This variable contains an object with a
            // clone operator.  Call it to create a copy.
            temp[myvar] = this[myvar].clone();
        } else {
            // This variable contains a scalar value,
            // a string value, or an object with no
            // clone function.  Assign it directly.
            temp[myvar] = this[myvar];
        }
    }
    return temp;
}

// Allocate an outer object and assign non-default values to variables in
// both the outer and inner objects.
outer = new outerObj;
outer.inner.x = 4;
outer.y = 16;

// Clone the outer object (which, in turn, clones the inner object).
newouter = outer.clone();

// Verify that both values were copied.
alert('inner x is '+newouter.inner.x); // prints 4
alert('y is '+newouter.y); // prints 16

史蒂夫

我在复制对象时遇到了问题。 这是因为当您执行以下操作时,您只是对对象进行“引用”,并且稍后更新源对象值时,被克隆的复制对象也会更改值,因为它只是一个“引用”,因此您查看源对象最后一次更改的多个值。

let x = { a: 1 };
let y = x; // y is a reference to x, so if x changes y also changes and v/v

因此,要解决此问题,请执行以下操作:

let y = JSON.parse(JSON.stringify(x)); //see Note below

防止引用的另一种方法是执行以下操作:

let x = { a: 1 };
let y = Object.assign({}, x); // Object.assign(target, ...sources)

y.a = 2;
console.log(x); // { a: 1 }
console.log(y); // { a: 2 }

注意: https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#warning_for_deep_clone

我找到了一种使用函数克隆 object 的方法(分多行以便于理解):

const clone = Object.assign(
    Object.create(
        Object.getPrototypeOf(originalObject)
    ),
    dataObject
);

我最喜欢和优雅的 JS 对象克隆解决方案是

function CloneObject() {}
function cloneObject(o) {
   CloneObject.prototype = o;
   return new CloneObject();
}

使用cloneObject(object)获取 JS 对象的克隆。

与许多复制解决方案不同,此克隆将原型关系保留在克隆对象中。

function clone(obj)
{
    var cloneObj = Object.create(obj);

    return cloneObj;
}

在 Javascript 中对象单独继承另一个对象(原型继承)。 Object.create(obj) 返回一个对象,它是 obj 的子对象或子对象。 在上述函数中,它将有效地返回对象的副本。

但是,这是一种非常奇怪的克隆方式,因为我没有将继承用于其真正目的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM