为什么在JavaScript中,!{}[true]的结果是true?

131

{}[true]表示[true],而![true]应该是false

那么为什么!{}[true]会被计算为true呢?


30
变量o被定义为一个空对象。当使用布尔值true作为属性名时,o[true]的值为undefined。 - azz
2
这里的解释可能与此前一个问题讨论的奇怪现象非常相似。 - IMSoP
45
"因为 JavaScript 很傻" 可能不是你想要的答案。 - georg
2
如上所述,如果您从控制台获取{}[true] === [true],那是因为它将{}视为一个空代码块,而不是对象。 - azz
3
如果有帮助的话,尝试在控制台中比较 {}({})(或者比较 {} 中的 [true]({}) 中的 [true])。另外,由于没有人提到,object[true] 会被计算为 object["true"] - BiAiB
显示剩余5条评论
10个回答

173

我认为这是因为纯粹的{}[true]被解析为一个空语句块(而不是对象文字),其后跟一个包含true的数组,该数组的值为true

另一方面,应用!运算符会使解析器将{}解释为一个对象字面量,因此以下{}[true]变成了一个返回undefined的成员访问操作,而!{}[true]确实是true(因为!undefinedtrue)。


25
然而,!undefined为真的事实仍然是不可原谅的。 - evilcandybag
87
这不是绝对的。undefined 是假值(我们经常依赖它,如 if (obj.maybeExists) ...),因此 !undefined 成立在逻辑上也是完全合理的。 - josh3736
8
@Josh,我认为evilcandybag更喜欢某些语言中类似于“null”的行为,其中!undefined等于undefined。 然而在JavaScript中不是这种情况。 - Frédéric Hamidi
6
只说某个东西是非未定义(!undefined)的,意义上来讲就表示它被定义了。如果某个东西被定义了,那么通常它被解释为true - OozeMeister
7
如果a和b都没有定义,我们怎么可能知道a不等于b呢?特别是当这两个变量唯一已知的特征恰好相同时。 - LJ2
显示剩余10条评论

44

因为{}[true]并不会返回true,而是返回undefined,而undefined会被视为false

http://jsfiddle.net/67GEu/

'use strict';
var b = {}[true];
alert(b); // undefined
b = !{}[true];
alert(b); // true

21
如果您在控制台中评估{}[true],则会得到[true],因为{}被解释为一个空代码块,而不是一个对象。这取决于上下文和{}的歧义性。 - IMSoP
1
@IMSoP 但是为什么{key:"value"}[1,2,3];也会得出[1,2,3]的结果呢? - t.niese
3
@t.niese,这是解析为一个语句块的标签(key:)和字符串字面值("value"),后跟一个数组。解析器仍未看到对象字面量。 - Frédéric Hamidi
1
@FrédéricHamidi 哦,没错。我忘记了标签^^ - t.niese
1
@dooxe 读一下其他答案;这取决于其被解释的上下文。如果你将它包裹在 alert() 或者 console.log() 中,或者将它赋值给一个变量,你就改变了上下文,这就是为什么它在控制台中单独输入时不会表现出相同的行为方式。 - IMSoP
显示剩余7条评论

27

由于

{}[true]

求值为undefined!undefined的结果是true

来自@schlingel:

true被用作键,而{}被用作哈希映射。不存在一个key是true的属性,因此返回undefined。预期的结果是,undefined不是true

控制台会话(Node.js [0.10.17]):

> {}[true]
undefined
> !{}[true]
true
> [true]
[ true ]
> ![true]
false
>

然而,在 Google Chrome 控制台中:

> !{}[true]
true

因此,没有不一致性。你可能正在使用旧版本的JavaScript VM。需要更多证据的人可以看下方图片:

Enter image description here

更新

Firefox中,它也评估为true

Enter image description here


有趣,你在哪个JS引擎中进行测试? - t.niese
2
为什么要给我点踩呢?这个回答和另一个已经有8个赞的回答几乎一模一样,而我却被点了踩?我做错了什么吗? - Games Brainiac
@t.niese 是的,那就是发生的事情。 - Games Brainiac
@Wayne 是哪个控制台?;) 也许我可以添加一个火狐浏览器。 - Games Brainiac
@GamesBrainiac Chrome没问题。在Node.js中,!{}[true]会返回true,但是{}[true]会返回[true]而不是undefined - lastr2d2
显示剩余8条评论

