@raina77ow recently helped me figure out computed property names. As part of their answer to my question, they shared a really tricky bit of code showcasing interesting aspects of JavaScript:
const increment = (() => { let x = 0; return () => ++x })();
const movingTarget = { toString: increment };
const weirdObjectLiteral = { [movingTarget]: 42 };
console.log( weirdObjectLiteral[movingTarget] ); // undefined
When I run that sample in the node CLI, that last line continually outputs undefined
, while the value x
in increment
continually increments.
If we replace const movingTarget = { toString: increment };
with const movingTarget = { [toString]: increment };
, this behaviour ceases to take place, and instead we get an output of 42
and the x
in increment
remains the same.
Can somebody help me understand why this is the case? What is it about JavaScript that makes things work this way?
Related Question: Does the x
in the function within increment
exist until we explicitly remove increment
from memory?
Let's dilute the complication a little bit by slightely changing the example. The following example is basically the same thing as
toString
is called every timemovingTarget
is evaluated, so we'll just get rid of it and call the function ourselves:See? The first time we called
func
, the value it returned was1
, so the "computed" property is"1"
. In the second time we calledfunc
, the returned value was2
, we tried accessing that and gotundefined
back because there is no property"2"
.How is this related to the example in the question?
It is related because in the original code we are using
movingTarget
as both the value of the computed property and the key to access that property. Since both of them are expecting strings,movingTarget
is coerced into a string by calling itstoString
method. ThistoString
method is defined as a function that incrementx
and returns its new value (i.e. the inner function returned by the IIFE, the function() => ++x
). So basically whenever we usedmovingTarget
as either a computed property value or a key, that function is called and its return value was used.Lets evaluate the following object literal:
toString
is an identifier pointing towindow.toString
(a function) As outlined by the answer,toString
will be called on that as object keys are always strings:Now that results in something like:
Now if we call
toString()
on this object, the standardObject.prototype.toString
will get called in the{[movingTarget]: 42}
part and the result is[Object object]
(as always):thats why the moving target isnt moving anymore. In the original code,
toString
of the object was set, and that gets called whenevermovingTarget
gets turned into a string: