Nodejs: як клонувати об'єкт


91

Якщо я клоную масив, я використовую cloneArr = arr.slice()

Я хочу знати, як клонувати об'єкт у nodejs.

Відповіді:


178

Для утиліт та класів, де немає необхідності вичавлювати кожну краплю продуктивності, я часто обманюю і просто використовую JSON для глибокої копії:

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

Це не єдина відповідь чи найелегантніша відповідь; всі інші відповіді слід розглядати як вузькі місця у виробництві. Однак це швидке та брудне рішення, досить ефективне та корисне в більшості ситуацій, коли я клоную простий хеш властивостей.


2
@djechlin Звичайно. Спробуйте: jsfiddle.net/katowulf/E5jC3 (протестовано за допомогою вузла 0.10.11). Він не зможе відновити функції або прототипові дані, але ці значення отримує просто чудово.
Като

13
Це перетворить дати в рядки
backus

4
@Backus А також об'єкти та функції.
Като

2
@Kato, чому циркулярні посилання означають, що немає необхідності робити глибокі копії? Це дві непов’язані речі. Звичайно, можна написати хороший метод клонування для NodeJS, який підтримує кругові посилання і не відмовляється від використання JSON для клонування. github.com/pvorb/node-clone
Самуель Нефф

2
Вже зазначено в коментарях та в описі. Ви не можете клонувати функцію, прототип об’єкта чи інші речі, яких не слід намагатися робити за допомогою загального методу клонування. Для цього вам знадобиться більше одного рядка коду, і, мабуть, все одно не слід клонувати цей мотлох - покладіть метод класу на свій клас, який знає, як обробляти внутрішні дані та робитиnewObj = obj.clone(args...);
Като

35

Object.assign не згадувалося в жодній із наведених вище відповідей.

let cloned = Object.assign({}, source);

Якщо ви використовуєте ES6, ви можете використовувати оператор поширення:

let cloned = { ... source };

Посилання: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/ assign


29
Зауважте, що обидва вони є неглибокими клонами, і оператор поширення на об’єктах вимагає node.js 8+.
jlh

1
Як @jlh згадав, це не буде працювати для вкладених об'єктів. Детальніше про проблеми із глибоким клонуванням цього методу ви можете прочитати тут: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Shnd

Як сказав Шнд, ця відповідь є неправильною. Для глибокого клонування нам потрібно використовувати інші альтернативи, оскільки Object.assign () копіює значення властивостей. Якщо вихідне значення є посиланням на об'єкт, воно лише копіює це посилальне значення.
тройська

1
Це не клонує, воно просто посилається на оригінальний об'єкт, тобто, якщо ви щось зміните, воно перейде до майстра
Ейдан Уелч

@AidanWelch Я не думаю, що це правильно. let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Мігель Пинто

20

Існує кілька модулів Node, якщо ви не хочете "прокручувати свої". Це добре виглядає: https://www.npmjs.com/package/clone

Схоже, він обробляє будь-які речі, включаючи циркулярні посилання. Зі сторінки github :

клонування майстрів клонування об'єктів, масивів, об'єктів дати та об'єктів RegEx. Все клонується рекурсивно, так що ви можете клонувати дати в масивах в об'єктах, наприклад. [...] Циркулярні посилання? Так!


10

Важко зробити загальну, але корисну операцію клонування, оскільки те, що слід клонувати рекурсивно, а що слід просто скопіювати, залежить від того, як повинен працювати конкретний об’єкт.

Щось, що може бути корисним, це

function clone(x)
{
    if (x === null || x === undefined)
        return x;
    if (typeof x.clone === "function")
        return x.clone();
    if (x.constructor == Array)
    {
        var r = [];
        for (var i=0,n=x.length; i<n; i++)
            r.push(clone(x[i]));
        return r;
    }
    return x;
}

У цьому коді логіка така

  • у випадку nullабо undefinedпросто повернути те саме (потрібен особливий випадок, оскільки помилка спроби з’ясувати, чи є cloneметод)
  • чи має об'єкт a clone метод? то використовуйте це
  • це об’єкт масивом? потім виконайте рекурсивну операцію клонування
  • в іншому випадку просто поверніть те саме значення

