Это хороший способ клонировать объект в ES6?

155

Поиск в Google для «javascript clone object» приносит действительно странные результаты, некоторые из них безнадежно устарели, а некоторые слишком сложны, не так ли просто, как просто:

let clone = {...original};

Что-то не так с этим?

Дмитрий Фадеев
источник
1
это не законно ES6. Но если это так, то это не клон: и ваш клон, и исходные свойства указывают на одно и то же. Например, original = { a: [1,2,3] }дает вам клон с clone.aбуквально существом original.a. Модификация не с помощью либо cloneили originalмодифицирует одно и то же , так что нет, это плохо =)
Mike «Pomax» Kamermans
2
@AlbertoRivera Это своего рода действительный JavaScript, что это этап 2 предложение о том , вероятно, будет в будущем дополнение к стандарту JavaScript.
Frxstrem
@Frxstrem с вопросом о ES6, это недопустимый JavaScript =)
Майк 'Pomax' Kamermans
3
Мелкое или глубокое клонирование?
Феликс Клинг,
2
Вы правы, это не действительно ES6, это действительно ES9 . developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mikemaccana

Ответы:

240

Это хорошо для мелкого клонирования . Распространение объекта является стандартной частью ECMAScript 2018 .

Для глубокого клонирования вам понадобится другое решение .

const clone = {...original} клонировать

const newobj = {...original, prop: newOne} непременно добавьте другую опору к оригиналу и сохраните как новый объект.

Марк Шуст в М.академии
источник
18
Однако разве это не мелкий клон? То есть свойства не клонируются рекурсивно, не так ли? Следовательно, original.innerObject === clone.innerObject и изменение original.innerObject.property изменит clone.innerObject.property.
Миланио
18
да, это мелкий клон. если вам нужен глубокий клон, которого вы должны использоватьJSON.parse(JSON.stringify(input))
Марк Шуст в M.academy
8
/! \ JSON.parse (JSON.stringify (input)) путает даты, неопределенные, ... Это не серебряная пуля для клонирования! См .: maxpou.fr/immutability-js-without-library
Гийом
1
Итак, действительно ли взлом JSON.stringify () / JSON.parse () действительно рекомендуемый способ глубокого клонирования объекта в ES6? Я продолжаю видеть это рекомендованным. Тревожный.
Solvitieg
3
@MarkShust JSON.parse(JSON.stringify(input))не сработает, потому что если там functionsили infinityкак значения он просто назначит nullна их место. Это будет работать только в том случае, если значения просты, literalsа не functions.
обратный
66

РЕДАКТИРОВАТЬ: Когда этот ответ был опубликован, {...obj}синтаксис был недоступен в большинстве браузеров. В настоящее время, вы должны нормально использовать его (если вам не нужно поддерживать IE 11).

Используйте Object.assign.

https://developer.mozilla.org/en-US/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))это правильный глубокий клон. Однако в тот момент, когда прототипы, функции или циклические ссылки находятся в игре, это решение больше не работает.
Майк 'Pomax' Камерманс
@ Mike'Pomax'Kamermans Это правда. Потеря функциональности для геттеров и сеттеров ужасна, хотя ...
Альберто Ривера
Если вам нужна универсальная функция для глубокого клонирования любого объекта, обратитесь к stackoverflow.com/a/13333781/560114 .
Мэтт Браун
1
Теперь есть способ сделать глубокое клонирование изначально .
Дан Даскалеску
1
@DanDascalescu, хотя это экспериментально, выглядит довольно многообещающе. Спасибо за информацию!
Альберто Ривера
4

Если используемые вами методы плохо работают с объектами, включающими такие типы данных, как Date , попробуйте это

Импортировать _

import * as _ from 'lodash';

Глубокий клон

myObjCopy = _.cloneDeep(myObj);
Шахир Шукур
источник
Просто import _ from 'lodash';достаточно. Но +1 за ответ "не изобретай велосипед".
rustyx
Лодаш раздутый. На самом деле не нужно тянуть в lodash только для простой глубокой копии. Множество других решений здесь. Это очень плохой ответ для веб-разработчиков, которые хотят создать простое приложение.
Джейсон Райс
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));
rafee_que_
источник
-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 (объект) .

Мохамед Эльшахави
источник
-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

Saksham
источник
JSON.parse / stringify упоминается как плохой метод глубокого клонирования в течение многих лет . Пожалуйста, проверьте предыдущие ответы, а также связанные вопросы. Кроме того, это не ново для ES6.
Дан Даскалеску
@DanDascalescu Я знаю это, и я думаю, это не должно быть проблемой, чтобы использовать его для простых объектов. Другие также упоминали об этом в своих ответах в том же посте и даже в комментариях. Я думаю, что это не заслуживает понижения.
Сакшам
Точно - "другие также упомянули" JSON.parse / stringify в своих ответах. Зачем отправлять еще один ответ с тем же решением?
Дан Даскалеску