Це хороший спосіб клонування об’єкта в ES6?


155

Гуглінг для "об’єкта клонування JavaScript" приносить деякі дійсно дивні результати, деякі з них безнадійно застаріли, а деякі просто занадто складні, чи не так просто, як просто:

let clone = {...original};

Чи щось з цим не так?


1
це не є законним ES6. Але якщо це було, це не клон: і ваш клон, і оригінальні властивості вказують на одне і те ж. Наприклад, original = { a: [1,2,3] }дає клон з clone.aбуквальним буттям original.a. Модифікація через cloneабо originalзмінює те саме , так що ні, це погано =)
Майк 'Pomax' Камерманс

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

18
так, це дрібний клон. якщо ви хочете глибокого клону, який ви повинні використатиJSON.parse(JSON.stringify(input))
Марк Шуст, М.Академі,

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

EDIT: Коли ця відповідь була розміщена, {...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 }

Однак це не зробить глибокого клону. На сьогодні не існує рідного способу глибокого клонування.

EDIT: Як згадується у коментарях @Mike 'Pomax' Kamermans, ви можете глибоко клонувати прості об’єкти (тобто відсутні прототипи, функції або кругові посилання), використовуючи JSON.parse(JSON.stringify(input))


19
Є один, за умови, що ваш об'єкт є справжнім об'єктом, буквальним, і суто даними, і в цьому випадку JSON.parse(JSON.stringify(input))є належним глибоким клоном. Однак, на даний момент прототипи, функції або циркулярні посилання грають, це рішення більше не працює.
Майк 'Помакс' Камерманс

@ Mike'Pomax'Kamermans Це правда. Втрата функціоналу для жителів та сетерів жахлива, хоча ...
Альберто Рівера

Якщо вам потрібна загальна функція, щоб глибоко клонувати будь-який об’єкт, перегляньте stackoverflow.com/a/13333781/560114 .
Метт Браун


1
@DanDascalescu, хоча і експериментальний, виглядає досить перспективно. Дякую за інформацію!
Альберто Рівера

4

Якщо використовувані вами методи не спрацьовують з об'єктами, що включають типи даних, такі як Date , спробуйте це

Імпорт _

import * as _ from 'lodash';

Об'єкт глибокого клонування

myObjCopy = _.cloneDeep(myObj);

Просто import _ from 'lodash';достатньо. Але +1 для відповіді "не винаходити колесо".
rustyx

лодаш роздутий. Дійсно, не потрібно втягуватися в лодаш лише для простої глибокої копії. Тут є багато інших рішень. Це дійсно погана відповідь для веб-розробників, які прагнуть створити пісну програму.
Джейсон Райс

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 я міг клонувати методA, але метод B був виключений. Це сталося тому, що він відсутній

enumerable: true

що означало, що він не з'явився в

for(let key in item)

Натомість я перейшов на

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

який буде включати не перелічувані ключі.

Я також виявив, що прототип ( прото ) не був клонований. Для цього я закінчила використовувати

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

Щоб виправити це, нам слід скористатися циклом клонування, який вивчає кожне значення користувача [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 / stringify вже згадується як поганий метод глибокого клонування роками . Перевірте попередні відповіді, а також відповідні запитання. Крім того, це не є новим для ES6.
Дан Даскалеску

@DanDascalescu Я це знаю, і я думаю, що використовувати його для простих об'єктів не повинно. Інші також згадували про це у своїх відповідях у тому самому дописі і навіть як коментарі. Я думаю, що це не заслуговує протистояння.
Сакшам

Саме так - "інші також згадували" JSON.parse / stringify у своїх відповідях. Навіщо публікувати ще одну відповідь з тим же рішенням?
Дан Даскалеску
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.