Ця функція клонування повинна дозволяти легко реалізовувати власні методи клонування ... наприклад

function Point(x, y)
{
    this.x = x;
    this.y = y;

    ...
}

Point.prototype.clone = function()
{
    return new Point(this.x, this.y);
};



function Polygon(points, style)
{
    this.points = points;
    this.style = style;

    ...
}

Polygon.prototype.clone = function()
{
    return new Polygon(clone(this.points),
                       this.style);
};

Коли в об’єкті ви знаєте, що правильна операція клонування для певного масиву - це лише неглибока копія, тоді ви можете зателефонувати values.slice()замість clone(values).

Наприклад, у наведеному вище коді я чітко вимагаю, щоб клонування об'єкта багатокутника клонувало точки, але поділяло той самий об'єкт стилю. Якщо я хочу замість цього клонувати об'єкт стилю, я можу просто передати його clone(this.style).


1
+1 за згадку про те, що об’єкти повинні .cloneсамі реалізовувати метод. Це найкращий спосіб боротьби з клонуванням об’єктів.
Райнос,

3
if (x.clone)повинно бутиif (typeof x.clone === 'function')
Янік Рошон

@YanickRochon: Дякую, виправлено. Вибачте, я якось раніше цього коментаря не помічав ...
6502,

9

Не існує власного методу клонування об'єктів. Підкреслюйте знаряддя, _.cloneщо представляють собою неглибокий клон.

_.clone = function(obj) {
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

Він або нарізає його, або розширює.

Ось _.extend

// extend the obj (first parameter)
_.extend = function(obj) {
  // for each other parameter
  each(slice.call(arguments, 1), function(source) {
    // loop through all properties of the other objects
    for (var prop in source) {
      // if the property is not undefined then add it to the object.
      if (source[prop] !== void 0) obj[prop] = source[prop];
    }
  });
  // return the object (first parameter)
  return obj;
};

Розширення просто перебирає всі елементи та створює новий об’єкт з елементами в ньому.

Ви можете розгорнути свою власну наївну реалізацію, якщо хочете

function clone(o) {
  var ret = {};
  Object.keys(o).forEach(function (val) {
    ret[val] = o[val];
  });
  return ret;
}

Є вагомі причини уникати глибокого клонування, оскільки закриття не можна клонувати.

Я особисто задав питання, deep cloning objects beforeі висновок, до якого я прийшов, полягає в тому, що ти просто не робиш цього.

Моя рекомендація - використовувати underscoreі це _.cloneметод для неглибоких клонів


9

Для дрібної копії мені подобається використовувати шаблон зменшення (зазвичай у модулі або подібному), наприклад:

var newObject = Object.keys(original).reduce(function (obj, item) {
    obj[item] = original[item];
    return obj;
},{});

Ось jsperf для декількох варіантів: http://jsperf.com/shallow-copying


простий, елегантний, блискучий. Я з нетерпінням чекаю Ваших інших внесків SO _ _ ^
Дякую

7

Старе питання, але є більш елегантна відповідь, ніж те, що пропонувалось дотепер; використовуйте вбудований utils._extend:

var extend = require("util")._extend;

var varToCopy = { test: 12345, nested: { val: 6789 } };

var copiedObject = extend({}, varToCopy);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 } }

Зверніть увагу на використання першого параметра з порожнім об’єктом {} - це говорить про те, що скопійовані об’єкти потрібно скопіювати в новий об’єкт. Якщо ви використовуєте існуючий об'єкт як перший параметр, тоді другий (і всі наступні) параметри будуть скопійовані в глибину через першу змінну параметра.

Використовуючи наведені вище приклади змінних, ви також можете зробити це:

var anotherMergeVar = { foo: "bar" };

extend(copiedObject, { anotherParam: 'value' }, anotherMergeVar);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 }, anotherParam: 'value', foo: 'bar' }

Дуже зручна утиліта, особливо там, де я звик розширювати свої функції в AngularJS та jQuery.

Сподіваюся, це допомагає комусь іншому; перезаписування посилань на об’єкти - це біда, і це вирішує це щоразу!


1
Тепер використання атрибута _extend знецінено. Замість цього використовуйте Object.assign (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MacroMan

7

Ви також можете використовувати лодаш . Він має методи clone та cloneDeep .

var _= require('lodash');

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

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);

