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

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

提问人: 提问时间:9/24/2008 最后编辑:26 revs, 21 users 25%jschrab 更新时间:4/5/2023 访问量:2856261

问:

这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或交互。

克隆 JavaScript 对象的最有效方法是什么?我见过被使用,但这是非标准的,只有Firefox支持

我做过这样的事情,但质疑效率。

我还看到了具有各种缺陷的递归复制函数。
我很惊讶没有规范的解决方案存在。
obj = eval(uneval(o));obj = JSON.parse(JSON.stringify(o));

JavaScript 对象 克隆

评论

566赞 Tegra Detra 3/22/2012
Eval 不是邪恶的。使用eval 不好是。如果你害怕它的副作用,你就用错了。您担心的副作用是使用它的原因。顺便说一句,有没有人真正回答了你的问题?
15赞 b01 3/12/2013
克隆对象是一项棘手的工作,尤其是对于任意集合的自定义对象。这可能就是为什么没有开箱即用的方法可以做到这一点。
12赞 user56reinstatemonica8 9/8/2014
eval()这通常是一个坏主意,因为许多 Javascript 引擎的优化器在处理通过 eval 设置的变量时必须关闭。仅仅在代码中加入可能会导致性能下降。eval()
12赞 oriadam 5/24/2017
请注意,该方法将丢失任何在 JSON 中没有等效项的 Javascript 类型。例如:将生成JSONJSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false})){a: null, b: null, c: null, g: false}
0赞 Navid 7/6/2019
react 社区引入了 immutability-helper

答:

380赞 11 revs, 9 users 30%ConroyP #1

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

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

评论

6赞 era s'q 8/7/2022
你能解释一下部分吗?isActiveClone
0赞 Peti29 3/13/2023
它似乎跟踪循环引用。例如:如果你试图克隆上面的对象而不使用该部分,你最终会陷入无限递归(因为对属性的递归调用)。const a = {}; a['selfref'] = a; a['text'] = 'something'; alert(a.selfref.text);aisActiveCloneclone()selfref
0赞 S.Serpooshan 4/5/2023
这里最好的答案之一,以可接受的速度(性能)处理和清理代码ArraysCyclic References
25赞 Mark Cidade #2
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
5838赞 22 revs, 20 users 20%Dan Dascalescu #3

原生深度克隆

现在有一种叫做“结构化克隆”的 JS 标准,它在 Node 11 及更高版本中实验性地工作,将登陆浏览器,并且为现有系统提供 polyfill

structuredClone(value)

如果需要,请先加载 polyfill:

import structuredClone from '@ungap/structured-clone';

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

较早的答案

数据丢失的快速克隆 - JSON.parse/stringify

如果不在对象中使用 s、functions、、、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、稀疏数组、类型化数组或其他复杂类型,则深度克隆对象的非常简单的一行代码是:DateundefinedInfinity

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'
  re: /.*/,  // lost
}
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()

有关基准,请参阅 Corban 的回答

使用文库进行可靠克隆

由于克隆对象并非易事(复杂类型、循环引用、函数等),因此大多数主要库都提供克隆对象的功能。不要重新发明轮子 - 如果您已经在使用库,请检查它是否具有对象克隆功能。例如

评论

135赞 Gabriel Hautclocq 9/30/2020
小心! 它还将修改对象 A !var A = { b: [ { a: [ 1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] } ] }; B = Object.assign( {}, A ); delete B.b[0].b;
17赞 Unicornist 12/15/2020
@Gabriel Hautclocq,这是因为或者两者都引用了内存中的同一对象。如果属性具有非对象值(如数字或字符串),则会正常复制该属性。但是,当复制包含对象值的属性时,它是按引用复制的,而不是按值复制的。另外,请记住,数组是 JS 中的一个对象。证明:A.bB.bAtypeof [] == 'object' && [] instanceof Array
47赞 Gabriel Hautclocq 12/15/2020
@Unicornist 是的,这就是为什么 Object.assign 没有回答这个问题:“在 JavaScript 中深度克隆对象的最有效方法是什么?因此,至少它不应该作为深度克隆的 ES6 解决方案呈现。标题“ES6”具有误导性,至少应该更改以反映这不是一种深度克隆方法。“浅层”这个词很容易被忽视,很多人只是采用他们在 Stack Overflow 中找到的最简单的解决方案,而没有阅读所有内容。依赖 Object.assign 进行对象克隆是很危险的。因此,我说。
6赞 bakkaa 1/15/2021
我使用了一个名为 really fast deep clone 的库:github.com/davidmarkclements/rfdc 对我来说效果非常好。
3赞 Gabriel Hautclocq 4/28/2021
@Ricardo 当然,在我写完评论之后,您可以查看答案的历史,以发现“(浅拷贝)”已在“ES6”之后添加。现在更清楚,这是一个肤浅的副本。
115赞 2 revsKamarey #4

法典:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

测试:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

评论

0赞 Gershom Maes 4/28/2021
我不这样做处理圆形结构
122赞 3 revs, 3 users 69%Alan #5

这是我正在使用的:

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

评论

0赞 Martin Luther ETOUMAN 4/9/2021
尝试: var a = {b: 1, c: 3, d: { a: 10, g: 20, h: { today: new Date() }}};对我不起作用。但做到了。Object.assign({}, a)
0赞 Gershom Maes 4/28/2021
更糟糕的是,试试let o = {}; o.o = o; cloneObject(o);
0赞 Nate Levin 7/6/2022
注意:这不适用于 sDate
0赞 zkldi 11/8/2022
这不适用于数组,因为它会将它们变成对象:will become .{ a: ["foo", "bar"} }{ a { "0": "foo", "1": "bar" } }
0赞 MohanaRajesh 2/4/2023
我已经为复杂对象扩展了此功能,但现在它不支持日期 stackblitz.com/edit/typescript-vudfgn?file=index.ts
72赞 5 revs, 4 users 31%Zibri #6
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});
13赞 2 revs, 2 users 70%Dima #7
// 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;
};
31赞 2 revs, 2 users 50%protonfish #8

