Почему эта действительно хитрая функция имени вычисляемого свойства работает именно так?

@raina77ow недавно помог мне разобраться в именах вычисляемых свойств. В рамках их ответа на мой вопрос они поделились действительно сложным кодом, демонстрирующим интересные аспекты JavaScript:

const increment = (() => { let x = 0; return () => ++x })();
const movingTarget = { toString: increment };
const weirdObjectLiteral = { [movingTarget]: 42 };
console.log( weirdObjectLiteral[movingTarget] ); // undefined

Когда я запускаю этот пример в интерфейсе командной строки узла, эта последняя строка постоянно выводит undefined, а значение x в increment постоянно увеличивается.

Если мы заменим const movingTarget = { toString: increment }; на const movingTarget = { [toString]: increment };, такое поведение перестанет иметь место, и вместо этого мы получим вывод 42, а x в increment останется прежним.

Может ли кто-нибудь помочь мне понять, почему это так? Что такого в JavaScript, что заставляет все работать именно так?

Похожий вопрос: существует ли x в функции внутри increment, пока мы явно не удалим increment из памяти?


person thisissami    schedule 01.09.2018    source источник


Ответы (2)


Давайте оценим следующий литерал объекта:

 {[toString]: increment }

toString - это идентификатор, указывающий на window.toString (функцию). Как указано в ответе, toString будет вызываться, поскольку ключи объекта всегда являются строками:

 {[toString.toString()]: increment }

Теперь это приводит к чему-то вроде:

 {["function() { [native code] }"]: increment }

Теперь, если мы вызовем toString() для этого объекта, стандартный Object.prototype.toString будет вызван в части {[movingTarget]: 42}, и результат будет [Object object] (как всегда):

 let x = 0;
 let movingTarget = { ["function() { [native code] }"]: () => ++x };

 console.log(
  movingTarget.toString(), // [Object object]
  {[movingTarget]: 42} // {["[Object object]"]: 42}
 );

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

 let x = 0;
 let movingTarget = { toString: () => ++x };
 console.log(
   movingTarget.toString(), // 1
   "" + movingTarget, // 2
  {[movingTarget]: 42} // { 3: 42 }
 );
person Jonas Wilms    schedule 01.09.2018

Давайте немного разбавим усложнение, немного изменив пример. Следующий пример в основном аналогичен тому, что toString вызывается каждый раз, когда вычисляется movingTarget, поэтому мы просто избавимся от него и вызовем функцию сами:

let x = 0;
let func = () => ++x;

const weirdObjectLiteral = { [func()]: 42 };   // equivalent to weirdObjectLiteral = { "1": 42 }

console.log( weirdObjectLiteral[func()] );     // equivalent to weirdObjectLiteral["2"]

Видеть? В первый раз, когда мы вызвали func, оно вернуло значение 1, поэтому "вычисляемое" свойство равно "1". Во второй раз, когда мы вызвали func, возвращенное значение было 2, мы попытались получить к нему доступ и получили undefined, потому что нет свойства "2".

Как это связано с примером в вопросе?

Это связано с тем, что в исходном коде мы используем movingTarget как значение вычисляемого свойства и как ключ для доступа к этому свойству. Поскольку оба они ожидают строки, movingTarget преобразуется в строку, вызывая его метод toString. Этот метод toString определен как функция, которая увеличивает x и возвращает новое значение (то есть внутреннюю функцию, возвращаемую IIFE, функцию () => ++x). Таким образом, всякий раз, когда мы использовали movingTarget либо в качестве вычисляемого значения свойства, либо в качестве ключа, вызывалась эта функция и использовалось ее возвращаемое значение.

person ibrahim mahrir    schedule 01.09.2018
comment
... фактически эквивалентно weirdObjectLiteral["2"]; следовательно undefined. Но в целом объяснение мне нравится. ) - person raina77ow; 01.09.2018
comment
оооооо, это потому что вы сбрасываете его toString функцию!!! d'oh, хорошо, теперь это имеет смысл для меня! - person thisissami; 02.09.2018
comment
Эй, так что это ответ, который заставил меня щелкнуть, что мы переопределяем toString для этого объекта - если вы отредактируете это, чтобы оно было более точным (согласно комментарию raina77ow), я приму это. - person thisissami; 02.09.2018