1

Залежно від того, що ви хочете зробити з вашим клонованим об'єктом, ви можете використовувати прототипний механізм успадкування javascript і досягти дещо клонованого об'єкта за допомогою:

var clonedObject = Object.create(originalObject);

Тільки пам’ятайте, що це не повноцінний клон - на краще чи гірше.

Хороша річ у тому, що ви насправді не дублювали об’єкт, тому обсяг пам'яті буде низьким.

Деякі хитрі речі, які слід пам’ятати щодо цього методу, полягає в тому, що ітерація властивостей, визначених у ланцюжку прототипів, іноді працює дещо по-іншому, і той факт, що будь-які зміни у вихідному об’єкті також вплинуть на клонований об’єкт, якщо ця властивість також не встановлена ​​сама .


3
Це настільки смішно небезпечно і зовсім не схоже на клона. var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
Дякую

@naomik: b.foo = "working"; console.log(a.foo); // still "broken";Звичайно, слід знати, що зміни в оригінальному об'єкті будуть відображені в "клоні", а зміни в "клоні" не будуть відображені в оригінальному об'єкті - але я б не називав це небезпечним - все залежить від того, що ви хочете зробити зі своїм клоном
VoxPelli

@naomik: Я зовсім новачок у JS, тому я хотів би дещо визначити. Неглибокий клон, запропонований olli-k і який вам сподобався, здається (IMHO, але я можу помилятися) має ті самі обмеження, що і рішення, запропоноване voxpelli. Оригінал і неглибоке закриття також матимуть однакові "предмети", оновлення елемента в оригіналі також вплине на дрібний клон. Або?
bmorin

@bmornin, якщо ви використовуєте техніку Оллі К., зміна оригінального об'єкта не призведе до змін у новому об'єкті.
Дякую

1
@VoxPelli Вся мета "клону" - ізолювати зміни від початкового об'єкта, отже, цей метод є більш небезпечним.
Даг,

1

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

Приклад використання:

parent = {'prop_chain':3}
obj = Object.create(parent)
obj.a=0; obj.b=1; obj.c=2;

obj2 = copy(obj)

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3
console.log(obj2, obj2.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3

parent.prop_chain=4
obj2.a = 15

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 4
console.log(obj2, obj2.prop_chain)
// '{'a':15, 'b':1, 'c':2} 4

Сам код:

Цей код копіює об'єкти з їх прототипами, а також копіює функції (може бути корисним для когось).

function copy(obj) {
  // (F.prototype will hold the object prototype chain)
  function F() {}
  var newObj;

  if(typeof obj.clone === 'function')
    return obj.clone()

  // To copy something that is not an object, just return it:
  if(typeof obj !== 'object' && typeof obj !== 'function' || obj == null)
    return obj;

  if(typeof obj === 'object') {    
    // Copy the prototype:
    newObj = {}
    var proto = Object.getPrototypeOf(obj)
    Object.setPrototypeOf(newObj, proto)
  } else {
    // If the object is a function the function evaluate it:
    var aux
    newObj = eval('aux='+obj.toString())
    // And copy the prototype:
    newObj.prototype = obj.prototype
  }

  // Copy the object normal properties with a deep copy:
  for(var i in obj) {
    if(obj.hasOwnProperty(i)) {
      if(typeof obj[i] !== 'object')
        newObj[i] = obj[i]
      else
        newObj[i] = copy(obj[i])
    }
  }

  return newObj;
}

За допомогою цієї копії я не можу знайти жодної різниці між оригіналом та скопійованою, за винятком випадків, коли оригінал використовував закриття для його побудови, тому я вважаю, що це хороша реалізація.

Сподіваюся, це допоможе


1

для масиву можна використовувати

var arr = [1,2,3]; 
var arr_2 = arr ; 

print ( arr_2 ); 

arr = arr.slice (0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 

1

Об'єкти та масиви в JavaScript використовують виклик за посиланням, якщо ви оновите скопійоване значення, це може відобразитися на вихідному об'єкті. Щоб запобігти цьому, ви можете глибоко клонувати об'єкт, запобігти передачі посилання, використовуючи команду запуску методу бібліотеки lodash cloneDeep

npm встановити lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.