Як працює призначення змінної в JavaScript?


97

Тож я грав ще днями, щоб точно побачити, як працює масове призначення в JavaScript.

Спочатку я спробував цей приклад у консолі:

a = b = {};
a.foo = 'bar';
console.log(b.foo);

Результатом було відображення "смужки" у попередженні. Це досить справедливо aі bнасправді є лише псевдонімами одного і того ж об’єкта. Тоді я подумав, як я можу зробити цей приклад простішим.

a = b = 'foo';
a = 'bar';
console.log(b);

Це майже те саме, чи не так? Ну, цього разу він повертається fooне так, barяк я б очікував від поведінки першого прикладу.

Чому це відбувається?

Примітка Цей приклад можна ще більше спростити за допомогою наступного коду:

a = {};
b = a;
a.foo = 'bar';
console.log(b.foo);

a = 'foo';
b = a;
a = 'bar';
console.log(b);

(Я підозрюю, що JavaScript по-різному ставиться до примітивів, таких як рядки та цілі числа, до хешів. Хеші повертають вказівник, тоді як "core" примітиви повертають копію себе)


Пояснення завдання тут: syntaxsuccess.com/viewarticle/…
TGH

Відповіді:


114

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

a = b = {};

aі bтепер вказують на той самий об’єкт. Отже, коли ви робите:

a.foo = 'bar';

Він встановлює b.fooтак само, як aі bвказує на один і той же об'єкт.

Однак!

Якщо ви робите це замість цього:

a = 'bar';

Ви говорите, що зараз aвказує на інший об’єкт. Це не впливає на те, на що aвказувалося раніше.

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

Але присвоєння властивості, як-от a.foo, змінить об'єкт, на який aвказує. Це, звичайно, також змінює всі інші посилання, які вказують на цей об’єкт, просто тому, що всі вони вказують на один і той же об'єкт.


3
"Ви говорите, що зараз вказує на інший об'єкт." Ні, не використовуйте об'єкт слова. Рядок не є об'єктом у JavaScript.
Nosredna

9
Можливо, рядки технічно не мають JavaScript типу "Object", але вони можуть думати про об'єкти в сенсі OO.
Алекс Уейн

2
@Squeegy: рядки - це примітиви, а не об'єкти: ви не можете призначити довільні властивості рядкам! Вони поводяться лише об’єктно через те, що на Java називають автобоксингом
Крістоф

11
Але рядки мають методи та властивості, і прототип String точно можна змінити. Вони впевнені, що діють як об’єкти.
Камерон Мартін

9
@ddlshack: Як пояснив Крістоф, це пов’язано з автобоксінгом. Якщо ви мали б визначити рядок через var foo = new String('foo');, то це був би об'єкт рядка (і typeofпідтвердить це). Але якщо ви оголосите це за допомогою рядкового літералу, то вони є рядковими примітивами. Дивіться: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Lèse majesté

26

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

Здається, існує велика плутанина щодо типів JavaScript у відповідях та коментарях, тому ось невелике вступ до системи типів JavaScript:

У JavaScript є два принципово різних типи значень: примітиви та об'єкти (і такого поняття, як «хеш») немає.

Рядки, числа та булеві, а також nullі undefinedпримітивні предмети - це все, що може мати властивості. Навіть масиви та функції є звичайними об'єктами, тому вони можуть містити довільні властивості. Вони просто відрізняються внутрішнім властивістю [[Class]] (функції додатково мають властивість під назвою [[Call]] та [[Construct]], але так, це деталі).

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

Ось приклад:

var a = 'quux';
a.foo = 'bar';
document.writeln(a.foo);

Це дозволить отримати undefined: aмістить примітивне значення, яке отримує рекламу об'єкта при призначенні властивості foo. Але цей новий об'єкт негайно відкидається, тому значення fooвтрачається.

Подумайте про це так:

var a = 'quux';
new String(a).foo = 'bar'; // we never save this new object anywhere!
document.writeln(new String(a).foo); // a completly new object gets created

Сторінка фонду Mozilla «Повторне введення в JavaScript (підручник JS)» описує об’єкти JavaScript «як прості колекції пар іменних значень. Як такі вони схожі на ...», далі йде зі списком словників, хеш , хеш-таблиці та хеш-карти з різних мов програмування. Ця ж сторінка описує посилання на властивості об’єктів як пошук хеш-таблиці. Таким чином, об'єкти - це все як "хеш-таблиці". Це не зводить нанівець іншу корисну інформацію, але оригінальна характеристика Кріса Ллойда не була неточною.
C Перкінс

2

Ви більш-менш правильні, за винятком того, що ви називаєте "хеш" - це насправді лише скорочений синтаксис для Об'єкта.

У першому прикладі а і b позначають один і той же об'єкт. У другому прикладі, ви міняєте послатися на що - то інше.


То чому подвійні стандарти для Object?
Кріс Ллойд

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

1
Ні, різниця полягає в тому, що у другому випадку ви маєте справу зі струною, а не об'єктом.
Носредна

1
Щоб було зрозуміло: це не має нічого спільного з рядками, що повертають копію себе. Причина, коли обидва фрагменти коду різні, знаходиться у другому абзаці Кевіна (більш детально пояснено у відповіді Скегі).
Чак

Не має значення, чи є у вас змінний рядок або об'єкт. Ви призначаєте нове, інше значення, і тоді змінна містить це нове, інше значення.
sth

2

ось моя версія відповіді:

obj = {a:"hello",b:"goodbye"}
x = obj
x.a = "bonjour"

// now obj.a is equal to "bonjour"
// because x has the same reference in memory as obj
// but if I write:
x = {}
x.a = obj.a
x.b = obj.b
x.a = "bonjour"

// now x = {a:"bonjour", b:"goodbye"} and obj = {a:"hello", b:"goodbye"}
// because x points to another place in the memory

0

Ви встановлюєте a, щоб вказувати на новий об’єкт рядка, тоді як b продовжує вказувати на старий об’єкт рядка.


0

У першому випадку ви змінюєте деяку властивість об'єкта, що міститься в змінній, у другому випадку ви присвоюєте нове значення змінній. Це принципово різні речі. Змінні aі bне якимось чином магічно пов'язані першим призначенням, вони просто містять один і той же об'єкт. Так само і у другому прикладі, поки ви не призначите bзмінному нове значення .


0

Різниця між простими типами та об'єктами.

Все, що є об'єктом (наприклад, масив або функція), передається шляхом посилання.

Копіюється все, що є простим типом (наприклад, рядок або число).

У мене завжди зручна функція copyArray, тому я можу бути впевнений, що я не створюю псевдонімів для того ж масиву.


Різниця не помітна у багатьох сценаріях, але Javascript насправді не передає та не призначає за посиланням. Він копіює опорні значення.
Хуан Пабло Каліфано

Ці хлопці вже зробили гарну роботу, пояснивши це, тому я просто вставлю посилання: stackoverflow.com/questions/40480/is-java-pass-by-reference (я маю на увазі Java, але семантика для проходження та призначення Значення / посилання такі ж, як у Javascript)
Хуан Пабло Каліфано

1
Насправді ця відповідь невірна, все передається за значенням у JavaScript. З MDN, "Параметри виклику функції - це аргументи функції. Аргументи передаються функціям за значенням. Якщо функція змінює значення аргументу, ця зміна не відображається глобально або в функції виклику. Однак посилання на об'єкти є значення теж є спеціальними: якщо функція змінює властивості згаданого об'єкта, ця зміна видно поза функцією. " developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bittersweetryan

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