为啥这个非常棘手的计算属性名称函数会以它的方式工作?

Posted

技术标签:

【中文标题】为啥这个非常棘手的计算属性名称函数会以它的方式工作?【英文标题】:Why does this really tricky computed property name function works the way it does?为什么这个非常棘手的计算属性名称函数会以它的方式工作? 【发布时间】:2019-02-07 08:06:56 【问题描述】:

@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,而 increment 中的值 x 不断增加。

如果我们将const movingTarget = toString: increment ; 替换为const movingTarget = [toString]: increment ;,则此行为将不再发生,而是得到42 的输出,而increment 中的x 保持不变。

有人可以帮我理解为什么会这样吗? JavaScript 是什么让事情以这种方式工作?

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

【问题讨论】:

【参考方案1】:

让我们评估以下对象字面量:

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

【讨论】:

【参考方案2】:

让我们稍微改变一下例子来淡化复杂性。下面的例子和每次movingTarget被求值时调用toString基本是一样的,所以我们干脆去掉它,自己调用函数:

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 作为计算属性值或键时,都会调用该函数并使用其返回值。

【讨论】:

...实际上相当于weirdObjectLiteral["2"];因此undefined。但总的来说,我喜欢这个解释。 ) ooooooh 这是因为您正在重置它的 toString 功能!!! d'oh,好的,现在这对我来说很有意义! 嘿,这就是让我点击的答案,我们正在为该对象覆盖toString - 如果您编辑它以使其更准确(根据raina77ow的评论),我会接受这个。

以上是关于为啥这个非常棘手的计算属性名称函数会以它的方式工作?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 IE 会以不同的方式呈现这个 HTML 表格?

Elixir:应用程序立即退出,或者在运行 distillery 包时没有收到输入。为啥它会以这种方式工作,以及如何解决它?

为啥 Typescript 不以正确的方式支持函数重载?

元素的定位

为啥我不能从 onclick 属性调用名为 clear 的函数?

Vue.js 计算属性