Клонування об’єкта в 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.