23
混淆的原因是对你的第一次声明的误解: {}[true][true] 当你运行它时看到的结果是一种歧义的结果。 Javascript 有一套定义明确的规则来处理这种歧义,而在这种情况下,它将您看到的单个语句分成两个单独的语句。
因此,Javascript 将上面的代码视为两个单独的语句:首先是一个 {},然后是一个完全独立的 [true]。第二个语句是给你结果 [true] 的原因,第一个语句 {} 被完全忽略了。
你可以通过尝试以下内容来证明这一点:
({}[true])

将整个语句用括号括起来以强制解释器将其作为单个语句读取。

现在,您会发现语句的实际值为undefined。(这也有助于我们后面理解下一部分)

现在我们知道你问题的初始部分是一个红鱼,所以让我们转向问题的最后部分:

那么为什么!{}[true]的结果为true?

在这里,我们有同样的语句,但在前面加上了!

在这种情况下,JavaScript的规则告诉它将整个语句作为单个语句进行计算。

回顾一下当我们将早期语句包含在括号中时发生了什么;我们得到了undefined。这次,我们实际上正在做同样的事情,但在前面加上了!。因此,您的代码可以简化为!undefined,这是true

希望这解释清楚了一些。

它是一个复杂的东西,但在此要学习的教训是在控制台中评估语句时,在语句周围使用括号,以避免出现类似这样的虚假结果。


2
我不认为{}[true]无效的,只是模棱两可的。它可以被解释为“空代码块后跟数组文字”或“没有属性的对象文字,其中正在访问属性”。我不知道第一个是否在技术上属于ASI的情况(许多语言都不会在那里放置分号),但上下文敏感的解释是问题的核心。 - IMSoP
@IMSoP - 在你发表评论之前,我已经编辑了答案。 :) - Spudley
1
回答开头仍然显示“{}[true]根本无效”。 - IMSoP
此外,OP 没有说 "{}[true]true",他们说的是 "{}[true][true]",这是模棱两可陈述的两种有效解释之一。 - IMSoP

14

{}[true]undefined。要查找,请写入以下内容:

a = {};
a[true] === undefined // true

或者简单地说:

({})[true] === undefined // true
我们知道 !undefinedtrue
@Benjamin Gruenbaum的回答中:

Chrome 开发者工具执行以下操作

  try {
      if (injectCommandLineAPI && inspectedWindow.console) {
          inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
          expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}";
      }
      var result = evalFunction.call(object, expression);
      if (objectGroup === "console")
          this._lastResult = result;
      return result;
  } 
  finally {
      if (injectCommandLineAPI && inspectedWindow.console)
          delete inspectedWindow.console._commandLineAPI;
  }

基本上,它对该对象执行一个包含表达式的call。这个表达式是:

with ((window && window.console && window.console._commandLineAPI) || {}) {
    {}+{};// <-- This is your code
}
所以,正如你所看到的,表达式是直接被评估的,没有包裹括号。
更多信息可以在这个问题中找到。

10
这里的答案很好,以下是伪代码解释:
  • {}['whatever'] = 空块,NewArray('whatever') = NewArray('whatever')
  • {}[true] = 空块,NewArray(true) = NewArray(true)
  • !{}['whatever'] = 逻辑非(convertToBool(NewObject.whatever)) = 逻辑非(convertToBool(undefined)) = 逻辑非(false) = true
  • ({}['whatever']) = 分组(NewObject.whatever) = 分组(undefined) = undefined

注意:请保留html标记,但不要提供任何解释。

8

这是因为您的意思中的{}并不是Object的字面表示,而是空范围(或空代码块):

{ var a = 1 }[true] // [true] (do the same thing)

它只评估范围内的代码,然后显示您的数组。

从你的

!{}[true]

将该范围转换为整数并返回相同的数组即可。此代码中没有布尔检查。

如果您尝试检查{}[true]的结果,您将得到false

{}[true] -> [true] -> ![true] -> false

由于没有任何余地,所以您问题中的感叹号!具有相同的作用:

!function() {
   //...
}

如果你执行 var x = {}; x[true],这将更容易看到。 - Chris Hayes
1
我不确定你所说的“converts to int this scope”是什么意思;我认为在前面加上!后,它被解释为空对象,而不是作用域,这就是差异所在。 - IMSoP

6
  • {} 是一个没有属性的对象。
  • 由于 [] 紧跟在一个对象后面,它的意思是“访问这个名称的属性”,而不是“创建一个数组”。
  • true 是一个布尔值,但被用作属性名,因此被转换为字符串 ("true")。
  • 该对象没有名为 true 的属性(因为它没有属性),所以 {}['true']undefined
  • !undefinedundefined 转换为布尔值 (false)。
  • 逻辑非运算符将 false 转换为 true

