Скопіюйте значення змінної в інше


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за очікуванням.

Я в тупі від цього питання! Що тут не так?


Будь ласка, покажіть свій обробник переміщення миші. Крім того, враховуючи те, що aбуло встановлено з, .val()я припускаю, що це JSON (рядок), а не об'єкт - це правильно? Чи використовуєте ви JSON.parse(a)в якийсь момент для отримання фактичного об'єкта?
nnnnnn

Так, це рядок, але я використовую $.parseJSONдля перетворення його в об'єкт. Структура: { 'key': {...}, 'key': {...}, ...}. На жаль, я не можу розмістити тут жодного коду, забороненого на моєму робочому місці!
Рутвік Гангурде,

1
так це aоб'єкт?
Арманд

1
Схоже на проблему масштабування .. це aглобальна змінна?
msturdy

2
Код, який ви показали, має лише рядки. Якщо ви говорите про об'єкти, то декілька змінних можуть посилатися на один і той же об'єкт, і тому цей об'єкт може бути мутованим за допомогою будь-якої зі змінних. Тому, не бачачи більше коду, який маніпулює змінними (звідки він $.parseJSON()потрапляє?), Важко сказати, в чому проблема. Що стосується правил вашого робочого місця, то вам не потрібно публікувати фактичний реальний код повністю, просто придумайте коротший та загальніший приклад, який демонструє проблему (і, в ідеалі, включіть посилання на реальну демонстрацію на jsfiddle.net ) .
nnnnnn

Відповіді:


198

Важливо розуміти, що =робить і чого не робить оператор у 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
    }
};

Цього можна уникнути за допомогою глибокої копії. Глибока копія повторюється в кожен вкладений об'єкт і масив (і Дата в коді Арманда), щоб зробити копії цих об'єктів так само, як і копію об'єкта верхнього рівня. Тож зміна copy.x.yне вплине obj.x.y.

Коротка відповідь: Якщо ви сумніваєтесь, ви, мабуть, хочете глибоку копію.


Дякую, це те, що я шукав. Будь-яким способом обійти це? У моїй ситуації це було легко виправити, оскільки я мав лише 2 властивості. Але можуть бути і більш великі предмети.
Рутвік Гангурде,

1
Відповідь Арманда має функцію клонування об’єкта, яка зробить трюк. Або оскільки ви використовуєте jQuery, ви можете використовувати вбудовану $.extend()функцію. Деталі вище. :-)
Michael Geary

Чудове пояснення Майкла!
Рутвік Гангурде

7
@MichaelGeary Це може бути хитромудрим, але чи не застосовується правило лише до об'єктів, а не примітивних змінних? у відповіді, можливо, варто зазначити
Джонатан дос Сантос

Рішення JS для цього - у відповіді Армандса, як сказав Майкл Гірі. =
AlexanderGriffin

57

Я виявив, що використовую роботи JSON, але слідкуйте за циркулярними посиланнями

var newInstance = JSON.parse(JSON.stringify(firstInstance));

2
Я розумію, що це трохи пізно, але чи є проблема з цим методом? Здається, це чудово працює з першої спроби.
Людство1023,

2
Це чудово майже в будь-якій ситуації, однак якщо ви працюєте з даними, які можуть бути циклічними, це призведе до збою вашої програми. деякий код для ілюстрації: let a = {}; let b = {a:a}; a.b = b; JSON.stringify(a)дасть TypeError
schu34

Це рішення чудово працює. Однак, наскільки це дорого під час обробки? Здається, це працює шляхом подвійного заперечення.
Абель Каллехо

29

це питання вже вирішено досить тривалий час, але для подальшого посилання можливим рішенням є

b = a.slice(0);

Будьте обережні, це працює правильно, лише якщо a - це невкладений масив чисел і рядків


23

newVariable = originalVariable.valueOf();

для об'єктів, які ви можете використовувати, b = Object.assign({},a);


3
для об'єктів, які ви можете використовувати, b = Object.assign ({}, a);
Kishor Patil

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.");
}

Дякую! Я бачив цю публікацію перед тим, як опублікувати нове запитання. Але я не був впевнений, чи це допоможе у моєму сценарії. Виявляється, саме це мені потрібно.
Рутвік Гангурде,

Чи не слід, крім першого, if (obj instanceof x) { ... }бути іншим, якщо?
Zac

@Zac Не в цьому випадку. Кожне if-body (що цікавить) повертає значення. (виходить із функції перед наступним, якщо)
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.


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