Клонирование объекта в Node.js

203

Каков наилучший способ клонирования объекта в node.js

например, я хочу избежать ситуации, когда:

var obj1 = {x: 5, y:5};
var obj2 = obj1;
obj2.x = 6;
console.log(obj1.x); // logs 6

Объект может содержать сложные типы в качестве атрибутов, поэтому простое for (var x в obj1) не решит проблему. Нужно ли мне самому писать рекурсивный клон или есть что-то встроенное, чего я не вижу?

slifty
источник
23
1. npm install underscore2. var _ = require('underscore')3 _.clone(objToClone).;
Салман фон Аббас
4
Обратите внимание, что в комментарии @ SalmanPK выше, это мелкий клон. так что это будет работать для примера slifty, но если есть вложенные массивы или объекты, они будут ссылками. : /
Джесси
1
Я нашел эту статью очень полезной: heyjavascript.com/4-creative-ways-to-clone-objects
Джордан Хадсон
3
@ Джордан Хадсон - Очень хорошее использование JSON во втором примере. var newObj = JSON.parse (JSON.stringify (oldObj)); // Теперь newObj является клоном. Единственная проблема заключается в том, что stringify не будет работать на рекурсивной ссылке, поэтому нужно быть осторожным.
Кфир Эрез

Ответы:

298

Возможность 1

Низкая навороченная глубокая копия:

var obj2 = JSON.parse(JSON.stringify(obj1));

Возможность 2 (не рекомендуется)

Внимание: это решение теперь помечено как устаревшее в документации по Node.js :

Метод util._extend () никогда не предназначался для использования вне внутренних модулей Node.js. Сообщество все равно нашло и использовало его.

Это устарело и не должно использоваться в новом коде. JavaScript поставляется с очень похожими встроенными функциями через Object.assign ().

Оригинальный ответ: ::

Для мелкой копии используйте встроенную util._extend()функцию Node .

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

var obj1 = {x: 5, y:5};
var obj2 = extend({}, obj1);
obj2.x = 6;
console.log(obj1.x); // still logs 5

Исходный код функции Node _extendнаходится здесь: https://github.com/joyent/node/blob/master/lib/util.js

exports._extend = function(origin, add) {
  // Don't do anything if add isn't an object
  if (!add || typeof add !== 'object') return origin;

  var keys = Object.keys(add);
  var i = keys.length;
  while (i--) {
    origin[keys[i]] = add[keys[i]];
  }
  return origin;
};
Джимбо
источник
5
Вопрос специально предусматривал рекурсивный клон. Это мелкий клон.
Бенджамин Аткин
28
Разве имя не _*должно означать, что это частный метод и на него нельзя полагаться?
Пушистый
7
Каждый проект JavaScript любого размера имеет одну или несколько реализаций extend (), и Node не является исключением. Ядро Node.js широко использует эту функцию. Цитируя Исаака: «Это никуда не денется в ближайшее время».
Джимбо
2
работал отлично для меня. намного лучше, чем возиться с прототипом Object imo
Майкл Даусманн
12
Это неправильный ответ. Согласно документации узла: nodejs.org/api/util.html#util_util_extend_obj метод никогда не был предназначен для использования вне внутренних модулей Node.js. Сообщество все равно нашло и использовало его. Это устарело и не должно использоваться в новом коде. JavaScript поставляется с очень похожей встроенной функциональностьюutil._extend() Object.assign().
Джорди
265

Я удивлен, Object.assignчто не было упомянуто.

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

Если доступно (например, Babel), вы можете использовать оператор распространения объекта :

let cloned = { ... source };
djanowski
источник
1
ты спас мой день! Спасибо
wzr1337
2
это гораздо лучшее решение, чем импорт сторонней библиотеки или использование несовершенного обходного пути JSON. Спасибо!
Нил С
75
это мелкая копия
Джордан Дэвидсон
14
Предупреждение для глубокого клона, для глубокого клона еще нужно использовать другие альтернативы. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
gsalgadotoledo
1
Из того, что я могу сказать, оператор распространения объекта - не вещь ES6, а предложение этапа 3. что означает, что вы можете использовать его с Babel, но не без того, что я понимаю. github.com/tc39/…
macdja38
24
Object.defineProperty(Object.prototype, "extend", {
    enumerable: false,
    value: function(from) {
        var props = Object.getOwnPropertyNames(from);
        var dest = this;
        props.forEach(function(name) {
            if (name in dest) {
                var destination = Object.getOwnPropertyDescriptor(from, name);
                Object.defineProperty(dest, name, destination);
            }
        });
        return this;
    }
});

Это определит метод расширения, который вы можете использовать. Код взят из этой статьи.

