Why does this really tricky computed property name

2019-05-18 17:01发布

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

2条回答
爷、活的狠高调
2楼-- · 2019-05-18 17:25

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 time movingTarget is evaluated, so we'll just get rid of it and call the function ourselves:

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

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

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

See? The first time we called func, the value it returned was 1, so the "computed" property is "1". In the second time we called func, the returned value was 2, we tried accessing that and got undefined 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 its toString method. This toString method is defined as a function that increment x and returns its new value (i.e. the inner function returned by the IIFE, the function () => ++x). So basically whenever we used movingTarget as either a computed property value or a key, that function is called and its return value was used.

查看更多
家丑人穷心不美
3楼-- · 2019-05-18 17:29

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 }
 );
查看更多
登录 后发表回答