+[] 也是一段 JS 代码。 运行的结果是什么呢?

求值

+[] 的运行结果是 0 。 如果不相信,按 F12 打开浏览器的调试器,按 ESC 调出控制台。输入 +[] ,回车就可以看到结果。

结果为什么这么反直觉?

想知道运算过程中发生了什么,一个最好不过的方式就是去看语言的规范。

比如想看 + 操作符的运算过程,打开 JS 规范

转到一元 + 操作符 这一节。

The unary + operator converts its operand to Number type.
The production UnaryExpression : + UnaryExpression is evaluated as follows:

  1. Let expr be the result of evaluating UnaryExpression.
  2. Return ToNumber(GetValue(expr)).

也就是求 ToNumber(GetValue([])) 。可以简单地认为求的是 ToNumber(false)

false 转成数字自然是 0 。

所以 +[] 的结果为 0 。

全家福

1
[][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][[]]+[])[+[]]+([][[]]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])()(([]+{})[+[]])[+[]]+(!+[]+!![]+!![]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[]))+([][[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]

这段代码的输出结果是 “Hello” 。是不是有点不可思议?代码中没有包含任何 “Hello” 字母。

JS 可以做到没有任何字母和数字就能编写代码。

只需要用到 !+[]{}() 8个字符就可以编写大部分代码。这样没有数字字母的 JS 代码称为 non-alpha Javascript

数字

non-alpha Javascript 是怎么做到没有数字字母也能写代码的呢?

其实本质是将数字字母转化为上述的八个字符。

上面分析了 +[] 等于 0

那么如果是 2 ,5 ,8 这种数字该怎么表达呢?

光用 0 是没有办法得到其他数字的。

1 + 1 = 2 。

所以只需要再得到 1 即可得到其他所有数字。

很容易联想到 false 对应的是 0 ,那么 true 对应的应该是 1 。

怎么得到 true ?已有的条件是 false 。那只需要取反即可。

也就是 !false 。在 JS 中,[] 被当作 false 。所以带入前式得 ![]

还要使用 +![] 将结果转为数字。

所以 +![] 就会得到 1 。

要是这么想就大错特错了。JS 可不是凡人能够理解的。

+![] 的结果是 0 ,跟 +[] 一样。

为什么是这样哪里出错了?

看代码就清楚了

1
2
3
4
5
6
7
8
9
10
11
[] == false
// => true

![] == false
// => true WTF???

!![] == []
// => false WTFF???

!![] == true
// => true WTFFF???

这里面是有深坑的(捂眼痛哭),我比较懒就暂时不深究了。

从上面可以看到 !![] == true 。所以 +true 等于 !![]

使用 + 将结果强制转化为数字。

+!![] 的结果就是 1 。

所以 2 = 1 + 1

也就是 2 = +!![] + +!![]

利用进行 + 运算时只要一边是整数类型,就会把另一边也强制转化为整数类型,上式可以简化为

2 = !![] + !![]

1 的表达式不是唯一的。

注意到 !0 == true

所以 +!0 == 1

也就是 +!+[] 的值为 1

所以 2 还可以表示为 +!+[] + +!+[]

简化一下得 !+[] + !+[]

现在知道 JS 有多灵活凌乱了吧。

类似的可以得到所有数字

数字 JS
0 +[]
1 !+[]
2 !+[] + !+[]
3 !+[] + !+[] + !+[]
4 !+[] + !+[] + !+[] + !+[]
5 !+[] + !+[] + !+[] + !+[] + !+[]
6 !+[] + !+[] + !+[] + !+[] + !+[] + !+[]
7 !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[]
8 !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[]
9 !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[]

现在已经拥有所有数字了。假设想得到 12 。用 9 + 4 即可。

字母

如果说得到数字的过程还算正常的话,那么下面介绍得到字母的方法只能说是奇技淫巧。

基本的思想是从 JS 内置的关键字中按索引得到单一的字母。

比如 JS 有一个 NaN 表示非数字。

NaN 中可以得到 Na

想要得到 a 只需要 "NaN"[1]

"NaN" 等于 +{}+[]

所以 "NaN"[1] 等于 (+{}+[])[1]

因为 1 等于 +!+[]

所以最后结果是 (+{}+[])[+!+[]]

类似地,从 true 中可以得到 t,r,u,e

undefine 可以得到 n,d,f,i

还有一种获取字母的通用方法

至此已经获得了所有的数字字母。可以愉快的编程了。

后记

non-alpha javascript 也不知道是谁先想出来的。真的是太有才了。有的人用它绕过 XSS 的过滤。这种思路写过滤器的人估计也没考虑到吧。也从侧面证明了 JS 的类型转化太灵活了,存在太多种特例。平时写代码要避免这些奇怪的边角情况。