Клонування об’єкта в Node.js


203

Який найкращий спосіб клонувати об’єкт у node.js

наприклад, я хочу уникнути ситуації, коли:

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

Об'єкт цілком може містити складні типи як атрибути, тому простий для (var x in obj1) не вирішить. Чи потрібно мені самостійно написати рекурсивний клон чи є щось вбудоване, чого я не бачу?


23
1. npm install underscore2. var _ = require('underscore')3 _.clone(objToClone).;
— Салман фон Аббас

4
Зауважте, що в коментарі @ SalmanPK вище це дрібний клон. тож він буде працювати для прикладу slifty, але якщо є вкладені масиви або об'єкти, вони будуть посиланнями. : /
— Джессі

1
Я вважаю цю статтю дуже корисною: heyjavascript.com/4-creative-ways-to-clone-objects
— Джордан Хадсон

3
@Jordan Hudson - Дуже приємно використовувати 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 будь-якого розміру має одну чи більше реалізацій exte (), і Node не є винятком. Ядро Node.js широко використовує цю функцію. Цитуючи Ісаака, "Скоро це нікуди не піде".
— jimbo

2
прекрасно працював для мене. набагато краще, ніж возитися з прототипом imo
— Michael Dausmann,

12
Це неправильна відповідь. Згідно з документацією вузла: nodejs.org/api/util.html#util_util_extend_obj метод ніколи не був призначений для використання поза внутрішніх модулів Node.js. Громада все-таки знайшла та використовувала її. Вона застаріла і не повинна використовуватися в новому коді. JavaScript поставляється з дуже схожим вбудованим функціоналом черезutil._extend() Object.assign().
— Jordie

265

Я здивований, Object.assignщо не згадували.

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

За наявності (наприклад, Babel), ви можете використовувати оператор розповсюдження об'єктів :

let cloned = { ... source };

1
ти врятував мій день! Дякую
— wzr1337

2
це набагато краще рішення, ніж імпортувати сторонні бібліотеки або використовувати недосконале вирішення JSON. Дякую!
— Ніл S

75
це неглибока копія
— Джордан Девідсон

14
Попередження про Deep Clone, Для глибокого клонування все-таки потрібно використовувати інші альтернативи. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
— gsalgadotoledo

1
З того, що я можу сказати, оператор розповсюдження об'єктів - це не річ ES6, а натомість пропозиція на етапі 3. це означає, що ви можете використовувати його з джебелем, але не без того, що я розумію. 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));

2
Це вже було запропоновано в цій існуючій відповіді, жоден пункт не повторюючи її.
— Shadow Wizard є Ear For You

@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 ідеальний. Дякую!
— Джастін

Чудова відповідь. Я люблю мало корисних модулів.
— Шев


8

Є кілька модулів Вузла там, якщо ви не хочете "скручувати свої". Це добре виглядає: 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

Ви все страждаєте, але рішення просте.

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

Хоча це не глибокий клон.


0

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


0

Немає вбудованого способу зробити справжній клон (глибоку копію) об’єкта в node.js. Є кілька складних крайових випадків, тому для цього обов'язково слід використовувати бібліотеку. Я написав таку функцію для своєї простої бібліотеки. Ви можете використовувати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

Сподіваюся, це корисно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.