Crockford建议(我更喜欢)使用这个函数:

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

var newObject = object(oldObject);

它简洁明了,按预期工作,您不需要库。


编辑:

这是 的 polyfill,因此您也可以使用它。Object.create

var newObject = Object.create(oldObject);

注意:如果您使用其中的一些,您可能会遇到一些使用 .因为,创建新的空对象谁继承了.但它对于克隆对象仍然有用和实用。hasOwnPropertycreateoldObject

例如,如果oldObject.a = 5;

newObject.a; // is 5

但:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
21赞 3 revs, 2 users 98%Page Notes #9

对于类似数组的对象,似乎还没有理想的深度克隆运算符。如下面的代码所示,John Resig 的 jQuery 克隆器将具有非数字属性的数组转换为非数组的对象,而 RegDwight 的 JSON 克隆器会删除非数字属性。以下测试在多个浏览器上说明了这些要点:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)
566赞 4 revs, 4 users 59%Sultan Shakir #10

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

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

评论

2赞 vsync 10/24/2020
函数日期
11赞 Koushik Shom Choudhury 9/7/2021
对于具有 Circular 属性的对象失败
1赞 Andy Carlson 2/8/2022
或 Sets 或其他不可 JSON 序列化的属性
2赞 albanx 5/17/2022
恕我直言,我认为这是克隆数据对象的最佳方法。尤其是在处理从 API 和状态管理中获取的数据时。我相信,如果您需要克隆使用本机 Javascript 结构(函数、日期、NaN...)创建的对象,则有问题,或者很可能您不需要克隆它。
1赞 zlatanned 8/27/2022
这对于 NaN、Infinity、undefined 等类型来说效率低下,JSON.stringify 将它们转换为 null。参考:JSON.parse(JSON.stringify({a:null,b:undefined})) 等于 {a: null}
2484赞 16 revs, 14 users 60%Corban Brook #11

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

在我之前的测试中,我发现速度是主要关注点

JSON.parse(JSON.stringify(obj))

成为深度克隆对象的最慢方式(它比 jQuery.extend 慢,标志设置为 true 10-20%)。deep

当标志设置为(浅克隆)时,jQuery.extend 非常快。这是一个不错的选择,因为它包含一些用于类型验证的额外逻辑,并且不会复制未定义的属性等,但这也会稍微减慢您的速度。deepfalse

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

最后,如果您尝试在热循环中克隆已知对象结构,则只需内联克隆过程并手动构造对象即可获得更高的性能。

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

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

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

此外,请注意,至少在 Chrome 65 中,原生克隆不是要走的路。根据 JSPerf 的说法,通过创建新函数来执行本机克隆比使用 JSON.stringify 慢近 800 倍,后者在全面运行速度非常快。

ES6 更新

如果您使用的是 Javascript ES6,请尝试使用这种本机方法进行克隆或浅拷贝。

Object.assign({}, obj);

评论

14赞 papillon 2/15/2021
请注意,您的工作台中有 2 个错误:首先,它将一些浅层克隆(lodash 和 )与一些深层克隆()进行比较。其次,它说 lodash 的“深度克隆”,但它做的是浅层克隆。_.cloneObject.assignJSON.parse(JSON.stringify())
0赞 ragan 5/29/2022
请注意,在相同的基准测试工具中进行测试时,对象分布似乎比 更有效。速度提高约 ~20%。let obj2 = {...obj}Object.assign()
0赞 Chrys G 12/3/2022
请注意,re Object.assign({}, obj} - 这是一个浅层副本,而不是深度克隆。这意味着,如果一个属性本身就是一个对象,你只能得到一个引用。
0赞 Isaac King 5/1/2023
+1000 用于内联克隆。我已经对对象的已知属性列表进行了硬编码,并循环访问它们,这给了我每毫秒大约 3000 个副本。一旦我摆脱了循环,并在对象文字中写出每个属性,它就会达到每毫秒大约 200 万个副本。绝对疯狂的速度提升,我从未意识到循环如此缓慢。
3赞 2 revs, 2 users 98%gion_13 #12

我认为,如果您想推广对象克隆算法,这是最好的解决方案。
它可以与jQuery一起使用,也可以不与jQuery一起使用,尽管如果您希望克隆的对象与原始对象具有相同的“类”,我建议将jQuery的扩展方法排除在外。

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);            
}
16赞 neatonk #13

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

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

循环阵列测试仪

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

功能测试...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
5赞 2 revs, 2 users 66%Steve Tomlin #14

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

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

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

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

评论

0赞 zkldi 11/8/2022
这是一个浅拷贝,不适用于数组。它还在全局范围内声明。i
57赞 2 revsJoe #15

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

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

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

评论

0赞 zkldi 11/8/2022
这不是现有对象的深度克隆,这只是创建一个新对象。
50赞 3 revs, 3 users 71%itsadok #16

如果您正在使用它,则 Underscore.js 库具有克隆方法。

var newObject = _.clone(oldObject);

评论

2赞 Diederik 9/30/2021
这做的是浅层拷贝,而不是 OP 正在寻找的深层拷贝。
658赞 Jeremy 6/6/2012 #17

结构化克隆

