这是在ES6中克隆对象的好方法吗?


155

谷歌搜索“ javascript克隆对象”会带来一些非常奇怪的结果,其中有些绝望地过时,而有些则过于复杂,难道不是那么简单:

let clone = {...original};

这有什么问题吗?


1
这不是合法的ES6。但是,如果不是的话,这不是克隆:您的克隆和原始属性现在都指向相同的事物。例如,original = { a: [1,2,3] }给您一个克隆,其clone.a字面为original.a。通过修改cloneoriginal修改同一件事,所以不,这很不好=)
Mike'Pomax'Kamermans

2
@AlbertoRivera这是一种有效的JavaScript,因为它是第2阶段的提案,很可能是JavaScript标准的未来添加。
Frxstrem '16

@Frxstrem,问题是关于ES6的,这不是有效的JavaScript =)
Mike'Pomax'Kamermans

3
浅克隆还是深克隆?
费利克斯·克林

2
没错,它不是有效的ES6,它是有效的ES9developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…–
mikemaccana

Answers:


240

对于浅克隆是有益。该物体传播是ECMAScript的2018的标准组成部分

要进行深克隆,您将需要其他解决方案

const clone = {...original} 浅表克隆

const newobj = {...original, prop: newOne} 以不变的方式将另一个道具添加到原始对象并存储为新对象。


18
但是,这不只是一个浅表克隆吗?例如,属性不是递归克隆的,是吗?因此,original.innerObject === clone.innerObject并更改original.innerObject.property将更改clone.innerObject.property。
milanio '18 -4-3

18
是的,这是一个浅表克隆。如果您想进行深度克隆,则必须使用JSON.parse(JSON.stringify(input))
Mark Shust在M.academy,

8
/!\ JSON.parse(JSON.stringify(input))弄乱了日期,未定义,...这不是克隆的灵丹妙药!参见:maxpou.fr/immutability-js-without-library
Guillaume

1
那么hack JSON.stringify()/ JSON.parse()是否真的是在ES6中深度克隆对象的推荐方法?我一直看到它推荐。令人不安。
Solvitieg

3
@MarkShust JSON.parse(JSON.stringify(input))将不起作用,因为如果存在 functionsinfinity作为值,它将仅null在其位置分配。仅当值简单literals且不是时,它才起作用functions
反斜杠

65

编辑:发布此答案时,{...obj}语法在大多数浏览器中不可用。如今,您应该可以很好地使用它(除非您需要支持IE 11)。

使用Object.assign。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

但是,这不会产生深层次的克隆。到目前为止,还没有原生的深度克隆方法。

编辑:正如@Mike'Pomax'Kamermans在评论中提到的那样,您可以使用以下方法深度克隆简单的对象(即没有原型,函数或循环引用) JSON.parse(JSON.stringify(input))


19
如果您的对象是真实的对象文字,并且纯粹是数据,JSON.parse(JSON.stringify(input))则只有一种,在这种情况下是适当的深克隆。但是,当原型,函数或循环引用开始起作用时,该解决方案将不再起作用。
Mike'Pomax'Kamermans

@ Mike'Pomax'Kamermans是的。吸气剂和塞脂剂的功能丧失是可怕的,但是……
Alberto Rivera

如果您需要通用功能来深克隆任何对象,请查看stackoverflow.com/a/13333781/560114
马特·布朗

1
现在有一种方法可以本地进行深克隆
Dan Dascalescu

1
@DanDascalescu即使是实验性的,它看起来也很有希望。谢谢(你的)信息!
Alberto Rivera

4

如果您使用的方法不适用于涉及数据类型(例如Date)的对象,请尝试以下操作

进口 _

import * as _ from 'lodash';

深度克隆对象

myObjCopy = _.cloneDeep(myObj);

只要import _ from 'lodash';是足够的。但是+1代表“不要重新发明轮子”答案。
rustyx

lodash blo肿。真的不需要仅仅为了简单的深层复制就输入lodash。这里还有许多其他解决方案。对于寻求构建精益应用程序的Web开发人员来说,这是一个非常糟糕的答案。
杰森·赖斯

3

如果您不想使用json.parse(json.stringify(object)),则可以递归创建键值副本:

