Как присваивание переменных работает в JavaScript?

98

Итак, я на днях поигрался, просто чтобы увидеть, как именно массовое назначение работает в JavaScript.

Сначала я попробовал этот пример в консоли:

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

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

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

Это почти то же самое, не так ли? Что ж, на этот раз он возвращается fooне так, barкак я ожидал бы от поведения первого примера.

Почему это происходит?

NB. Этот пример можно еще больше упростить с помощью следующего кода:

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

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

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

Крис Ллойд
источник
Объясняя назначение здесь: 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
«вы говорите, что теперь a указывает на другой объект». Нет, не используйте слово «объект». Строка не является объектом в JavaScript.
Носредна 04
9
Возможно, строки технически не относятся к типу javascript "Object", но их можно рассматривать как объекты в объектно-ориентированном смысле.
Alex Wayne
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 Foundation «Повторное введение в JavaScript (учебник по JS)» объекты JavaScript описываются «как простые коллекции пар имя-значение. Таким образом, они похожи на ...», затем следует список словарей, хэш , хеш-таблицы и хеш-карты из разных языков программирования. На той же странице описаны ссылки на свойства объекта как поиск по хеш-таблице. Итак, объекты - это все, как «хеш-таблица». Это не отменяет другой полезной информации, но первоначальная характеристика Криса Ллойда не была неточной.
C Perkins
2

Вы более или менее правы, за исключением того, что то, что вы называете «хешем», на самом деле является сокращенным синтаксисом объекта.

В первом примере a и b оба относятся к одному и тому же объекту. Во втором примере вы меняете a, чтобы ссылаться на что-то еще.

Кевин
источник
Так почему двойные стандарты для Object?
Крис Ллойд,
Это не двойной стандарт. В первом примере a и b по-прежнему относятся к одному и тому же объекту, вы просто изменяете свойство этого объекта. Во втором примере вы указываете на другой объект.
Кевин,
1
Нет, разница в том, что во втором случае вы имеете дело со строкой, а не с объектом.
Носредна 04
1
Чтобы быть ясным: это не имеет ничего общего со строками, возвращающими свою копию. Причина, по которой эти два фрагмента кода отличаются, находится во втором абзаце Кевина (более подробно объясненном в ответе Squeegy).
Чак
Не имеет значения, есть ли у вас в переменной строка или объект. Вы присваиваете новое, другое значение, а затем переменная содержит это новое, другое значение.
чт,
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 продолжал указывать на старый строковый объект.

mdm
источник
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
Примитивы ведут себя как неизменяемые объекты ( точно так же, как они в строгом режиме). Это неверный ответ.
Ry-