Скопируйте значение переменной в другую

100

У меня есть переменная, значение которой имеет объект JSON. Я напрямую назначаю эту переменную какой-то другой переменной, чтобы они имели одинаковое значение. Вот как это работает:

var a = $('#some_hidden_var').val(),
    b = a;

Это работает, и оба имеют одинаковую ценность. Я использую mousemoveобработчик событий для обновления bвсего приложения. При нажатии кнопки я хочу вернуться bк исходному значению, то есть к значению, хранящемуся в a.

$('#revert').on('click', function(e){
    b = a;
});

После этого, если я использую тот же mousemoveобработчик событий, он обновляет оба, aа bкогда раньше он обновлял толькоb как ожидалось.

Я в тупике по этому поводу! Что здесь не так?

Рутвик Гангурд
источник
Покажите свой обработчик mousemove. Кроме того, учитывая, что это aбыло установлено, .val()я предполагаю, что это JSON (строка), а не объект - верно? Используете ли вы JSON.parse(a)в какой-то момент, чтобы получить реальный объект?
nnnnnn
Да, это строка, но я использую ее $.parseJSONдля преобразования в объект. Структура: { 'key': {...}, 'key': {...}, ...}. Извините, я не могу разместить здесь код, это запрещено на моем рабочем месте!
Rutwick Gangurde
1
так aэто объект?
Armand
1
Похоже, проблема в области видимости .. aэто глобальная переменная?
msturdy
2
Код, который вы показали, содержит только строки. Если вы говорите об объектах, тогда несколько переменных могут ссылаться на один и тот же объект, и поэтому этот объект может быть изменен с помощью любой из переменных. Поэтому, не видя больше кода, который манипулирует переменными (откуда это $.parseJSON()взялось?), Трудно сказать, в чем проблема. Что касается правил вашего рабочего места, вам не нужно публиковать свой реальный реальный код полностью, просто приведите более короткий и общий пример, демонстрирующий проблему (и, в идеале, включите ссылку на живую демонстрацию на jsfiddle.net ) .
nnnnnn

Ответы:

201

Важно понимать, что делает =оператор в JavaScript, а что не делает.

=Оператор не делает копию данных.

=Оператор создает новую ссылку на тот же данные.

После запуска исходного кода:

var a = $('#some_hidden_var').val(),
    b = a;

aи bтеперь это два разных имени для одного и того же объекта .

Любые изменения, внесенные вами в содержимое этого объекта, будут видны одинаково независимо от того, ссылаетесь ли вы на него через aпеременную или bпеременную. Это один и тот же объект.

Итак, когда вы позже попытаетесь «вернуться» bк исходному aобъекту с помощью этого кода:

b = a;

Код вообще ничего не делает , потому что aи b- это одно и то же. Код такой же, как если бы вы написали:

b = b;

который явно ничего не сделает.

Почему ваш новый код работает?

b = { key1: a.key1, key2: a.key2 };

Здесь вы создаете новый объект с {...}литералом объекта. Этот новый объект не такой, как ваш старый. Итак, теперь вы устанавливаете bссылку на этот новый объект, который делает то, что вы хотите.

Для обработки любого произвольного объекта вы можете использовать функцию клонирования объекта, такую ​​как та, что указана в ответе Арманда, или, поскольку вы используете jQuery, просто используйте эту $.extend()функцию . Эта функция сделает мелкую или глубокую копию объекта. (Не путайте это с $().clone()методом который предназначен для копирования элементов DOM, а не объектов.)

Для мелкой копии:

b = $.extend( {}, a );

Или глубокая копия:

b = $.extend( true, {}, a );

В чем разница между мелкой копией и глубокой копией? Неглубокая копия похожа на ваш код, который создает новый объект с литералом объекта. Он создает новый объект верхнего уровня, содержащий ссылки на те же свойства, что и исходный объект.

Если ваш объект содержит только примитивные типы, такие как числа и строки, глубокая копия и поверхностная копия будут делать то же самое. Но если ваш объект содержит другие объекты или массивы, вложенные в него, то неглубокая копия не копирует эти вложенные объекты, а просто создает ссылки на них. Таким образом, у вас может быть та же проблема с вложенными объектами, что и с вашим объектом верхнего уровня. Например, с учетом этого объекта:

var obj = {
    w: 123,
    x: {
        y: 456,
        z: 789
    }
};

Если вы сделаете неглубокую копию этого объекта, тогда xсвойством вашего нового объекта будет тот же xобъект, что и у оригинала:

var copy = $.extend( {}, obj );
copy.w = 321;
copy.x.y = 654;

Теперь ваши объекты будут выглядеть так:

// copy looks as expected
var copy = {
    w: 321,
    x: {
        y: 654,
        z: 789
    }
};

// But changing copy.x.y also changed obj.x.y!
var obj = {
    w: 123,  // changing copy.w didn't affect obj.w
    x: {
        y: 654,  // changing copy.x.y also changed obj.x.y
        z: 789
    }
};

Вы можете избежать этого с помощью глубокой копии. Глубокая копия повторяется во всех вложенных объектах и ​​массивах (и в Date в коде Арманда), чтобы сделать копии этих объектов так же, как и копию объекта верхнего уровня. Так что изменение copy.x.yне повлияетobj.x.y .

Краткий ответ: если сомневаетесь, возможно, вам понадобится глубокая копия.

