angqiuli 2019-12-28
你可能在网上见过有人用 几个不同的字符写的各种稀奇古怪的 JavaScript 代码,虽然看起来奇怪,但是能正常运行!比如这个:
(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]] 复制代码
你猜运行结果是什么?你可以自己去控制台试一下。
看起来很神奇,但这到底是怎么回事呢?
事实上,你几乎可以用下面这 6 个字符写出任意的 JavaScript 程序:
[]()!+ 复制代码
很多人都知道这个技巧,但是没有多少开发人员知道它到底是如何工作的。今天,我们就来看看它背后的执行原理。我们的目标是用这几个字符来写出字符串“self”
。姑且用这个字符串向 Self 语言致敬,JavaScript 的灵感来源之一就是它。
我们之所以能够抛开其他字符不用,要归功于 JavaScript 的类型系统和数据类型转换机制。
这 6 个字符是这样各显神通的:[]
可以用来创建数组,!
和+
可以在数组上执行一些操作,再用()
给这些操作分组。
先看一个简单的数组:
[] 复制代码
数组前加上!
会把它转成布尔值。数组被认为是真值,因此取非之后变成了false
:
![] === false 复制代码
除非转换为类似类型,否则无法将不同类型的值加在一起。JavaScript 在进行转换时遵循一个预定义的规则:
在表达式2 + true
中,JavaScript 会将true
转成数字,得到表达式2+1
。
在表达式2 + "2"
中,JavaScript 会将数字转成字符串,得到2 + "2" === "22"
。
这些转换规则还不算糟糕,但是对于其他类型,好戏马上来了。
数组相加会转换成字符串并连接起来。空数组转换为空字符串,因此将两个数组相加将得到空字符串。
[] + [] === "" + "" === "" 复制代码
数组跟其他类型值相加时也一样:
![] + [] === "false" + "" === "false" 复制代码
惊不惊喜?我们得到了目标字符串"self"
所包含的几个字符!
如果我们能产生一些数字,就可以按正确的顺序提取所需的字符:
"false"[3] === "s" (![] + [])[3] === "s" 复制代码
那么,如何生成数字呢?
前面提到了,可以把数组转成布尔值。那如果用加号+
把它转成数字会怎样?
+[] === ??? 复制代码
JavaScript 会尝试调用数组的valueOf
方法,但是发现不存在这个方法,然后就转而调用toString()
方法了。因此上面的代码等效于:
+[] === +"" 复制代码
将字符串转换为数字将产生以下结果:
+"42" === 42 +"esg" == NaN +"" === 0 复制代码
空字符串是一个 false
值,跟 null,undefined和数字零类似,因此将其中任何一个转换为数字都会变成零:
+null === 0 +undefined === 0 +false === 0 +NaN === 0 +"" === 0 复制代码
因此,将数组转换为数字需要先将其转换为字符串,最后转成 0:
+[] === +"" === 0 复制代码
第一个数字已经造出来了!我们还需要更多数字,继续:
!0 === !false !false === true !0 === true 复制代码
将 0
取否就得到一个为真的布尔值。为真的布尔值转成数字,就是1
:
+true === 1 复制代码
有了 1
,自然就可以得到2
,所谓道生一,一生二,二生三,三生万物……
用上面的转换大法,可以轻松得到我们想要的这些数字:
1 === +true == +(!0) ==== +(!(+[])) === +!+[] 1 === +!+[] 2 === +!+[] +!+[] 3 === +!+[] +!+[] +!+[] 4 === +!+[] +!+[] +!+[] +!+[] 复制代码
总结下这些规则:
false
: ![] // false
[] + [] // ""
0
,再去否得到 true
,再转成数字得到1
:+(!(+[])) === 1
根据这些规则,我们就能得到想要的字符串。看下面这个示意图就很清楚了:
![] + [] === "false" +!+[] === 1 (![] + [])[3] + (![] + [])[4] + (![] + [])[2] + (![] + [])[0] ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ "false" "false" "false" "false" ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ s e l f 复制代码
最终的表达式就是这样:
(![] + [])[+!+[]+!+[]+!+[]] + (![] + [])[+!+[]+!+[]+!+[]+!+[]] + (![] + [])[+!+[]+!+[]] + (![] + [])[+[]] 复制代码
整理下空格和换行,就是一行代码:
(![]+[])[+!+[]+!+[]+!+[]]+(![]+[])[+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+!+[]+!+[]]+(![]+[])[+[]]