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) Он не сможет восстановить функции или прототипные данные, но он отлично получает значения.
Kato
13
Это преобразует даты в строки
backus
4
@Backus А также объекты и функции.
Kato
2
@Kato, почему круговые ссылки означают, что нет необходимости делать глубокие копии? Это две не связанные между собой вещи. Конечно, можно написать хороший метод клонирования для NodeJS, который поддерживает циклические ссылки и не прибегает к использованию JSON для клонирования. github.com/pvorb/node-clone
Сэмюэл Нефф
2
Уже отмечалось в комментариях и в описании. Вы не можете клонировать функцию, прототип объекта или другие вещи, которые не должны пытаться делать с помощью универсального метода клонирования. Для этого вам понадобится более одной строки кода, и, вероятно, в любом случае не следует клонировать этот мусор - поместите в свой класс метод clone, который знает, как обрабатывать внутренние компоненты, и делать это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 :

clone masters клонирует объекты, массивы, объекты Date и объекты 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метод)
  • у объекта есть 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).

6502
источник
1
+1 за упоминание о том, что объекты должны .cloneсами реализовать метод. Это лучший способ справиться с клонированием объектов.
Raynos
3
if (x.clone)должно бытьif (typeof x.clone === 'function')
Яник Рочон
@YanickRochon: Спасибо, исправлено. К сожалению, как - то я не заметил этот комментарий перед ...
6502
9

Не существует собственного метода клонирования объектов. Underscore _.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;
};

Extend просто перебирает все элементы и создает новый объект с элементами в нем.

Вы можете развернуть свою собственную наивную реализацию, если хотите

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

Олли К
источник
просто, элегантно, блестяще. Я с нетерпением жду ваших других ТАКИХ вкладов ^ _ ^
Спасибо
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

Вы также можете использовать lodash . Он имеет методы 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);

Просто помните, что это не полный клон - хорошо это или плохо.

В этом отношении хорошо то, что вы фактически не продублировали объект, поэтому объем памяти будет низким.

Тем не менее, некоторые хитрые вещи, которые следует помнить об этом методе, заключаются в том, что итерация свойств, определенных в цепочке прототипов, иногда работает немного по-другому, и тот факт, что любые изменения в исходном объекте также повлияют на клонированный объект, если это свойство также не было установлено для самого себя. .

VoxPelli
источник
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 и который вам понравился, кажется (ИМХО, но я могу ошибаться) имеет те же ограничения, что и решение, предложенное 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 ); 
tlqtangok
источник
1

Объекты и массивы в JavaScript используют вызов по ссылке, если вы обновите скопированное значение, оно может отразиться на исходном объекте. Чтобы предотвратить это, вы можете глубоко клонировать объект, чтобы предотвратить передачу ссылки, используя команду запуска метода cloneDeep библиотеки lodash

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

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
Vinay Dagar
источник