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. Есть ли лучший способ передать объекты по значению, кроме создания локальной копии / клона?
vol7ron
источник
3
Каков ваш вариант использования для этого?
jondavidjohn
2
Программирование весело. Увидеть, исправили ли это новые JS-движки (технически он передает ссылку по значению), но в основном для развлечения.
vol7ron
1
См. Раздел « Является ли JavaScript языком передачи по ссылке или по значению?» - JavaScript не передается по ссылке. Как и в Java, при передаче объектов функции она передается по значению, но значение является ссылкой. См. Также Передается ли Java по ссылке? .
Ричард JP Le Guen
1
Насколько мне известно, нельзя передавать объекты по значению. Даже если бы он был, он фактически выполнял бы клонирование, о котором вы говорите выше, так что я ничего не вижу. Кроме, возможно, сохранения трех строк кода.
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'

Здесь вы можете видеть, что, поскольку вы не затеняли массив в bazon 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
user113716
источник
3
+1, я тоже собирался предложить Object.createв своем ответе, но я не хотел растягиваться, чтобы объяснить поверхностность текста ;-)
Andy E
+1, я забыл об этом. Я пробовал var obj = new Object(x). Для меня, я думаю, это new Object(x)будет работать Object.create(x)по умолчанию - интересно
vol7ron
1
@ vol7ron: Да, new Object(x)просто выплевывает тот же объект (как вы, наверное, заметили) . Было бы неплохо, если бы был родной способ клонирования. Не уверен, что в работе что-то есть.
user113716
45

Посмотрите этот ответ https://stackoverflow.com/a/5344074/746491 .

Короче говоря, JSON.parse(JSON.stringify(obj))это быстрый способ скопировать ваши объекты, если ваши объекты могут быть сериализованы в json.

свимре
источник
2
Я стараюсь избегать этого, поскольку объекты не всегда могут быть сериализованы, и потому что в браузерах среднего возраста, таких как IE7 / 8, параметр JSONIs не включен и должен быть определен - с таким же успехом может быть более описательным с именем вроде 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
40

Вот функция клонирования, которая будет выполнять глубокую копию объекта:

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
Это был глубокий ответ.
Натан
Красивая! Лучший ответ ИМО.
ganders
24

Использовать 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);
Берги
Согласовано. Соответственно обновляю ответ. Кроме того, не каждый браузер поддерживает метод assign (). В зависимости от ваших требований к совместимости может потребоваться использование полифилла, подобного тому, что есть в MDN.
Brandon
3
@Brandon, в той самой ссылке, которую вы предоставили, он дает предупреждение о глубоком клонировании: «Для глубокого клонирования нам нужно использовать другие альтернативы, потому что Object.assign () копирует значения свойств. Если исходное значение является ссылкой на объект, он копирует только это эталонное значение ".
pwilcox
2
Это работает, но имейте в виду, что он не выполняет глубокое копирование, любые объекты, содержащиеся внутри, aбудут скопированы как ссылка.
Стоинов
16

Вы немного запутались в том, как работают объекты в JavaScript. Ссылка на объект - это значение переменной. Нет несериализованного значения. Когда вы создаете объект, его структура сохраняется в памяти, а переменная, которой он был назначен, содержит ссылку на эту структуру.

Даже если то, что вы просите, было предоставлено в виде какой-то простой конструкции на родном языке, технически это все равно было бы клонированием.

На самом деле JavaScript - это просто передача по значению ... просто переданное значение может быть ссылкой на что-то.

Энди Э
источник
2
Привет, Энди! Меня это не смущает, я надеялся, что JS выполнит клонирование по умолчанию. Самоклонирование имеет большую неэффективность, поэтому, если бы это делал движок, я уверен, что было бы лучше. Я сомневаюсь, что потребность / спрос превышает выгоду.
vol7ron
13
В JS, когда люди говорят по ссылке, а не по значению, они имеют в виду, что значение похоже на указатель, а не на дубликат. Это довольно устоявшийся жаргон.
Эрик Реппен,
4
@Erik: по моему скромному мнению, я думаю, что лучше дать более четкое определение, чтобы не путать новичков с языком. То, что что-то хорошо установлено, не означает, что это уместно или даже технически правильно (потому что это не так). Люди говорят «по ссылке», потому что им легче сказать это, чем объяснять, как это действительно работает.
Andy E
6
Значение является ссылкой на место в памяти. В конечном итоге вы по-прежнему работаете с одним и тем же элементом в памяти, а не с копией. Как создание этого неоднозначного различия должно помочь новичкам? Именно так это объясняется во многих технических книгах, включая Авторитетное руководство О'Рейли, которое сейчас находится в пятом (ОТ: и очень слабо обновленном) издании.
Эрик Реппен
1
@AndyE Я еще немного почитал, чтобы понять, о чем идет речь. ИМО, это похоже на вопрос о том, имеет ли JS «истинное» ООП, или мы можем правильно назвать что-то «методом» по сравнению с другими языками. На самом деле я не уверен, что можно будет рассматривать vars как запись непосредственно в один и тот же объект в памяти, а не перезаписывать его скопированный указатель на языке, основанном на закрытии, с ветвлением поведения оператора присваивания, снижающим производительность. Единственное различие в поведении, которое мы имеем в виду в JS и других языках, заключается в том, что происходит, когда вы используете оператор присваивания.
Эрик Реппен
16

