Многократная путаница заданий
Я понимаю, что оператор присваивания является правильным ассоциативным.
Итак, например, x = y = z = 2
эквивалентно (x = (y = (z = 2)))
В этом случае я попробовал следующее:
foo.x = foo = {a:1}
Я ожидал, что объект foo
будет создан со значением {a:1}
, а затем свойство x
будет создано на foo
, которое будет просто ссылкой на объект foo
.
(На самом деле это происходит, если я должен был отделить оператор множественного присваивания от двух отдельных операторов foo = {a:1};foo.x = foo;
)
Результат был фактически:
ReferenceError: foo не определен (...)
Итак, я попробовал следующее:
var foo = {};
foo.x = foo = {a:1};
Теперь я больше не получаю исключение, но foo.x
есть undefined!
Почему назначение не работает так, как я ожидал?
Отказ от ответственности. Вопрос "дубликат" кажется очень отличным от того, который я задаю, поскольку проблема заключается в том, что переменные, которые были созданы в присваивании, были глобальными, поскольку они были связаны с созданными переменными с ключевым словом
var
. Это не проблема.
Ответы
Ответ 1
Существует важное различие между ассоциативностью и порядком оценки.
В JavaScript, хотя оператор присваивания имеет право справа налево, операнды оцениваются слева направо до того, как выполняются фактические назначения (которые происходят справа налево). Рассмотрим этот пример:
var a = {};
var b = {};
var c = a;
c.x = (function() { c = b; return 1; })();
Переменная c
первоначально ссылается на a
, но правая часть присваивания устанавливает c
в b
. Какое свойство присваивается, a.x
или b.x
? Ответ a.x
, потому что левая сторона сначала оценивается, когда c
все еще ссылается на a
.
В общем случае выражение x = y
оценивается следующим образом:
- Оцените
x
и запомните результат.
- Оцените
y
и запомните результат.
- Назначить результат с шага 2 на результат шага 1 (и вернуть его в результате выражения
x = y
).
Что происходит с несколькими назначениями, как в x = (y = z)
? Рекурсия!
- Оцените
x
и запомните результат.
- Оцените
y = z
и запомните результат. Сделать это:
- Оцените
y
и запомните результат.
- Оцените
z
и запомните результат.
- Назначить результат с шага 2.2 для результата этапа 2.1 (и вернуть его в результате выражения
y = z
).
- Назначить результат с шага 2 на результат шага 1 (и вернуть его в результате выражения
x = (y = z)
).
Теперь посмотрим на ваш пример, слегка отредактированный:
var foo = {};
var bar = foo; // save a reference to foo
foo.x = (foo = {a:1}); // add parentheses for clarity
foo.x
оценивается до foo
присваивается {a:1}
, поэтому свойство x
добавляется к исходному объекту {}
(который вы можете проверить, изучив bar
).
Ответ 2
Отредактирован ответ, чтобы сделать его простым
Прежде всего, вы должны понимать разницу между Reference - и Value -.
var foo = {};
foo
переменная содержит ссылку на объект в памяти, скажем A
Теперь есть два вида аксессуаров: Variable Accessor и Property Accessor.
So foo.x = foo = {a:1}
можно понимать как
[foo_VARIABLE_ACCESSOR][x_PROPERTY_ACCESSOR] = [foo_VARIABLE_ACCESSOR] = {a:1}
!!! Цепочка аксессуаров сначала оценивается, чтобы получить последний аксессор, который затем оценивается ассоциативно.
A['x'] = foo = {a:1}
Аксессор свойств разделен на сеттеры и геттеры
var foo = { bar: {} };
foo.bar.x = foo = {a:1}
Здесь где деканированы два вложенных объекта foo
и bar
. В памяти у нас есть два объекта A
и B
.
[foo_VAR_ACCESSOR][bar_PROP_GETTER][x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> A[bar_PROP_GETTER][x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> B[x_PROP_ACCESSOR] = [foo_VAR_ACCESSOR] = {a:1}
> B['x'] = foo = {a: 1}
Здесь у вас мало примеров
var A = {};
var B = {}
Object.defineProperty(A, 'bar', {
get () {
console.log('A.bar::getter')
return B;
}
})
Object.defineProperty(B, 'x', {
set () {
console.log('B.x::getter')
}
});
var foo = A;
foo.bar.x = foo = (console.log('test'), 'hello');
// > A.bar.getter
// > test
// > B.x.setter
Ответ 3
Отличный вопрос. Здесь нужно помнить, что JavaScript использует указатели для всего. Легко забыть об этом, так как невозможно получить доступ к значениям, представляющим адреса памяти в JavaScript (см. этот вопрос SO). Но осознание этого очень важно для понимания многих вещей в JavaScript.
Итак, утверждение
var foo = {};
создает объект в памяти и присваивает указатель этому объекту foo
. Теперь, когда этот оператор работает:
foo.x = foo = {a: 1};
свойство x
фактически добавляется к исходному объекту в памяти, а foo
получает назначенный указатель на новый объект, {a: 1}
. Например,
var foo, bar = foo = {};
foo.x = foo = {a: 1};
показывает, что если foo
и bar
сначала указывают на один и тот же объект, bar
(который все равно укажет на этот исходный объект) будет выглядеть как {x: {a: 1}}
, а foo
- просто {a: 1}
.
Так почему же foo
выглядит как {a: 1, x: foo}
?
В то время как вы правы в том, что присвоения являются правильными ассоциативными, вы также должны понимать, что интерпретатор все еще читает слева направо. Возьмем подробный пример (с некоторыми абзацами):
var foo = {};
Хорошо, создайте объект в ячейке памяти 47328 (или что-то еще), присвойте foo
указателю, указывающему на 47328.
foo.x = ....
Хорошо, возьмите объект, который foo
в настоящее время указывает на в ячейке памяти 47328, добавьте к нему свойство x
и приготовьтесь присвоить x
ячейке памяти любого следующий следующий.
foo = ....
Хорошо, возьмите указатель foo
и приготовьтесь назначить его в ячейку памяти следующего следующего.
{a: 1};
Хорошо, создайте новый объект в памяти в местоположении 47452. Теперь верните цепочку: Назначьте foo
, чтобы указать на ячейку памяти 47452. Назначьте свойство x
объекта в ячейке памяти 47328, чтобы также указать на то, что foo
теперь указывает на - ячейку памяти 47452.
Короче говоря, нет сокращенного способа делать
var foo = {a: 1};
foo.x = foo;