Майкл Гири
источник
Спасибо, это то, что я искал. Есть ли способ обойти это? В моей нынешней ситуации это было легко исправить, так как у меня было всего 2 свойства. Но могут быть предметы побольше.
Rutwick Gangurde
1
В ответе Арманда есть функция клонирования объекта, которая поможет. Или, поскольку вы используете jQuery, вы можете использовать встроенную $.extend()функцию. Подробности выше. :-)
Майкл Гири
Отличное объяснение, Майкл!
Rutwick Gangurde
7
@MichaelGeary Это может быть придирка, но разве правило не применяется только к объектам, а не к примитивным переменным? это, возможно, стоит отметить в ответе
Джонатан душ Сантуш
Решение JS для этого есть в ответе Арманда, как сказал Майкл Гири. =
Александр
57

Я обнаружил, что использование JSON работает, но следите за циклическими ссылками

var newInstance = JSON.parse(JSON.stringify(firstInstance));
kernowcode
источник
2
Я понимаю, что это немного поздно, но есть ли проблема с этим методом? Кажется, с первого раза это работает потрясающе.
Mankind1023,
2
Это замечательно почти в любой ситуации, однако, если вы работаете с данными, которые потенциально могут быть специфичными, это приведет к сбою вашей программы. какой-то код для иллюстрации: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)выдаст
TypeError
Это решение отлично работает. Однако насколько это дорого при обработке? Кажется, это работает путем двойного отрицания.
Абель Кальехо,
30

вопрос уже решен довольно давно, но для дальнейшего использования возможное решение

b = a.slice(0);

Будьте осторожны, это работает правильно, только если a - невложенный массив чисел и строк.

Маркош
источник
24

newVariable = originalVariable.valueOf();

для предметов, которые вы можете использовать, b = Object.assign({},a);

Кишор Патил
источник
3
для объектов, которые вы можете использовать, b = Object.assign ({}, a);
Кишор Патил
15

Причина этого проста. JavaScript использует ссылки, поэтому, когда вы назначаете, b = aвы назначаете ссылку, bтаким образом, при обновлении aвы также обновляетеb

Я нашел это в stackoverflow и помогу предотвратить подобные вещи в будущем, просто вызвав этот метод, если вы хотите сделать глубокую копию объекта.

function clone(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}
Арман
источник
Спасибо! Я видел этот пост до того, как опубликовал новый вопрос. Но я не был уверен, поможет ли это в моем сценарии. Оказывается, это то, что мне нужно.
Rutwick Gangurde
Разве все, кроме первого, не if (obj instanceof x) { ... }должно быть иначе, если?
Zac
@Zac Не в этом случае. Каждое интересующее нас if-тело возвращает значение. (выходит из функции до следующего if)
Bitterblue
8

Я не понимаю, почему ответы такие сложные. В Javascript примитивы (строки, числа и т. Д.) Передаются по значению и копируются. Объекты, включая массивы, передаются по ссылке. В любом случае присвоение «a» нового значения или ссылки на объект не изменит «b». Но изменение содержимого 'a' изменит содержимое 'b'.

var a = 'a'; var b = a; a = 'c'; // b === 'a'

var a = {a:'a'}; var b = a; a = {c:'c'}; // b === {a:'a'} and a = {c:'c'}

var a = {a:'a'}; var b = a; a.a = 'c'; // b.a === 'c' and a.a === 'c'

Вставьте любую из вышеперечисленных строк (по одной) в узел или любую консоль javascript браузера. Затем введите любую переменную, и консоль покажет ее значение.

Трентон Д. Адамс
источник
4

Для строк или входных значений вы можете просто использовать это:

var a = $('#some_hidden_var').val(),
b = a.substr(0);
Тим
источник
Зачем вам подставлять примитив? Просто назначьте его, он копирует строку. По ссылке передаются только объекты и массивы (которые являются объектами).
Трентон Д. Адамс
2

Большинство ответов здесь используют встроенные методы или библиотеки / фреймворки. Этот простой метод должен работать нормально:

function copy(x) {
    return JSON.parse( JSON.stringify(x) );
}

// Usage
var a = 'some';
var b = copy(a);
a += 'thing';

console.log(b); // "some"

var c = { x: 1 };
var d = copy(c);
c.x = 2;

console.log(d); // { x: 1 }
Лассе Брустад
источник
Гений! Другие методы превращают все в словарь, включая массивы внутри них. Таким образом я клонирую объект, и его содержимое остается неизменным, в отличие от этого:Object.assign({},a)
Тики,
0

Решил пока сам. Исходное значение имеет только 2 подсвойства. Я преобразовал новый объект со свойствами из, aа затем назначил его b. Теперь мой обработчик событий обновляется b, а исходный aостается как есть.

var a = { key1: 'value1', key2: 'value2' },
    b = a;

$('#revert').on('click', function(e){
    //FAIL!
    b = a;

    //WIN
    b = { key1: a.key1, key2: a.key2 };
});

Это прекрасно работает. Я не изменил ни одной строчки в своем коде, за исключением приведенного выше, и он работает именно так, как я хотел. Так что поверьте мне, больше ничего не обновлялось a.

Рутвик Гангурд
источник
0

Решение для AngularJS :

$scope.targetObject = angular.copy($scope.sourceObject)
Тони Сепия
источник