2022 年更新:structuredClone 全局函数已经在 Firefox 94、Node 17 和 Deno 1.14 中可用

HTML 标准包括一个内部结构化克隆/序列化算法,该算法可以创建对象的深度克隆。它仍然局限于某些内置类型,但除了 JSON 支持的少数类型外,它还支持 Dates、RegExps、Maps、Sets、Blobs、FileLists、ImageDatas、稀疏数组、类型化数组,以及将来可能更多。它还保留了克隆数据中的引用,使其能够支持会导致 JSON 错误的循环和递归结构。

Node.js 中的支持:

structuredClone 全局函数由 Node 17.0 提供:

const clone = structuredClone(original);

以前的版本:Node.js 中的模块(从 Node 11 开始)直接公开结构化序列化 API,但此功能仍标记为“实验性”,并且可能会在将来的版本中更改或删除。如果您使用的是兼容版本,则克隆对象非常简单:v8

const v8 = require('v8');

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

浏览器中的直接支持:在 Firefox 94 中可用

structuredClone 全局函数将很快由所有主流浏览器提供(之前在 GitHub 上的 whatwg/html#793 中讨论过)。它看起来/将看起来像这样:

const clone = structuredClone(original);

在发布之前,浏览器的结构化克隆实现仅间接公开。

异步解决办法:可用。😕

使用现有 API 创建结构化克隆的开销较低的方法是通过 MessageChannel 的一个端口发布数据。另一个端口将发出一个事件,其中包含附加的 .遗憾的是,侦听这些事件必然是异步的,而同步替代方案不太实用。message.data

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

使用示例:

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

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

使用示例:

'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 构造函数创建其关联数据的结构化克隆。它还会尝试向用户显示浏览器通知,但除非您请求通知权限,否则这将以静默方式失败。如果您拥有用于其他目的的权限,我们将立即关闭我们创建的通知。

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

使用示例:

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

评论

59赞 Fardin K. 8/1/2014
这太不对劲了!该 API 不应以这种方式使用。
328赞 Justin L. 8/15/2014
作为在Firefox中实现pushState的人,我对这个黑客感到一种奇怪的自豪和厌恶。干得好,伙计们。
1赞 Shishir Arora 7/4/2019
pushState 或 Notification hack 不适用于某些对象类型,如 Function
5赞 oelna 4/10/2022
2022 年 4 月更新:适用于 FF 94+、Chrome 98+ 和 Safari 15.4+ 和 Edge 98+,因此适用于所有当前版本的主要浏览器!structuredClone
2赞 Armen Michaeli 5/4/2022
只要 HTML 创作委员会从根本上无法设计出高质量的 API,并继续生产设计被破坏的 API,那么像上述那样滥用 API(对 @Jeremy 勇敢地尝试展示解决方案并不感到冒犯)就会继续下去。例如,结构化克隆算法定义了一个僵化的过程(例如,脚本几乎无法扩展),同时给用户代理留下了太多的东西。例如,Firefox无法克隆对象,但MDN自豪地表示它支持和朋友,尽管这是一种解释方式。ErrorstructuredClone
22赞 4 revs, 3 users 79%Maël Nison #18

浅文单行(ECMAScript 第 5 版):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

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

和浅文单行(ECMAScript 第 6 版,2015 年):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

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

评论

0赞 zkldi 11/8/2022
这是一个浅层副本和一个深层克隆,就像所要求的问题一样。这不适用于嵌套对象。
13赞 4 revs, 4 users 59%user1547016 #19

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

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

评论

0赞 Danubian Sailor 8/1/2014
它将基元转换为包装对象,在大多数情况下不是一个好的解决方案。
0赞 Jimbo Jonny 2/3/2016
@DanubianSailor - 我不认为它...它似乎从一开始就立即返回原语,并且似乎没有对它们做任何事情,在返回它们时将它们变成包装对象。
67赞 3 revspvorb #20

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

你也可以在 npm 上找到它。它可以用于浏览器以及 Node.js。

以下是如何使用它的示例:

安装方式

npm install clone

或将其与 Ender 打包。

ender build clone [...]

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

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

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

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

48赞 9 revsMatt Browne #21

这是上面 ConroyP 答案的一个版本,即使构造函数具有必需的参数,它也有效:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

这个函数在我的simpleoo库中也可用。

编辑:

这是一个更强大的版本(感谢 Justin McCandless,它现在也支持循环引用):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from 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();
    };
}
18赞 2 revs, 2 users 81%Michael Uzquiano #22

我有两个很好的答案,这取决于你的目标是是否是克隆一个“普通的旧 JavaScript 对象”。

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

对于普通的旧 JavaScript 对象,在现代运行时中克隆对象的一种久经考验的好方法非常简单:

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

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

它有效吗?哎呀,是的。我们尝试了各种克隆方法,效果最好。我敢肯定,有些忍者会想出一种更快的方法。但我怀疑我们谈论的是边际收益。

这种方法简单易行。把它包装成一个方便的功能,如果你真的需要挤出一些收益,那就以后再去。

现在,对于非纯 JavaScript 对象,没有一个非常简单的答案。事实上,由于 JavaScript 函数和内部对象状态的动态性质,不可能有。深度克隆包含函数的 JSON 结构需要重新创建这些函数及其内部上下文。而 JavaScript 根本没有标准化的方式来做到这一点。

同样,执行此操作的正确方法是通过一种方便的方法,您可以在代码中声明和重用该方法。这种便捷方法可以赋予您对自己的对象的一些了解,因此您可以确保在新对象中正确地重新创建图形。

我们是自己写的,但我见过的最好的通用方法在这里介绍:

http://davidwalsh.name/javascript-clone