Использовать это

x = Object.create(x1);

xи x1будет два разных объекта, изменение xне изменитсяx1

user5242529
источник
В итоге я использую Object.assign (). Для простого тестового примера .create () работает, но для более сложных (шаблон которых я пока не могу найти) он по-прежнему ссылается на тот же адрес.
Coisox 03
1
Это не изменится только в том случае, если значения объекта не сохранены по ссылке, то есть они являются предварительными типами данных, такими как число или логическое значение. Например, рассмотрим приведенный ниже код a = {x: [12], y: []}; b = Object.create (а); 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 За «всегда проходит по значению», хотя я предпочитаю использовать « Вызов по значению» (в отличие от «Вызов по значению [ссылки]») и продвигать «значение» к «самому объекту» (с примечанием, что объект не копируется / не клонируется / не дублируется), поскольку ссылки являются лишь деталью реализации и не отображаются в JavaScript (или прямо упоминаются в спецификации!).
У меня возникла проблема с добавлением свойств к объекту, который я получаю через удаленное соединение (я подумал, что может быть проблема в том, что я не могу добавить какое-либо свойство к этому объекту), поэтому я попробовал эту функцию поверхностного копирования и получил эту ошибку - прототип объекта может быть только Object или null в var copy = Object.create (o); любая идея, как решить мою проблему
Harshit Laddha
Разве это не глубокая копия?
River Tam
@RiverTam Нет, это не глубокая копия, потому что она не клонирует объекты внутри объектов. Однако вы можете легко сделать его глубокой копией, просто заставив его рекурсивно вызывать себя для каждого из подобъектов.
ABPerson,
7

Для пользователей jQuery есть также способ сделать это простым способом, используя фреймворк. JQuery - это еще один способ сделать нашу жизнь немного проще.

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

Ссылки :

Бретт Вебер
источник
Ах, вы открыли этот старый вопрос :) Я думаю, что исходный вопрос был скорее исследованием языка JS. Кажется, тогда даже моя терминология была слабой, поскольку мой пример был скорее глубокой копией , чем клоном . Но кажется, что объекты / хэши могут передаваться только по ссылке, что является обычным явлением для языков сценариев
vol7ron
1
:) Наткнулся на него в поисках точного функционала. У меня был объект данных, который мне нужно было скопировать для редактирования перед отправкой, сохраняя исходный объект. Я предпочитаю собственное решение JS и буду использовать его в своей базовой библиотеке, но решение jQuery - мое временное исправление. Принятый ответ фантастический. : D
Бретт Вебер
6

На самом деле Javascript всегда передается по значению . Но поскольку ссылки на объекты являются значениями , объекты будут вести себя так, как будто они передаются по ссылке .

Поэтому для того , чтобы ходить вокруг этого, stringify объект и разобрать его обратно, как с помощью 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 03
Я на самом деле тестировал это и видел, как это делают другие, и из-за эффективности javascripts json engine этот метод действительно является одним из самых быстрых способов сделать это, не прибегая к большой библиотеке, написанию большого количества кода или бездействию настоящая глубокая копия.
tedivm
4

Использование оператора распространения, например, obj2 = { ...obj1 } будет иметь одинаковые значения, но разные ссылки

Гитаншу Гулати
источник
2
Новичок в javascript здесь. Оператор распространения создает новое значение вместо копирования по ссылке? Так почему это было отклонено?
jo3birdtalk
2
На первый взгляд, у меня все получается. Но я обнаружил, что это только первый уровень переменной. Например, работает, когда let a = {value: "old"} let b = [...a] b.value = "new"и будет {value: "old"}, а будет {value: "new"}. Но не получается когда let a = [{value: "old"}] let b = [...a] b[0].value = "new", а будет [{value: "new"}], а будет [{value: "new"}].
Брэди Хуанг
да, это мелкая копия. Если у вас есть вложенный объект, вам нужно сделать глубокую копию. Для простоты вы можете использовать функции lodash clone и deepClone
Гитаншу Гулати
3

Мне нужно было скопировать объект по значению (не по ссылке), и я нашел эту страницу полезной:

Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript? . В частности, клонирование объекта с помощью следующего кода от Джона Ресига:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
BeepBop
источник
Речь идет о JavaScript, а не о библиотеке jQuery.
j08691
1
Правда, для людей, которые не могут использовать JQuery по какой-либо причине, это им не поможет. Однако, поскольку Jquery - это библиотека javascript, я думаю, что она все равно поможет большинству пользователей javascript.
BeepBop
Это не поможет никому из программистов, пытающихся сделать это на сервере. Так что - нет. Это неверный ответ.
Tomasz Gałkowski
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

Кон Антонакос
источник
Глубокая копия также должна включать события, я не слишком знаком с lodash, отражает ли она события для нового объекта?
vol7ron 01