2
{}[true]的情况下(没有其他上下文),{}不是没有属性的对象,而是一个空代码块。 - IMSoP

4

让我们再玩一会儿吧!

首先,让我们来玩点有趣的东西:

//----------#01#-----------
{}[true]; //[true]

//----------#02#-----------
var a = {}[true]; 
      console.log(a); //undefined

//----------#03#-----------
{ b: 12345 }[true]; //[true]

//----------#04#-----------
{ b: 12345 }["b"]; //evaluates to ["b"] ?!?

//----------#05#-----------
{ b: 12345 }.b; // "Unexpected token ."

//----------#06#-----------
({ b: 12345 }).b; //12345

//----------#07#-----------
var c = { b: 12345 }.b; 
      console.log(c); //12345

//----------#08#-----------
var c = { b: 12345 }["b"];
      console.log(c); //12345

//----------#09#-----------
{ true: 54321 }[true]; // "SyntaxError: Unexpected token : "

//----------#10#-----------
var d = { true: 54321 }[true]; //No error here ¬¬
      console.log(d); //54321

//----------#11#-----------
!{}[true]; // true

好的,让我们逐一了解这些疯狂的行为:

1) 这里的 {} 被解析为一个空代码块。没有赋值、否定、分组(用括号)或任何表明这个 {} 是对象字面量的语法,解析器默认认为它只是一个无用的空块。

这是这种行为的证明:

{ alert(123) }[true]

上面的代码将正常显示警报,并被评估为[true],就像{}[true]一样。

没有分号的块语句

块类型的语句在其后不需要分号。

例如:

for(var i=0; i < 1; i++){}function a(){};alert("Passed here!");if(true){}alert("Passed here too!")

两个警报都会显示。

因此,我们可以看到一个空的块语句,没有分号,是有效的并且什么也不做。这样,当您在开发者工具(或Firebug)控制台中输入{}[true]时,计算出的值将是最后一个表达式语句的值。在这种情况下,最后一个表达式语句是[true]

2) 在赋值上下文中,解析器将确保{}是一个对象字面量。当您执行var a = {}[true]时,您消除了任何歧义,并提示解析器{}不是一个块语句。
因此,在这里,您正在尝试从空对象中获取具有键"true"的值。显然,没有这个键名的键值对。这样,变量a未定义。

保留字作为对象键

ECMAScript 5允许对象键为保留字。因此,以下键是合法的:

var obj = {if: 111, for: 222, switch: 333, function: 444, true: 555}

3) 同例子1的解释,但是... 如果{ b: 12345 }部分被视为块语句,那么b: 12345语句的类型是什么??

... (?????)

它是一个标签语句,你之前已经看到过它... 它在循环和switch中使用。以下是一些关于标签语句的有趣链接:1, (2)[Javascript中跳出嵌套循环的最佳方法是什么?, (3)[如何在javascript中跳出嵌套循环?

注意:只需尝试评估此内容:

{a: 1, b: 2} //=>>>SyntaxError: Unexpected token :

标签语句不能用逗号逗号操作符分隔,需使用分号分隔。因此这是有效的:{a: 1; b: 2}

4) 参见示例13的解释...

5) 再一次,我们有一个{ b: 12345 }被视为代码块,并且您正在尝试使用点表示法访问代码块的属性,显然,这是不允许的,解析器会抛出一个"Unexpected token :"异常。

6) 代码与上面的示例几乎相同,但是通过使用表达式分组运算符{ b: 12345 }语句括起来,解析器将知道它是一个对象。这样,您就可以正常访问"b"属性。

7) 记住示例2,我们在这里进行了赋值,解析器知道{ b: 12345 }是一个对象。

8) 与上面的示例相同,但是这里我们使用方括号表示法而不是点表示法。

9) 我已经说过,在块语句中,"identifier: value"语法是标签。但是,您还必须知道标签名称不能是保留关键字(对象属性名称的反义词)。当我们尝试定义一个名为"true"的标签时,会得到一个SyntaxError

10) 再次提醒,我们正在处理一个对象。在这里使用保留字没有问题。=)

11) 最后,我们有这个:!{}[true]

让我们将它们分开:

a) 通过取反操作,我们告诉解析器{}一个对象

b) 如示例2所示,{}对象没有一个名为true的属性,因此该表达式将计算为undefined

c) 最终结果是undefined值的取反。Javascript执行隐式类型转换,而undefined值为假

d) 因此,false 的否定是……true


4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接