JavaScript: Як передавати об’єкт за значенням?


96
  • Передаючи об'єкти як параметри, JavaScript передає їх за посиланням і ускладнює створення локальних копій об'єктів.

    var o = {};
    (function(x){
        var obj = x;
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)
    

    oматимуть .fooі .bar.

  • Це можна обійти шляхом клонування; простий приклад:

    var o = {};
    
    function Clone(x) {
       for(p in x)
       this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
    }
    
    (function(x){
        var obj = new Clone(x);
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    oне матиме .fooабо .bar.


Питання

  1. Чи є кращий спосіб передавати об'єкти за значенням, крім створення локальної копії / клону?

3
Який ви використовуєте варіант для цього?
jondavidjohn

2
Програмування весело. Перевіряючи, чи вирішили це нові двигуни JS (технічно це передає посилання за значенням), але головним чином для розваги.
vol7ron

1
Див. Чи є JavaScript прохідною посиланням чи мовою передачі значення? - JavaScript не передається за посиланням. Як і Java, передаючи об'єкти функції, вона передає значення, але значення є посиланням. Див. Також Чи передається Java через посилання? .
Richard JP Le Guen,

1
Наскільки мені відомо, ви не можете передавати об'єкти за значенням. Навіть якби це було, це фактично могло б зробити клонування, про яке ви маєте на увазі вище, тому я не бачу нічого, що я бачу. Крім, можливо, збереження 3 рядків коду.
Thor84no

1
@ vol7ron Так, вони вирішили це питання, правильно застосувавши характеристику мовного дизайну.
jondavidjohn

Відповіді:


61

Не зовсім.

Залежно від того, що вам насправді потрібно, може бути одна можливість встановити oв якості прототипу нового об’єкта.

var o = {};
(function(x){
    var obj = Object.create( x );
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o);

alert( o.foo ); // undefined

Тому будь-які властивості, до яких ви додаєте obj, не додаватимуться o. Будь-які властивості, додані до objтого ж імені властивості, як і властивість у o, затьмарять його o.

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

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

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // 'new_value'

Тут ви можете побачити , що , тому що ви не затінювати масив в bazна oз bazвласністю на obj, то o.bazмасив модифікується.

Тому натомість вам спочатку потрібно буде затінити його:

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz = [];
    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // undefined

3
+1, я теж збирався запропонувати Object.createу своїй відповіді, але я не хотів переходити до пояснення неглибокості копії ;-)
Енді Е

+1, я забув про це. Я намагався var obj = new Object(x). Мені здається, що це new Object(x)буде Object.create(x)за замовчуванням - цікаво
vol7ron

1
@ vol7ron: Так, new Object(x)просто виплює той самий об’єкт (як ви, напевно, помітили) . Було б добре, якби був рідний спосіб клонування. Не впевнений, чи є щось у роботах.
користувач113716

43

Перевірте цю відповідь https://stackoverflow.com/a/5344074/746491 .

Коротше кажучи, JSON.parse(JSON.stringify(obj))це швидкий спосіб скопіювати ваші об'єкти, якщо ваші об'єкти можна серіалізувати в json.


2
Я схильний уникати цього, оскільки об'єкти не завжди можуть бути серіалізовані, а тому, що в браузери середнього віку, як IE7 / 8, він JSONне включений і потребує визначення - це може бути також більш описовим з такою назвою Clone. Однак я підозрюю, що моя думка може змінитися в майбутньому, оскільки JSON більше інтегрована в не браузерне програмне забезпечення, таке як бази даних та IDE
vol7ron

1
Якщо немає функцій та об’єктів дати, рішення JSON.parse здається найкращим. stackoverflow.com/questions/122102 / ...
Herr_Hansen

Дійсно?!? Серіалізувати в JSON просто для створення копії об’єкта? Ще одна причина ненавидіти цю абсурдну мову.
RyanNerd,

1
Серіалізація JSON - це дійсно швидкий спосіб скопіювати його? Згідно з документацією NodeJS, маніпуляція JSON є найбільш інтенсивним процесором. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad

38

Ось функція клонування, яка буде виконувати глибоку копію об’єкта:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

Тепер ви можете використовувати так:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)

3
Це була глибока відповідь.
Натан

Гарний! Найкраща відповідь ІМО.
Гандер

23

Використовуйте Object.assign()

Приклад:

var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.

Приклад чистішого, який робить те саме:

var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/ assign


4
Краще:var b = Object.assign({}, a);
Бергі

Домовились. Відповідне оновлення відповіді. Крім того, не кожен браузер підтримує метод призначити (). Залежно від вимог до сумісності, може знадобитися використання поліфайлера, подібного до MDN.
Брендон

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

2
Це працює, але майте на увазі, що він не виконує глибоке копіювання, будь-які об’єкти, що містяться всередині, aбудуть скопійовані як посилання.
Стоїнов

15

Ви трохи заплутані в тому, як працюють об’єкти в JavaScript. Посилання на об'єкт - це значення змінної. Несеризованого значення немає. Коли ви створюєте об'єкт, його структура зберігається в пам'яті, а змінна, якій вона була призначена, містить посилання на цю структуру.

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

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


1
Гей, Енді! Мене це не бентежить, я сподівався, що Дж. С. за замовчуванням зробить клонування. Існує велика неефективність самостійного клонування, тому якщо двигун це зробив, я впевнений, що було б краще. Я сумніваюся, що потреба / попит перевищує користь.
vol7ron

12
У JS, коли люди говорять за посиланням, а не за значенням, вони означають, що значення є як вказівник, а не дублікат. Це досить налагоджене лінго.
Ерік Реппен

4
@Erik: на мою скромну думку, я вважаю, що краще визначити це більш чітко, щоб не плутати новачків з мовою. Тільки тому, що щось добре налагоджене, не означає, що це доречно або навіть технічно правильно (тому що це не так). Люди кажуть «за посиланням», бо їм простіше сказати, ніж пояснити, як це насправді працює.
Енді Е

6
Значення - це посилання на місце в пам'яті. Зрештою, ви все одно дієте на один і той же елемент у пам'яті, а не працюєте з копією. Як зробити цю відмінність неоднозначною, щоб допомогти новачкам? Ось як це пояснюється у багатьох технічних книгах, включаючи авторитетне керівництво О'Рейлі, яке зараз виходить у п’яте (ОТ: і дуже слабо оновлене) видання.
Erik Reppen

1
@AndyE Я трохи більше читав, щоб побачити, у чому суєта. ІМО, це як би питання про те, чи має JS "справжній" ООП, чи ми можемо правильно назвати щось "методом" порівняно з іншими мовами. Я насправді не впевнений, що було б доцільно розглядати vars як запис безпосередньо на той самий об'єкт у пам'яті, а не перезапис його скопійованого покажчика мовою, що базується на закритті, з розгалуженням поведінки оператора присвоєння, що вбиває продуктивність. Єдина реальна відмінність поведінки від того, що ми маємо на увазі в JS та інших мовах, - це те, що відбувається під час використання оператора призначення.
Ерік Реппен

15

Використовуй це

x = Object.create(x1);

xі x1буде двома різними об'єктами, зміна в xне змінитьсяx1


У підсумку я використовую Object.assign (). Для простого тестового випадку .create () працює, але для більш складного (який я ще не можу знайти зразок) він все одно посилається на ту саму адресу.
Coisox

Це не зміниться лише в тому випадку, якщо значення об'єкта не зберігаються за посиланням, тобто вони є премітивними типами даних, такими як число або логічне значення. Наприклад, розглянемо код нижче a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (сокира);
Jjagwe Dennis

8

Javascript завжди передається за значенням. У цьому випадку це передавання копії посилання oв анонімну функцію. Код використовує копію посилання, але він мутує один об'єкт. Немає способу змусити javascript пройти мимо чогось іншого, крім значення.

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

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}

1
+1 Для "завжди передається за значенням", хоча я вважаю за краще використовувати Call by Object Sharing (на відміну від "Call by Value [Посилання]") та сприяти "значенню" до "самого об'єкта" (із зауваженням, що об'єкт не копіюється / клонується / дублюється), оскільки посилання є лише деталлю реалізації та не піддається впливу JavaScript (або безпосередньо згадується в специфікації!).

У мене виникла проблема з додаванням властивостей до об'єкта, який я отримую через віддалене з'єднання (я думав, що це може бути проблема, що я не в змозі додати до цього об'єкта жодне властивість), тому я спробував цю функцію дрібної копіювання і отримав цю помилку - прототип об'єкта може бути лише Object або null у var copy = Object.create (o); будь-яка ідея, як вирішити мою проблему
Harshit Laddha

Це не глибока копія?
Річка Там

@RiverTam Ні, це не глибока копія, оскільки вона не клонує об’єкти всередині об’єктів. Однак ви можете легко зробити його глибокою копією, просто зробивши її рекурсивно викликати себе на кожному з під-об'єктів.
ABPerson

7

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

var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy    = jQuery.extend(true, {}, o); 

посилання:


Ах - ти відкрив це старе запитання :) Я думаю, що оригінальне питання було більше дослідженням мови JS. Здається, навіть моя термінологія тоді була слабкою, оскільки мій приклад був глибокою копією, а не клоном . Але, схоже, об'єкти / хеші можуть передаватися лише за посиланням, що є загальним для мов сценаріїв
vol7ron

1
:) Натрапив на нього, шукаючи точну функціональність. У мене був об’єкт даних, який мені потрібно було скопіювати для редагування, перш ніж надсилати, зберігаючи оригінальний об'єкт. Я віддаю перевагу власному рішенню JS і буду використовувати його у власній базовій бібліотеці, але рішення jQuery - це моє тимчасове виправлення. Прийнята відповідь - фантастична. : D
Бретт Вебер,

6

Насправді Javascript завжди передається за значенням . Але оскільки посилання на об’єкти є значеннями , об’єкти будуть поводитися так, як передаються посиланням .

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

var person = { Name: 'John', Age: '21', Gender: 'Male' };

var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object

var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects

2
Так, це варіант для простого перетворення / повторного перетворення. Як правило, робота зі рядками дуже неефективна; хоча я не оцінював ефективність аналізу JSON протягом деякого часу. Майте на увазі, що поки це працює для простих об'єктів, об’єкти, які містять функції як значення, втрачають вміст.
vol7ron

4

використовувати obj2 = {... obj1} Тепер обидва об'єкти мають однакове значення бюста різного посилання


2
Нове для JavaScript тут. Оператор розповсюдження створює нове значення замість копіювання за посиланням? То чому це було знято?
jo3birdtalk

2
На перший погляд у мене це виходить. Але я виявляю, що це стосується лише першого шару змінної. Наприклад, це працює, коли let a = {value: "old"} let b = [...a] b.value = "new"і буде a, буде {value: "old"}b {value: "new"}. Але це не виходить, коли let a = [{value: "old"}] let b = [...a] b[0].value = "new", а буде, буде і буде [{value: "new"}]b [{value: "new"}].
Брейді Хуанг,

так, це неглибока копія. Якщо у вас є вкладений об'єкт, вам потрібно зробити глибоке копіювання. Для простоти ви можете використовувати функції clone та deepClone lodash
Geetanshu Gulati

3

Мені потрібно було скопіювати об’єкт за значенням (не посиланням), і я знайшов цю сторінку корисною:

Який найефективніший спосіб глибокого клонування об’єкта в JavaScript? . Зокрема, клонування об’єкта із таким кодом Джона Ресіга:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Питання стосується JavaScript, а не бібліотеки jQuery.
j08691

1
Правда, людям, які не можуть користуватися Jquery з якоїсь причини, це не допомогло б. Однак, оскільки Jquery - це бібліотека javascript, я думаю, це все одно допоможе більшості користувачів javascript.
Ентоні Хаффі

Це не допоможе жодному з програмістів, які намагаються це зробити на сервері. Отже - ні. Це неправдива відповідь.
Томаш Галовскі

3

З синтаксисом ES6:

let obj = Object.assign({}, o);


3
Дуже гарна згадка @galkowskit. Найбільша проблема (краще: на що слід звернути увагу ) у тому Object.assign, що вона не виконує глибоку копію.
vol7ron

1

Коли ви перейдете до цього, це просто химерний надмірно складний проксі, але, можливо, Catch-All Proxies міг би це зробити?

var o = {
    a: 'a',
    b: 'b',
    func: function() { return 'func'; }
};

var proxy = Proxy.create(handlerMaker(o), o);

(function(x){
    var obj = x;
    console.log(x.a);
    console.log(x.b);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(proxy);

console.log(o.foo);

function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {

   },
   delete:       function(name) { return delete obj[name]; },   
   fix:          function() {}
  };
}

Річарде, я трохи запізнився з відповіддю :) але я думаю, що ніколи цього не робив з кількох причин: я шукав синтаксичний цукор, що дало JS можливість передавати предмети різними способами; Ваша відповідь виглядала довго, і я повісив на ваше використання "проксі". 3 роки пізніше, і я все-таки відмовився від використання. Можливо, я не приділяв достатньо уваги на уроці, але подібно до мереж, я думав, що довірені особи в CS повинні виконувати роль посередника. Можливо, тут і тут, і я неправильно не замислююся над тим, що це посередництво.
vol7ron

0

Якщо ви використовуєте lodash або npm, використовуйте функцію злиття lodash, щоб глибоко скопіювати всі властивості об’єкта в новий порожній об’єкт, наприклад:

var objectCopy = lodash.merge({}, originalObject);

https://lodash.com/docs#merge

https://www.npmjs.com/package/lodash.merge


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