这是正确的想法。作者(David Walsh)注释了广义函数的克隆。这是您可能会选择执行的操作,具体取决于您的用例。

主要思想是,您需要在每个类型的基础上特殊处理函数(或原型类,可以这么说)的实例化。在这里,他提供了一些 RegExp 和 Date 的示例。

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

这有效率吗?哎呀,是的。鉴于目标是生成真正的深拷贝克隆,那么您将不得不遍历源对象图的成员。使用此方法,可以精确调整要处理的子成员以及如何手动处理自定义类型。

所以你去吧。两种方法。在我看来,两者都是有效的。

23赞 2 revs, 2 users 67%opensas #23

Lodash 有一个很好的 _.cloneDeep(value) 方法:

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
5赞 Daniel Lorenz #24

有很多答案,但没有一个给出我需要的预期效果。我想利用jQuery的深度复制的强大功能...但是,当它遇到数组时,它只是复制对数组的引用并深度复制其中的项。为了解决这个问题,我做了一个很好的小递归函数,它会自动创建一个新数组。

(如果您愿意,它甚至可以检查 kendo.data.ObservableArray!但是,如果您希望数组再次可观察,请确保调用 kendo.observable(newItem)。

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

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;
        }
    }
}
5赞 2 revsCody #25

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

var o = {};

var oo = Object.create(o);

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

观看旧版浏览器!

评论

0赞 Hola Soy Edu Feliz Navidad 5/3/2014
第二种方式需要一个原型,我更喜欢第一种方式,即使它不是性能最好的方式,因为你可以与很多浏览器和 Node JS 一起使用。
0赞 user420667 3/30/2016
这很酷,而且假设 o 有一个属性 a。现在 oo.hasOwnProperty('a')?
0赞 Cody 3/30/2016
不,o 本质上是作为 oo 的原型添加的。这可能不是理想的行为,这就是为什么我编写的 99.9% 的 serialize() 方法都使用上面提到的 JSON 方法。我基本上总是使用 JSON,并且在使用 Object.create 时还有其他注意事项。
1赞 kator 6/22/2018
不,看这个代码! 不需要创建对象的副本,而是使用旧对象作为克隆的原型Object.create
4赞 3 revs, 2 users 89%weeger #26

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

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

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

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

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

评论

1赞 AymKdn 5/26/2017
您可能希望在测试时添加不覆盖 的现有成员。例如,我们希望找到 ,但通过您的代码,我们发现:|| typeof target[name] !== "undefined"if (target === to_add[name]) { continue; }targetvar a={hello:"world", foo:"bar"}; var b={hello:"you"}; extend(b, a);b => {hello:"you", foo:"bar"}b => {hello:"world", foo:"bar"}
0赞 weeger 6/16/2017
就我而言,我确实希望覆盖现有成员,因此当前的行为是这种用法的正确行为。但感谢您添加这个有用的建议。
6赞 2 revs, 2 users 77%Robin Whittleton #27

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

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

在撰写本文时,浏览器中的 Firefox 34 支持仅限于 Firefox 34,因此它还不能在生产代码中使用(当然,除非您正在编写 Firefox 扩展)。

评论

3赞 Oriol 1/25/2015
你的意思是.您当前的代码等同于 。obj2 = Object.assign({}, obj1)obj2 = obj1
6赞 Josh from Qaribou 2/6/2017
这是一个浅克隆。 现在也是。const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456;o1.a.deep === 456
3赞 Redu 4/5/2017
Object.assign()不用于克隆嵌套对象。
4赞 basickarl 4/21/2017
哇,又一个没用的答案。摘自 MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...Warning for Deep Clone - For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.
135赞 20 revs, 3 users 92%tim-montague #28

按性能分类的深度拷贝:

根据基准
https://www.measurethat.net/Benchmarks/Show/17502/0/deep-copy-comparison 从最好到最差排名

  • spread 运算符(基元数组 - 仅限)...
  • slice()(基元数组 - 仅限)
  • splice(0)(基元数组 - 仅限)
  • concat()(基元数组 - 仅限)
  • JSON.parse(JSON.stringify())(原始数组和文字数组 - 仅限)
  • 自定义函数,如下所示(任何数组)
  • Lodash's(任意数组)_.cloneDeep()
  • jQuery(任意数组)$.extend()
  • 下划线(原始数组和文字数组 - 仅限)_.clone()

哪里:

  • 基元 = 字符串、数字和布尔值
  • literals = 对象字面量 , 数组字面量{}[]
  • any = 基元、文本和原型

深度复制基元数组:

let arr1a = [1, 'a', true];

要仅使用基元(即数字、字符串和布尔值)深度复制数组,可以使用重新赋值、 、 和下划线。slice()concat()clone()

点差表现最快的地方:

let arr1b = [...arr1a];

哪里有比和更好的性能slice()splice(0)concat()

let arr1d = arr1a.slice();
let arr1c = arr1a.splice(0);
let arr1e = arr1a.concat();

深层复制基元和对象文字数组:

let arr2a = [1, 'a', true, {}, []];
let arr2b = JSON.parse(JSON.stringify(arr2a));

深层复制基元、对象文本和原型的数组:

let arr3a = [1, 'a', true, {}, [], new Object()];

编写自定义函数(性能比jQuery快):$.extend()

function copy(aObject) {
  // Prevent undefined objects
  // if (!aObject) return aObject;

  let bObject = Array.isArray(aObject) ? [] : {};

  let value;
  for (const key in aObject) {

    // Prevent self-references to parent object
    // if (Object.is(aObject[key], aObject)) continue;
    
    value = aObject[key];

    bObject[key] = (typeof value === "object") ? copy(value) : value;
  }

  return bObject;
}

