屌丝 JavaScript 之怪异的框框 []
你能一下子就说出下面这段代码的值吗?
++[[]][+[]]+[+[]]
如果我告诉你,这段代码的值是 10 你会惊讶吗?
其实我一开始也惊讶了,不过仔细想了想好像也没什么。
不过我觉得有时候可以用这种形式的代码来实现某些“隐藏值”,或者是纯粹为了装逼。
因为这样往往能让初次碰到的人大吃一惊,可能还会对你顶礼膜拜哦。
扯远了,其实这段代码的关键在于————弱类型语言的自动类型转换
首先我们将上面的那段代码拆分成两段
++[[]][+[]]
+[+[]]
然后我们提取出其中的
+[] // 这是将要被转换的代码
js会把 +[] 转换为 0,注意类型为整形,而非字符型。通过下面的方式可以证明
+[] === 0 // 返回 true
+'' === 0 // 同样返回 true
+[7] === 7 // 还是返回 true
那么上面的代码就等同于
++[[]][0]
+[0]
再来看 [[]][0],很多人对此的解释是————这等同于取出数组的第一个元素,也就是取出了 []。
这个也对也不对,主要是没有人对下面这段代码作出过详细的解释
++[] // 出错
++[]+[0] // 还是出错
那么为什么 ++[[]][0] 能返回整形的 1,而 ++[] 就会报错呢?
首先,我们来看 ++[] 的错误信息
ReferenceError: Invalid left-hand side expression in prefix operation
// 引用错误:前缀操作中的左侧表达式无效。
这个错误是引用规范中提到的抽象操作 PutValue (V, W) 被调用时抛出的。
当 ++[] 执行时,PutValue 就会被调用。
PutValue 要求要有一个引用,而 ++[] 中的 [] 本身只是一个表达式,而不是一个引用,
因此会报错。这是非常好理解的,我们可以设想一下 ++[] 会在 [] 的基础上加 1,
假如 [] 的内存地址是 9527 但是新的值存放在哪里呢?不可能直接替换掉 9527 吧?
所以必须用一个引用来指向这个新值的内存地址(姑且假设新值的内存地址为 9528 吧)。
那么我们加上引用再试一下
var a = [];
++a; // 返回结果 1
由此可以证明确实是缺少引用的问题。但是为什么 ++[[]][0] 不会报错呢?
这个就是我为什么说前面的说法也对也不对了。
[[]] 是一个对象这个大家肯定都没有疑问,而 [[]][0] 显然是访问了 [[]] 这个
对象的第一个属性吧?
那么也就是说 “0” 引用了 [[]] 中的第一个属性 []。既然引用有了那么接下来的
操作就和 var a = []; 没有什么分别了。
++[[]][0] === 1 // 返回 true
1 + [0] // 返回 "10"
最后再回过头来说说 js 自动类型转换的一些规则,由此来解释
为什么 1 + [0] 结果会是 10.
首先是 ++ 的问题,对于引用的是对象的这种,比如前面的 “0” 引用 [],或者
a 引用 [],会执行以下步骤的操作
a.toString(); // 返回 "" ,一个空字符串
++""; // 返回整形的 1
接下来是 1 + [0] 为什么是 “10” 而不是 1 的问题了。
我们前面说了 +[] 和 +[0] 得到的是整形的 0,但是这里为什么会变成
了字符型的 “0”。
原因很简单 +[] 的时候 + 不是表示加号,而是表示正负的正号。
[] 是一个对象,会调用 toString() 转换为 “”.
而 +”” 会得到结果整形的 0。
而这里的 1 + [0] 究竟是加号还是连字符
这要取决于 [0] 的转换结果。
[0].toString(); // 返回的结果为字符型的 "0"
1 + "0" // 返回字符串 "10"
这个是因为数字 + 字符串时,+ 会作为连字符对待,把表达式的两个因子拼接到一起。
OK,至此算是把这段代码的前世今生分析清楚了,阿弥陀佛。。。。
更多框框里个框框,请参见Java/script: no alnum cheat sheets
关于引用的说明请查看引用规范.
关于js的类型转换,请参考Javascript类型转换的规则中的–隐式转换.