Майкл Диллон
источник
Я не понимаю, как это должно работать. Он изменяет оригинальный объект! Как я должен использовать эту функцию, чтобы получить клон объекта? Можете ли вы добавить код использования здесь? После прочтения вашего поста и поста в блоге я все еще не могу понять, как это предполагается использовать для клонирования объекта.
Брэд
3
это действительно работает? «if (имя в dest)» - изменит свойство, только если оно уже существует в dest. это должно быть сведено на нет.
мемориал
8
Разве изменение Object.prototype не должно быть верботеным? Также ссылка на статью не работает.
Даниэль Шаффер
Только что попробовал ссылку на статью, и она работает для меня. Может быть, это был сбой сети, когда вы попробовали это.
Майкл Диллон
Основываясь на ряде комментариев, я обновил ответ, добавив вариант, который не добавляет к прототипу объекта.
Шамасис Бхаттачарья
20
var obj2 = JSON.parse(JSON.stringify(obj1));
user2516109
источник
2
Это уже было предложено в этом существующем ответе, и нет смысла повторять его.
Shadow Wizard - это ухо для тебя
@ShadowWizard это разные методы. Этот просто конвертирует в json и обратно в объект, в то время как связанный ответ использует Object.keys()для перебора объекта
mente
Этот ответ неверен. JSON.stringify берет объект даты и сокращает его до строки, а затем после анализа оставляет его как строку, так что он мутирует состояние вашего объекта, оставляя вас с объектом, отличным от первоначального в случае дат.
twboc
13

Вы можете использовать функцию расширения из JQuery:

var newClone= jQuery.extend({}, oldObject);  
var deepClone = jQuery.extend(true, {}, oldObject); 

Также есть плагин Node.js:

https://github.com/shimondoodkin/nodejs-clone-extend

Чтобы сделать это без JQuery или плагина, прочитайте это здесь:

http://my.opera.com/GreyWyvern/blog/show.dml/1725165

Чудакулли
источник
Плагин Node.js идеально подходит. Спасибо!
Джастин
Отличный ответ. Я люблю маленькие полезные модули.
Chev
8

Есть некоторые Node-модули, если вы не хотите «свернуть свои собственные». Это выглядит хорошо: https://www.npmjs.com/package/clone

Похоже, он обрабатывает все виды вещей, в том числе круговые ссылки. Со страницы GitHub :

клон мастеров клонирования объектов, массивов, объектов Date и объектов RegEx. Все клонируется рекурсивно, так что вы можете, например, клонировать даты в массивах объектов. [...] Циркулярные ссылки? Ага!

Клинт Харрис
источник
7

Этот код также работает, потому что метод Object.create () создает новый объект с указанным объектом-прототипом и свойствами.

var obj1 = {x:5, y:5};

var obj2 = Object.create(obj1);

obj2.x; //5
obj2.x = 6;
obj2.x; //6

obj1.x; //5
Хирон
источник
4
это мелкая копия
Радагаст Коричневый
6

Самый простой и быстрый способ клонировать объект в NodeJS - это использовать метод Object.keys (obj)

var a = {"a": "a11", "b": "avc"};
var b;

for(var keys = Object.keys(a), l = keys.length; l; --l)
{
   b[ keys[l-1] ] = a[ keys[l-1] ];
}
b.a = 0;

console.log("a: " + JSON.stringify(a)); // LOG: a: {"a":"a11","b":"avc"} 
console.log("b: " + JSON.stringify(b)); // LOG: b: {"a":0,"b":"avc"}

Метод Object.keys требует JavaScript 1.8.5; nodeJS v0.4.11 поддерживает этот метод

но конечно для вложенных объектов нужно реализовать рекурсивную функцию


Другое решение - использовать нативный JSON (реализован в JavaScript 1.7), но он намного медленнее (примерно в 10 раз медленнее, чем предыдущий).

var a = {"a": i, "b": i*i};
var b = JSON.parse(JSON.stringify(a));
b.a = 0;
ничего не должен
источник
5

Существует также проект на Github, который направлен на то, чтобы стать более прямым портом jQuery.extend() :

https://github.com/dreamerslab/node.extend

Пример, модифицированный из документов jQuery :

var extend = require('node.extend');

var object1 = {
    apple: 0,
    banana: {
        weight: 52,
        price: 100
    },
    cherry: 97
};

var object2 = {
    banana: {
        price: 200
    },
    durian: 100
};

var merged = extend(object1, object2);
похотливый
источник
4

Есть еще одна библиотека lodash , в ней есть clone и cloneDeep , а также множество других полезных функций.

zangw
источник
4

Вы все страдаете, но решение простое.

var obj1 = {x: 5, y:5};

var obj2 = {...obj1}; // Бум

Нгенд Лио
источник
3

В поисках истинного варианта клонирования, я наткнулся на ссылку Ридкулли здесь:

http://my.opera.com/GreyWyvern/blog/show.dml/1725165

Я изменил решение на этой странице, чтобы функция, присоединенная к Objectпрототипу, не была перечисляемой. Вот мой результат:

Object.defineProperty(Object.prototype, 'clone', {
    enumerable: false,
    value: function() {
        var newObj = (this instanceof Array) ? [] : {};
        for (i in this) {
        if (i == 'clone') continue;
            if (this[i] && typeof this[i] == "object") {
                newObj[i] = this[i].clone();
            } else newObj[i] = this[i]
        } return newObj;
    }
});

Надеюсь, это поможет кому-то еще. Обратите внимание, что есть некоторые предостережения ... особенно со свойствами под названием "клон". Это хорошо работает для меня. Я не возьму на себя ответственность за написание этого. Опять же, я только изменил, как это было определено.

штифтик
источник
Это не верно. Тип даты - это объект, поэтому этот код будет заменять даты пустыми объектами ... Не используйте это.
Jtblin
0

Если вы используете кофе-скрипт, это так же просто, как:

newObject = {}
newObject[key] = value  for own key,value of oldObject

Хотя это не глубокий клон.

balupton
источник
0

Ни один из ответов не удовлетворил меня, некоторые не работают или являются просто клонами, ответы от @ clint-harris и использования JSON.parse / stringify хорошие, но довольно медленные. Я нашел модуль, который быстро выполняет глубокое клонирование: https://github.com/AlexeyKupershtokh/node-v8-clone

jtblin
источник
0

Нет встроенного способа сделать реальный клон (глубокое копирование) объекта в node.js. Есть некоторые сложные случаи, поэтому вам обязательно нужно использовать библиотеку для этого. Я написал такую ​​функцию для моей библиотеки simpleoo . Вы можете использовать deepCopyфункцию, не используя ничего из библиотеки (которая довольно мала), если вам это не нужно. Эта функция поддерживает клонирование нескольких типов данных, включая массивы, даты и регулярные выражения, поддерживает рекурсивные ссылки, а также работает с объектами, функции конструктора которых имеют требуемые параметры.

Вот код:

//If Object.create isn't already defined, we just do the simple shim, without the second argument,
//since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy = function deepCopy(src, /* INTERNAL */ _visited) {
    if(src == null || typeof(src) !== 'object'){
        return src;
    }

    // Initialize the visited objects array if needed
    // This is used to detect cyclic references
    if (_visited == undefined){
        _visited = [];
    }
    // Ensure src has not already been visited
    else {
        var i, len = _visited.length;
        for (i = 0; i < len; i++) {
            // If src was already visited, don't try to copy it, just return the reference
            if (src === _visited[i]) {
                return src;
            }
        }
    }

    // Add this object to the visited array
    _visited.push(src);

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice(0) would soft clone
        ret = src.slice();
        var i = ret.length;
        while (i--){
            ret[i] = deepCopy(ret[i], _visited);
        }
        return ret;
    }
    //Date
    if (src instanceof Date) {
        return new Date(src.getTime());
    }
    //RegExp
    if (src instanceof RegExp) {
        return new RegExp(src);
    }
    //DOM Element
    if (src.nodeType && typeof src.cloneNode == 'function') {
        return src.cloneNode(true);
    }

    //If we've reached here, we have a regular object, array, or function

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var ret = object_create(proto);

    for(var key in src){
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        ret[key] = deepCopy(src[key], _visited);
    }
    return ret;
};
Мэтт Браун
источник
0
npm install node-v8-clone

Самый быстрый клонер, он открывает нативный метод клонирования из node.js

var clone = require('node-v8-clone').clone;
var newObj = clone(obj, true); //true - deep recursive clone
Алексей Чечель
источник
0

Другое решение заключается в инкапсуляции непосредственно в новую переменную с использованием: obj1= {...obj2}

Лабель
источник
Это мелкая копия
Rémi Doolaeghe
0

Вы также можете использовать эту библиотеку клонов для глубокого клонирования объектов.

 npm install --save clone
const clone = require('clone');

const clonedObject = clone(sourceObject);
Ланил Марасингхе
источник
-2

Вы можете создать прототип объекта, а затем вызывать экземпляр объекта каждый раз, когда захотите использовать и изменить объект:

function object () {
  this.x = 5;
  this.y = 5;
}
var obj1 = new object();
var obj2 = new object();
obj2.x = 6;
console.log(obj1.x); //logs 5

Вы также можете передать аргументы в конструктор объекта

function object (x, y) {
   this.x = x;
   this.y = y;
}
var obj1 = new object(5, 5);
var obj2 = new object(6, 6);
console.log(obj1.x); //logs 5
console.log(obj2.x); //logs 6

Надеюсь, это полезно.

user3459287
источник