let arr3b = copy(arr3a);

或者使用第三方实用程序函数:

let arr3c = $.extend(true, [], arr3a); // jQuery
let arr3d = _.cloneDeep(arr3a); // Lodash

评论

2赞 mikiqex 6/29/2021
使用 for-in 循环,您应该使用它来排除继承的属性。我使用(可能更快)plain for 循环。hasOwnPropertyObject.keys
3赞 tim-montague 7/1/2021
在深层复制中,您难道不想复制继承的属性吗?另请注意,调用该方法会为每个键创建性能影响(将函数调用推入和推出堆栈,并执行方法代码)。hasOwnProperty
1赞 zkldi 11/8/2022
其中大多数根本不是深度克隆,因此将它们相互基准比较是没有意义的。
1赞 S.Serpooshan 4/5/2023
在我的测试中,尽管它们都是浅拷贝,但比 快得多slice()splice(0)
0赞 tim-montague 4/5/2023
@S.Serpooshan - 同意,答案已根据以下基准更新(measurethat.net/Benchmarks/Show/17502/0/deep-copy-comparison)
3赞 3 revsSteven Vachon #29

用于获取 的 和 支持,并使用循环获取可枚举的键:Object.create()prototypeinstanceoffor()

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

评论

0赞 lepe 10/20/2015
很好的答案!我认为这是保持 setter 和 getter 完好无损的唯一方法之一。这解决了我的问题。谢谢!(参见:stackoverflow.com/questions/33207028/...)
0赞 Jeremy 4/8/2016
难道您不想使用 clone = ,而不是继承您也覆盖的属性吗?Object.create(Object.getPrototypeOf(source))
0赞 Steven Vachon 4/12/2016
有趣。但是,使用 on 会将其索引变成新 .getPrototypeOfArrayObject
0赞 zkldi 11/8/2022
不适用于数组,因为它用于尝试创建数组。Object.create()
2赞 Tristian #30

需要新的浏览器,但是......

让我们扩展原生对象并得到一个真正的 .extend();

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() 的任何代码之前弹出它即可。

例:

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

评论

1赞 Josh from Qaribou 2/6/2017
突变原型通常被认为是不好的做法,唯一的例外是垫片。
33赞 3 revs, 3 users 71%nathan rogers #31

下面创建同一对象的两个实例。我找到了它,目前正在使用它。它简单易用。

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
18赞 3 revs, 3 users 68%andrew #32

仅当可以使用 ECMAScript 6转译器时。

特征:

  • 复制时不会触发 getter/setter。
  • 保留 getter/setter。
  • 保留原型信息。
  • 适用于对象文字函数式 OO 编写风格。

法典:

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

评论

1赞 Zortext 10/15/2021
对于以下数据类型有问题Date
0赞 CherryDT 11/1/2021
如果与具有原型的对象一起使用,这将创建对同一对象实例的引用(而不是深层复制它),因为是 false。nullObject.create(null) instanceof Object
16赞 3 revs, 3 users 51%Buzinas #33

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

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(obj), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v)
    return v;
  })
}

// usage:
var original = {
 a: [1, null, undefined, 0, {a:null}, new Date()],
 b: {
   c(){ return 0 }
 }
}

var cloned = clone(original)

console.log(cloned)

评论

0赞 vsync 10/24/2020
不完全是 100% 克隆
169赞 3 revs, 3 users 95%Eugene Tiurin #34

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

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

评论

104赞 mwhite 3/9/2016
这不会递归复制,因此并不能真正为克隆对象的问题提供解决方案。
7赞 Nico 5/10/2016
这种方法有效,尽管我测试了一些,并且 _.extend({}, (obj)) 是迄今为止最快的:例如,比 JSON.parse 快 20 倍,比 Object.assign 快 60%。它很好地复制了所有子对象。
18赞 Meirion Hughes 6/8/2016
@mwhite克隆和深度克隆之间是有区别的。这个答案实际上确实克隆了,但它不是深度克隆。
4赞 johannes_lalala 3/4/2021
问题是关于递归副本的。Object.assign 以及给定的自定义赋值不会以递归方式复制
0赞 zkldi 11/8/2022
这个答案不是深度克隆,也不是问题所在。
7赞 4 revs, 3 users 94%Bodhi Hu #35

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

/*
 * Trampoline to avoid recursion in JavaScript, see:
 *     https://www.integralist.co.uk/posts/functional-recursive-javascript-programming/
 */
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;
};

评论

3赞 rich remer 2/13/2016
尾部调用递归实际上在大多数 JavaScript 实现中都非常有效,并且需要在 ES6 中进行优化。
0赞 Bodhi Hu 4/22/2016
嗨,我当时做了一个小测试,当目标对象变得复杂时,调用堆栈很容易溢出,尽管我没有做任何笔记,希望在 es6 中这将是一个很大的操作。
2赞 Yichong 1/11/2017
堆栈很容易溢出,可能是因为循环引用。
-5赞 5 revs, 4 users 63%Barry Staes #36

使用当今的 JavaScript 克隆对象:ECMAScript 2015(以前称为 ECMAScript 6)

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 到 JavaScript 的编译器(如 Babel)来输出 JavaScript 代码的 ECMAScript 5 版本。

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

评论

34赞 Jim Hall 3/26/2016
这并不能解决深度合并问题。gist.github.com/jimbol/5d5a3e3875c34abcf60a
25赞 basickarl 4/21/2017
哇,这个答案太错了。这两种方法都对一个级别进行浅层复制。任何看到这个答案的人,请继续前进。
19赞 3 revs, 2 users 62%Dan Atkinson #37

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

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

