在阅读这篇发布在 dzone 上的文章 时,我发现了一段 JavaScript 片段,最初由 Marcus Lagergren 发布在 Twitter 上。
下面的代码显然打印了字符串
"fail"
(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]];
这涉及到隐式类型转换,我试图理解这行代码是如何被解释的。
我已经隔离了每个角色
(![]+[])[+[]]
版画 "f"
(![]+[])[+!+[]]
版画 "a"
([![]]+[][[]])[+!+[]+[+[]]]
版画 "i"
(![]+[])[!+[]+!+[]]
版画 "l"
我还设法分解了返回每个字母的表达式,除了
"i"
"f"
![]
空数组是一个对象,根据 ECMAScript 文档,点 9.2 在转换为 true
时计算为 boolean
所以这是 false
false+[]
根据 Point 11.6.1 二元运算符 +
的两个参数都转换为字符串,因此我们得到 "false"+""
,它计算 "false"
+[]
如果参数是 ToNumber
,一元加运算符会导致 ToPrimitive
转换,然后是 Object
转换。这种转换的结果是通过调用对象的[[DefaultValue]]
内部方法来确定的。如果是空数组,则默认为0
。
(ECMAScript 文档,部分:11.4.6、9.3、9.1)
"false"[0]
我们正在访问索引0
处的字符,因此"f"
"a"
同一个故事,这里唯一的区别是方括号中的部分进行了额外的转换(计算为一个数字以指向字符串中的另一个字符
"false"
),由使用一元+
和!
运算符触发.
+[]
的计算结果为 0
,如上所述。
!0
的计算结果为 true
,如 Section 9.2 和 Section 11.4.9 中所定义。首先,0
被转换为布尔值 false
然后运算符反转值。
+true
再次,一元加号触发 ToNumber
转换,返回二进制 1
的 true
(第 11.4.6 和 9.3)
"false"[1]
返回字符串中的第二个字符,即 "a"
!+[]
评估为 true
如上所述
true+true
在基元上使用二进制 +
会触发 ToNumber
转换。如果为真,它的结果是1
和1+1
等于2
"false"[2]
- 不言自明
"i"
让我难过的是字母
"i"
。我可以看到第二部分(在方括号中)计算为字符串 "10"
并且第一部分(在括号中)返回 "falseundefined"
但是 我无法弄清楚这是如何发生的。有人可以一步一步解释吗? 尤其是方括号发生的魔法? (数组和数组访问)
如果可能的话,我希望每个步骤都包含一个指向底层 ECMAScript 规则的链接。
我觉得最神秘的是这部分:
[][[]]
如果你稍微重写一下,你的神秘部分就不会那么神秘了:
[]['']
[]
将被强制转换为字符串,因为它不是整数,因此您正在寻找 []
的属性,其名称为 ''
(空字符串)。你只会得到undefined
,因为没有具有该名称的属性。
至于实际的字母,将表达式分解为两个主要部分:
([![]]+[][[]])
:
[![]]
是[false]
.[][[]]
是undefined
."falseundefined"
.[+!+[]+[+[]]]
。一些空格和括号将使操作更加清晰:[+(!(+[])) + [+[]]]
:
[+[]]
是[0]
.+[]
强制 []
为整数,所以你得到 0
.!+[]
将 0
强制转换为布尔值并将其取反,因此您得到 true
.+!+[]
强制 true
为整数,所以你得到 1
.["10"]
.当使用字符串访问数组的属性并且字符串恰好是数组的元素时,字符串被强制转换为整数,你得到数组的实际元素:
> [1, 2, 3]["0"]
1
> [1, 2, 3]["1"]
2
所以你最后的结果是:
> "falseundefined"["10"]
"i"
阅读这个答案以获得
[false] + undefined
部分的解释。
([![]]+[][[]])[+!+[]+[+[]]]
有两部分:
([![]]+[][[]])
和你自己找到的另一个。
![]
返回false
。然后我们使用[...]
来获得.toString()
的+
行为。
([]+[]
与[].toString()+[].toString()
相同)
[][[]]
未定义,因为我们试图访问未定义的 []
的索引 [].toString()
(或 ''
,即 []
)。