function copy(item){
  let result = null;
  if(!item) return result;
  if(Array.isArray(item)){
    result = [];
    item.forEach(element=>{
      result.push(copy(element));
    });
  }
  else if(item instanceof Object && !(item instanceof Function)){ 
    result = {};
    for(let key in item){
      if(key){
        result[key] = copy(item[key]);
      }
    }
  }
  return result || item;
}

但是最好的方法是创建一个可以自我返回其副本的类

class MyClass{
    data = null;
    constructor(values){ this.data = values }
    toString(){ console.log("MyClass: "+this.data.toString(;) }
    remove(id){ this.data = data.filter(d=>d.id!==id) }
    clone(){ return new MyClass(this.data) }
}

2

@marcel给出的答案之后,我发现克隆的对象上仍然缺少一些功能。例如

function MyObject() {
  var methodAValue = null,
      methodBValue = null

  Object.defineProperty(this, "methodA", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    },
    enumerable: true
  });

  Object.defineProperty(this, "methodB", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    }
  });
}

在MyObject上我可以克隆methodA的位置,但不包括methodB。发生这种情况是因为缺少

enumerable: true

这意味着它没有出现在

for(let key in item)

相反,我切换到

Object.getOwnPropertyNames(item).forEach((key) => {
    ....
  });

其中将包含不可枚举的密钥。

我还发现原型(proto)没有被克隆。为此,我最终使用

if (obj.__proto__) {
  copy.__proto__ = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
}

PS:令人沮丧的是我找不到内置函数来执行此操作。


1

您也可以这样做

let copiedData = JSON.parse(JSON.stringify(data));

-1
We can do that with two way:
1- First create a new object and replicate the structure of the existing one by iterating 
 over its properties and copying them on the primitive level.

let user = {
     name: "John",
     age: 30
    };

    let clone = {}; // the new empty object

    // let's copy all user properties into it
    for (let key in user) {
      clone[key] = user[key];
    }

    // now clone is a fully independant clone
    clone.name = "Pete"; // changed the data in it

    alert( user.name ); // still John in the original object

2- Second we can use the method Object.assign for that 
    let user = { name: "John" };
    let permissions1 = { canView: true };
    let permissions2 = { canEdit: true };

    // copies all properties from permissions1 and permissions2 into user
    Object.assign(user, permissions1, permissions2);

  -Another example

    let user = {
      name: "John",
      age: 30
    };

    let clone = Object.assign({}, user);
It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

但是Object.assign()不会创建深层克隆

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

为了解决这个问题,我们应该使用克隆循环来检查user [key]的每个值,如果它是一个对象,则还要复制其结构。这就是所谓的“深度克隆”。

有一种用于深度克隆的标准算法(结构化克隆算法)可以处理上述情况和更复杂的情况。为了避免重新发明轮子,我们可以从JavaScript库lodash中使用它的有效实现,该方法称为_.cloneDeep(obj)


-1

上面的所有方法都不能处理嵌套到n个层的对象的深度克隆。我没有检查它在其他方面的性能,但是它简短而简单。

下面的第一个示例显示了对象克隆,使用Object.assign该对象克隆直到第一级。

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

newPerson = Object.assign({},person);
newPerson.skills.lang = 'angular';
console.log(newPerson.skills.lang); //logs Angular

使用以下方法深度克隆对象

var person = {
    name:'saksham',
    age:22,
    skills: {
        lang:'javascript',
        experience:5
    }
}

anotherNewPerson = JSON.parse(JSON.stringify(person));
anotherNewPerson.skills.lang = 'angular';
console.log(person.skills.lang); //logs javascript


JSON.parse /字符串化已经提到,作为一个贫困程度深克隆方法多年。请检查以前的答案以及相关问题。另外,这对于ES6来说并不是新事物。
Dan Dascalescu

@DanDascalescu我知道这一点,我认为将其用于简单对象应该不是问题。其他人也在同一篇文章的答案中甚至在评论中提到了这一点。我认为这不应该被否决。
萨克斯姆(Saksham)

确实-答案中还有“其他人也提到过” JSON.parse / stringify。为什么用相同的解决方案发布另一个答案?
Dan Dascalescu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.