如何正确克隆一个JavaScript对象?

3720
我有一个对象 x。我想将它作为对象 y 复制,使得对 y 的更改不会修改 x。我意识到,复制从内置 JavaScript 对象派生的对象会导致额外的、不必要的属性。这不是问题,因为我要复制的是我自己通过字面量构造的对象。
如何正确地克隆 JavaScript 对象?

34
看这个问题:https://dev59.com/83VD5IYBdhLWcg3wAGiD这个问题是关于如何克隆JavaScript对象的,你需要找到一种高效的方法来实现这个目标。 - Niyaz
289
对于 JSON,我使用 mObj=JSON.parse(JSON.stringify(jsonObject)); - Lord Loh.
78
我不明白为什么没有人建议使用 Object.create(o),它完全满足了作者的要求。 - froginvasion
59
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; 执行完这段代码后,y.deep.key 的值也会变成2,因此 Object.create 不能用于克隆对象。 - Ruben Stolk
23
@r3wt那样做行不通...请先对解决方案进行基本测试再发帖。 - user3275211
显示剩余21条评论
82个回答

3
在我的代码中,我经常定义一个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

3

许多同行在这里提出的深度克隆解决方案 JSON.parse(JSON.stringify(orig_obj)存在几个问题,我发现了以下问题:

  1. 它在复制原始对象时会丢弃值为 undefined 的条目
  2. 如果有一些像 Infinity NaN 等的值,则在复制时它们将被转换为 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);

你需要检查所有顶层属性并判断它们是否为日期类型,但如果不是,你需要进行JSON stringify/parse操作。如果顶层属性是一个包含日期类型的对象怎么办?你的解决方案需要递归地工作,以捕获所有不能被JSON解析的嵌套对象的可能情况。 - A. Levy

2

这里有一种现代化的解决方案,它没有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;
    }, {});
};

2
为了更好地理解对象的复制,这个有价值的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('-----------------------------------')

2

我曾在标量对象的情况下尝试过这个方法,对我来说有效:

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

Regards.


a=1;b=a;alert(++a);alert(b); // still 1 - dandavis

2

不同之处

仅复制顶层: {...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.b.c = "change should be only for objB.b.c but it in objA.b.c"
console.log("objA.a.c second level has Change:\n" + JSON.stringify(objA, false, 2));

如果需要进行深拷贝,请使用structuredClone() 2022或者JSON.parse(JSON.stringify(object))(对于旧的浏览器),这两种方法都很简单易用,无需进行任何hack。

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.b.c = "change should be only for objB.c but it in objA.c"

console.log("objA has no Change:\n" + JSON.stringify(objA, false, 2));


2
使用展开语法进行浅拷贝对象,这意味着嵌套的对象实例都没有被克隆。下面是一个嵌套对象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);


2
使用defaults(历史上专门针对Node.js,但现在可以通过现代JS从浏览器中使用):
import defaults from 'object.defaults';

const myCopy = defaults({}, myObject);

2

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

扩展语法是一种语法糖(在这种情况下用于Object.assign)。这个答案是误导性的。 - Tchakabam
对于快速测试 Object.assign({}, person); 它是完美的。 - Eduardo Mior

1
我提供了一个答案来回答这个问题,因为我没有看到任何本地的、递归实现来解决DOM元素的问题。
问题在于,<element>具有parentchild属性,这些属性链接到具有parentchild值的其他元素,这些值指向原始的<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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接