评论

0赞 Galvani 9/21/2016
或者它可能以与 jQiery extend 相同的方式使用:angular.extend({},obj);
2赞 Dan Atkinson 10/16/2016
@Galvani:需要注意的是,和都是浅拷贝。 是深层副本。jQuery.extendangular.extendangular.copy
6赞 5 revs, 4 users 49%nem035 #38

单行 ECMAScript 6 解决方案(未处理 Date/Regex 等特殊对象类型):

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

评论

5赞 coatless 7/18/2016
请在代码块旁边提供解释,以便有类似问题的其他人可以轻松理解正在发生的事情。就目前而言,这个问题处于低质量的帖子审查队列中。
1赞 Paritosh 7/18/2016
请编辑更多信息。不鼓励使用纯代码和“试试这个”答案,因为它们不包含可搜索的内容,并且不解释为什么有人应该“试试这个”。
8赞 3 revs, 2 users 92%user3071643 #39

我使用 npm 克隆库。显然,它也可以在浏览器中使用。

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

let a = clone(b)
12赞 azerafati #40

AngularJS的

好吧,如果你使用的是angular,你也可以这样做

var newObject = angular.copy(oldObject);
2赞 2 revsSAlidadi #41

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

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

评论

0赞 vsync 10/24/2020
通过简单的测试完全失败[1,2]
3赞 Ashutosh Jha #42

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

ES6 (美):

_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 (美) :

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

}

例如

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

评论

2赞 Soldeplata Saketos 10/30/2017
这不处理数组,数组实际上也是对象。
0赞 zkldi 11/8/2022
不适用于数组或 null,因为 typeof 检查不正确。
3赞 Ihor Pavlyk #43

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

83赞 13 revs, 4 users 72%Alireza #44

克隆对象在 JS 中一直是一个问题,但在 ES6 之前,我在下面列出了在 JavaScript 中复制对象的不同方法,假设您有下面的对象并希望拥有它的深度副本:

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

有几种方法可以在不更改原点的情况下复制此对象:

  1. ES5+,使用一个简单的函数为您完成复制:

    function deepCopyObj(obj) {
        if (null == obj || "object" != typeof obj) return obj;
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0, len = obj.length; i < len; i++) {
                copy[i] = deepCopyObj(obj[i]);
            }
            return copy;
        }
        if (obj instanceof Object) {
            var copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) copy[attr] = deepCopyObj(obj[attr]);
            }
            return copy;
        }
        throw new Error("Unable to copy obj this object.");
    }
    
  2. ES5+,使用 和 .JSON.parseJSON.stringify

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

    var deepCopyObj = angular.copy(obj);
    
  4. j查询:

    var deepCopyObj = jQuery.extend(true, {}, obj);
    
  5. 下划线.js & Lodash:

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

希望这些对您有所帮助...

3赞 Redu #45

在不触及原型继承的情况下,您可以按如下方式深化孤对象和数组;

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

6赞 Daniel Barde #46

Lodash 有一个功能可以为您处理这个问题。

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

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

在此处阅读文档。

评论

0赞 tommyalvarez 7/12/2017
我最终使用了它,因为 JSON.parse(JSON.stringify(obj)) 不保留原始对象原型
0赞 RobbyD 7/20/2017
这是我的首选答案。除了我使用 Lodash 的合并之外,保持深度和浅层复制的语法在某种程度上一致。//Deep copy: _.merge({},foo) //Shallow copy: Object.Assign({}, foo)
14赞 prograhammer #47

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

以下是快速参考的功能:

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
}

评论

2赞 Luis 8/22/2017
我喜欢这种方法,但它不能正确处理日期;考虑添加类似 after checking for null 'if(o instanceof Date) return new Date(o.valueOf());
0赞 Harry 3/18/2018
循环引用时崩溃。
0赞 WBT 1/15/2019
在最新的稳定版 Firefox 中,这比该 Jsben.ch 链接的其他策略要长得多,一个数量级或更多。它在错误的方向上击败了其他人。
1赞 JTeam #48

由于这个问题在参考内置功能(例如 Object.assign 或自定义代码)时得到了很多关注和答案,因此我想分享一些深度克隆库,

1. 克隆

npm install --savedev esclone https://www.npmjs.com/package/esclone

ES6 中的使用示例:

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 中的使用示例:

var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)

2. 深拷贝

npm install 深层复制 https://www.npmjs.com/package/deep-copy

例:

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

3. 克隆深度

$ npm install --save clone-deep https://www.npmjs.com/package/clone-deep

例:

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);
5赞 shobhit1 #49

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

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

让我知道你的想法。

18赞 5 revs, 2 users 85%Mayur Agarwal #50

我回答这个问题已经晚了,但我有另一种克隆对象的方法:

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

然后更好更快:

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

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/hasOwnProperty

评论

0赞 Antoniossss 4/26/2018
这很有趣,但是当我运行您的测试时,它实际上告诉我方法 1 是最慢的
0赞 SPG 12/5/2018
和我一样,1号区块是最低的!
0赞 Phoenix 3/13/2021
唯一适合我的解决方案!必须深度克隆包含具有函数属性的其他对象的对象。完善。
0赞 Aykut Kllic 4/16/2021
为什么要设置然后删除它?你为什么不打电话?obj['isActiveClone'] = nullobj.hasOwnProperty(key)
4赞 Julez #51

这是我使用默认值和扩展运算符深度克隆对象的方法ES2015

 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
}

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

评论

