Search code examples
javascriptecmascript-6computed-properties

Why does this really tricky computed property name function works the way it does?


@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?


Solution

  • Lets evaluate the following object literal:

     {[toString]: increment }
    

    toString is an identifier pointing to window.toString (a function) As outlined by the answer, toString will be called on that as object keys are always strings:

     {[toString.toString()]: increment }
    

    Now that results in something like:

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

    Now if we call toString() on this object, the standard Object.prototype.toString will get called in the {[movingTarget]: 42} part and the result is [Object object] (as always):

     let x = 0;
     let movingTarget = { ["function() { [native code] }"]: () => ++x };
    
     console.log(
      movingTarget.toString(), // [Object object]
      {[movingTarget]: 42} // {["[Object object]"]: 42}
     );
    

    thats why the moving target isnt moving anymore. In the original code, toString of the object was set, and that gets called whenever movingTarget gets turned into a string:

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