为什么这个真正棘手的计算属性名称函数按其方式工作?

时间:2018-09-01 16:04:27

标签: javascript ecmascript-6 computed-properties

@ raina77ow最近帮助我弄清楚了计算出的属性名称。作为their answer to my question的一部分,他们共享了一些非常棘手的代码,这些代码展示了JavaScript的有趣方面:

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

当我在节点CLI中运行该示例时,最后一行持续输出undefined,而x中的值increment连续递增。

如果我们将const movingTarget = { toString: increment };替换为const movingTarget = { [toString]: increment };,此行为将停止发生,取而代之的是,我们在42中得到xincrement的输出保持原样。

有人可以帮助我理解为什么会这样吗? JavaScript使事情以这种方式起作用的原因是什么?

相关问题:在我们将x从内存中明确删除之前,increment中函数中的increment是否存在?

2 个答案:

答案 0 :(得分:3)

让我们评估以下对象文字:

 {[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 }
 );

答案 1 :(得分:2)

让我们稍微改变一下例子,就可以使并发症减轻一点。以下示例基本上与每次对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用作计算的属性值或键,就会调用该函数并使用其返回值。