0赞 vsync 10/24/2020
旅游对象太幼稚了。它必须有一些 、 函数、日期,而不仅仅是一堆字符串testundefinednull
0赞 zkldi 11/8/2022
不适用于值为 null 的字段,也不适用于值为数组的字段。
3赞 Константин Ван #52

异步对象克隆呢?Promise

async function clone(thingy /**/)
{
    if(thingy instanceof Promise)
    {
        throw Error("This function cannot clone Promises.");
    }
    return thingy;
}

评论

2赞 Константин Ван 8/28/2019
等一下,5位赞成票,它是如何运作的?我自己忘记了,这看起来有悖常理,现在已经过去了一年半。
0赞 Sebi 8/22/2020
不知道它应该做什么,我很困惑:s
0赞 Константин Ван 8/23/2020
解析克隆的吗?我对此表示怀疑,超越了我自己。Promise.resolve(value)value
0赞 zkldi 11/8/2022
这根本不做任何克隆。事实上,它什么也没做。这相当于 。const newObj = obj
4赞 Steve Griffith #53

纵观这一长串答案,除了我知道的答案外,几乎所有的解决方案都已涵盖。这是深度克隆对象的 VANILLA JS 方法列表。

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

  2. 通过带有 pushState 或 replaceState 的 history.state

  3. Web 通知 API,但这有一个缺点,即要求用户提供权限。

  4. 在对象中执行自己的递归循环以复制每个级别。

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

评论

0赞 Jack G 4/7/2018
所有这些都已经在答案或评论中进行了转换。不过,如果您为每个代码示例提供独特的代码示例,我会对此进行投票。
6赞 codeMonkey #54

ES 2017 示例:

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

评论

0赞 Takeshi Tokugawa YD 6/10/2018
谢谢你的回答。我尝试了你的方法,但不幸的是,它不起作用。因为这可能是我这边的某种错误,我请你检查我在 JSFiddle 中的示例,如果这是我这边的一些错误,我会投票支持你的答案。
0赞 codeMonkey 6/10/2018
当我拉你的小提琴时,我得到和.这难道不是你所期望的吗?{ foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3}{ foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4}
0赞 Takeshi Tokugawa YD 6/10/2018
你粘贴的就是我所期望的。我不明白为什么,但我看到了两者和控制台......(截图fooBaz: 44testObj2testObj3)
3赞 Nikita Malyschkin 1/17/2019
这不是一个深层副本,而是一个浅层副本。 @GurebuBokofu
10赞 Parabolord #55

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

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

这比该方法快 40 倍左右。JSON.parse...

评论

2赞 Parabolord 8/16/2018
伪代码是:对于每个键,将其值分配给新对象(浅拷贝)中的同一键。但是,如果该值的类型(不能浅拷贝),则该函数会以递归方式调用自身,并将该值作为参数。Object
1赞 zenw0lf 8/25/2019
太糟糕了,当值是数组时,它不能正常工作。但是,修改以使其适用于这种情况应该不会太难。
0赞 medBouzid 10/19/2020
TypeError:无法读取未定义的属性“构造函数”
0赞 zkldi 11/8/2022
不适用于 null,因为它试图访问 ,不适用于数组,因为它将它们变成了对象。null.constructor
3赞 2 revs, 2 users 90%Vikram K #56

对于浅楷贝,ECMAScript2018 标准中引入了一种简单易用的方法。它涉及使用点差运算符

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

let objClone = { ...obj };

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

这种技术非常简单明了。我认为这是一劳永逸地解决这个问题的真正最佳实践。

评论

1赞 Taugenichts 6/27/2018
在 objClone 中更新 e 仍将在 obj 中更新 e。这仍然只是一个浅薄的副本。该问题明确要求进行深度克隆。
0赞 mickro 6/28/2018
@Taugenichts......你测试过吗?该方法非常有效。Spread_syntax部分Spread in object literals
1赞 Taugenichts 6/28/2018
是的,我测试过了。运行以下代码:objClone.e[4] = 5;控制台.log(obj.e);您将看到 obj.e 正在更新
2赞 Lupus Ossorum 6/30/2018
因为两者都存储在不同的位置,这仅仅意味着它至少是一个浅拷贝。看在哪里,被存储;您会发现它们存储在同一个位置。obj.eobjClone.e
1赞 Vikram K 7/10/2018
非常感谢,guys@LupusOssorum @Taugenichts指出这一点。我自己测试了一下,发现了你们在这里发现的东西。但是你知道为什么数组仍然不改变内存吗,尽管ECMA2018吹嘘这是一个功能。
4赞 Jinu Joseph Daniel #57

希望这会有所帮助。

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

103赞 4 revs, 2 users 99%Tính Ngô Quang #58

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

1. 使用 JSON.parse(JSON.stringify(object));

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.使用创建方法

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

3.使用 Lo-Dash 的 _.cloneDeep 链接 lodash

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() 方法

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 }  

但是错了

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.使用下划线.js _.clone链接下划线.js

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 }  

但是错了

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.)

JSBEN的。CH 性能基准测试游乐场 1~3 http://jsben.ch/KVQLd Performance Deep copying objects in JavaScript

评论

1赞 kenanyildiz90 12/10/2019
嘿,你最后一个例子是错误的。在我看来,你必须使用_clone而不是_cloneDeep错误的例子。
1赞 Toivo Säwén 12/18/2019
这个创建的方法 (2.) 不适用于数组,是吗?
1赞 Frank Fajardo 3/7/2021
方法 #2 容易受到原型污染的影响,类似于 lodash 的 .它不应该复制 if ,也不应该复制 if 。defaultsDeep(i === '__proto__')(i === 'constuctor' && typeof obj[i] === 'function')
2赞 Prasanth Jaya #59

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

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

现在 A 将是您在没有任何引用的情况下新克隆的 obj。

2赞 2 revsshunryu111 #60

如果您发现自己经常做这类事情(例如 - 创建撤消重做功能),可能值得研究 Immutable.js

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?editors=1111

12赞 chandan gupta #61

在 JavaScript 中,你可以编写如下方法deepCopy

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

评论

1赞 Frank Fajardo 3/7/2021
这容易受到全球物体污染的影响。它不应该复制 if 或 ifprop(prop === 'constuctor' && typeof src[prop] === 'function')(prop === '__proto__')
0赞 5 revs, 2 users 96%Eternal Darkness #62

如何将对象的与其合并?

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).ca.b.cdeepClone(a).b

评论

0赞 zkldi 11/8/2022
不是深度克隆。只做一层深度。由于检查类型不正确,也不适用于数组或任何具有该值的东西。null
1赞 3 revs, 2 users 71%shakthi nagaraj #63
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 对象?

2赞 2 revsShidersz #64

随着新方法Object.fromEntries()的提出,该方法在某些浏览器的较新版本上受支持(参考)。我想为下一个递归方法做出贡献:

const obj = {
  key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
  key2: {key21: "key21", key22: "key22"},
  key3: "key3",
  key4: [1,2,3, {key: "value"}]
}

const cloneObj = (obj) =>
{
    if (Object(obj) !== obj)
       return obj;
    else if (Array.isArray(obj))
       return obj.map(cloneObj);

    return Object.fromEntries(Object.entries(obj).map(
        ([k,v]) => ([k, cloneObj(v)])
    ));
}

// Clone the original object.
let newObj = cloneObj(obj);

// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";

// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

4赞 2 revsKamyar #65

我的情况有点不同。我有一个带有嵌套对象和函数的对象。因此,不是我问题的解决方案。使用第三方库对我来说也不是一种选择。Object.assign()JSON.stringify()

因此,我决定制作一个简单的函数,使用内置方法来复制具有文本属性、嵌套对象和函数的对象。

let deepCopy = (target, source) => {
    Object.assign(target, source);
    // check if there's any nested objects
    Object.keys(source).forEach((prop) => {
        /**
          * assign function copies functions and
          * literals (int, strings, etc...)
          * except for objects and arrays, so:
          */
        if (typeof(source[prop]) === 'object') {
            // check if the item is, in fact, an array
            if (Array.isArray(source[prop])) {
                // clear the copied referenece of nested array
                target[prop] = Array();
                // iterate array's item and copy over
                source[prop].forEach((item, index) => {
                    // array's items could be objects too!
                    if (typeof(item) === 'object') {
                        // clear the copied referenece of nested objects
                        target[prop][index] = Object();
                        // and re do the process for nested objects
                        deepCopy(target[prop][index], item);
                    } else {
                        target[prop].push(item);
                    }
                });
            // otherwise, treat it as an object
            } else {
                // clear the copied referenece of nested objects
                target[prop] = Object();
                // and re do the process for nested objects
                deepCopy(target[prop], source[prop]);
            }
        }
    });
};

下面是一个测试代码:

let a = {
    name: 'Human', 
    func: () => {
        console.log('Hi!');
    }, 
    prop: {
        age: 21, 
        info: {
            hasShirt: true, 
            hasHat: false
        }
    },
    mark: [89, 92, { exam: [1, 2, 3] }]
};

let b = Object();

deepCopy(b, a);

a.name = 'Alien';
a.func = () => { console.log('Wassup!'); };
a.prop.age = 1024;
a.prop.info.hasShirt = false;
a.mark[0] = 87;
a.mark[1] = 91;
a.mark[2].exam = [4, 5, 6];

console.log(a); // updated props
console.log(b);

对于与效率相关的问题,我相信这是解决我遇到的问题的最简单、最有效的解决方案。我将不胜感激对该算法的任何评论,以使其更有效率。

评论

0赞 zkldi 11/8/2022
此代码将 inside objects 替换为 ,如null{}typeof null === "object"
5赞 KRIPA SHANKAR JHA #66

Object.assign({},sourceObj)仅当对象的属性没有引用类型键时,才克隆对象。 前任

obj={a:"lol",b:["yes","no","maybe"]}
clonedObj = Object.assign({},obj);

clonedObj.b.push("skip")// changes will reflected to the actual obj as well because of its reference type.
obj.b //will also console => yes,no,maybe,skip

因此,对于深度克隆是不可能以这种方式实现的。

最好的解决方案是

var obj = Json.stringify(yourSourceObj)
var cloned = Json.parse(obj);

评论

0赞 vsync 10/24/2020
远非“最好”。也许对于简单的对象。
0赞 zkldi 11/8/2022
这个答案不正确 - 是一个浅薄的副本,不是使用实用程序的正确方法。Object.assign()JsonJSON
3赞 Ankur Kedia #67

这是我的解决方案,无需使用任何库或本机 javascript 函数。

function deepClone(obj) {
  if (typeof obj !== "object") {
    return obj;
  } else {
    let newObj =
      typeof obj === "object" && obj.length !== undefined ? [] : {};
    for (let key in obj) {
      if (key) {
        newObj[key] = deepClone(obj[key]);
      }
    }
    return newObj;
  }
}

评论

0赞 Ian 8/22/2019
小心... ->递归错误。const o = {}; o.a = o; deepClone(o);
0赞 zkldi 11/8/2022
不适用于恰好具有名为 的属性的对象,例如 ,因为它不能正确推断数组。此外,变成 ,因为它没有正确检查 null。length{ foo: "bar", length: "10cm" }{ foo: null }{ foo: {} }