心形表白代码
情人节的时候,在推特上看到了一个很神奇的代码,整个代码构成了几颗心的形状。既然是代码,当然可以运行咯。将其复制到浏览器的 console ,执行后弹出对话框:I love you.
真是太酷了有木有,通篇只用了 $=~[];{_+,:!"\.
这几个符号,连数字都没有出现,居然可以实现文字弹窗!
于是二话不说立马将它剖析,看看葫芦里面到底卖的什么药。其实剖析的另一个目的就是为了给妹子定制一个特殊的版本啦~
¶Step I. Format
这样漂亮的代码可没法读,剖析的第一步当然是对其进行格式化(format/beautify),将其打回原形!使用 Sublime Text 的 jsFormat 插件,或者 WebStorm 很容易即可完成格式化。当然,再手动调整一下就更好了:
$ = ~[];
$ = {
___: ++$,
$$$$: (![] + "")[$],
__$: ++$,
$_$_: (![] + "")[$],
_$_: ++$,
$_$$: ({} + "")[$],
$$_$: ($[$] + "")[$],
_$$: ++$,
$$$_: (!"" + "")[$],
$__: ++$,
$_$: ++$,
$$__: ({} + "")[$],
$$_: ++$,
$$$: ++$,
$___: ++$,
$__$: ++$
};
$.$_ =
($.$_ = $ + "")[$.$_$] +
($._$ = $.$_[$.__$]) +
($.$$ = ($.$ + "")[$.__$]) +
((!$) + "")[$._$$] +
($.__ = $.$_[$.$$_]) +
($.$ = (!"" + "")[$.__$]) +
($._ = (!"" + "")[$._$_]) +
$.$_[$.$_$] +
$.__ +
$._$ +
$.$;
$.$$ =
$.$ +
(!"" + "")[$._$$] +
$.__ +
$._ +
$.$ +
$.$$;
$.$ =
($.___)[$.$_][$.$_];
$.$(
$.$(
$.$$ +
"\"" +
$.$_$_ +
(![] + "")[$._$_] +
$.$$$_ +
"\\" +
$.__$ +
$.$$_ +
$._$_ +
$.__ +
"(\\\"\\" +
$.__$ +
$.__$ +
$.__$ +
"\\" +
$.$__ +
$.___ +
(![] + "")[$._$_] +
$._$ +
"\\" +
$.__$ +
$.$$_ +
$.$$_ +
$.$$$_ +
"\\" +
$.$__ +
$.___ +
"\\" +
$.__$ +
$.$$$ +
$.__$ +
$._$ +
$._ +
".\\\"\\" +
$.$__ +
$.___ +
")" +
"\""
)()
)();
从格式化后的代码,很容易可以看出主要的五个部分。接下来就要对其一一分析了。
¶Step II. Parse
¶part 1
$ = ~[]; // $ = -1
$ = {
___:++$, // $.___ = $ = 0
$$$$: (![] + "")[$], // $.$$$$ = "false"[0] = "f"
__$: ++$, // $.__ = $ = 1
$_$_: (![] + "")[$], // $.$_$_ = "false"[1] = "a"
_$_: ++$, // $._$_ = $ = 2
$_$$: ({} + "")[$], // $.$_$$ = "[object Object]"[2] = "b"
$$_$:($[$] + "")[$], // $.$$_$ = "undefined"[2] = "d"
_$$:++$, // $._$$ = $ = 3
$$$_: (!"" + "")[$], // $.$$$_ = "true"[3] = "e"
$__:++$, // $.$__ = $ = 4
$_$: ++$, // $.$_$ = $ = 5
$$__: ({} + "")[$], // $.$$__ = "[object Object]"[5] = "c"
$$_: ++$, // $.$$_ = $ = 6
$$$: ++$, // $.$$$ = $ = 7
$___: ++$, // $.$___ = $ = 8
$__$: ++$ // $.$__$ = $ = 9
};
这一部分的目的主要是构造出数字表 0~9
其中第一句利用 ~[]
得到 -1
。
为什么
~[]
能得到-1
呢,[]
是一个具体对象,对应的布尔值是 true ,在 javascript 里 true 对应的数字是 1 。而~(NOT)运算
的具体运算过程是将数字按位取反后减1,所以~[] = ~1 = 0-1 = -1
。
更多关于 javascript 位运算的信息,可以看这里。
然后 $
从 -1 不断自加,得到 0~9, 忽略一些不相关的语句,我们可以看到:
$ = {
___: ++$, // $.___ = $ = 0
__$: ++$, // $.__ = $ = 1
_$_: ++$, // $._$_ = $ = 2
_$$: ++$, // $._$$ = $ = 3
$__: ++$, // $.$__ = $ = 4
$_$: ++$, // $.$_$ = $ = 5
$$_: ++$, // $.$$_ = $ = 6
$$$: ++$, // $.$$$ = $ = 7
$___: ++$, // $.$___ = $ = 8
$__$: ++$ // $.$__$ = $ = 9
};
此外这里还用 _
表示 0; $
表示 1 为变量合理命名,使得后面的引用变更有章可循。
¶part 2
$.$_ =
($.$_ = $ + "")[$.$_$] + // $.$_ = "[object Object]"
// "[object Object]"[5] = "c"
($._$ = $.$_[$.__$]) + // $._$ = "[object Object]"[1] = "o"
($.$$ = ($.$ + "")[$.__$]) + // $.$$ = "undefined"[1] = "n"
((!$) + "")[$._$$] + // "false"[3] = "s"
($.__ = $.$_[$.$$_]) + // $.__ = "[object Object]"[6] = "t"
($.$ = (!"" + "")[$.__$]) + // $.$ = "true"[1] = "r"
($._ = (!"" + "")[$._$_]) + // $._ = "true"[2] = "u"
$.$_[$.$_$] + // "[object Object]"[5] = "c"
$.__ + // "t"
$._$ + // "o"
$.$; // "r"
第二部分做了一件了不起的事,有了数字序列后,如何得到字母?可能你会想到 ascii 之类的东西,但现实有点残酷,写这个代码的人不想用到 $=~[];{_+,:!"\.
之外的字符,当然包括 String.fromCharCode()
这样的函数,其实现在甚至连函数都无法执行。但是他利用了 javascript 的类型转换特性:
当一个 object 与字符串相加的时候,会自动调用object的
toString()
方法,而 object 默认返回"[object Object]"
于是($ + "")[$.$_$]
实际上就得到了"[object Object]"[5] = "c"
真是太聪明了。用类似的方法可以得到 “undefined”、“true” 和 “false” 。最重要的一点,我们凑齐了 "constructor"
这个单词!
¶part 3
$.$$ =
$.$ + // "r"
(!"" + "")[$._$$] + // "true"[3] = "e"
$.__ + // "t"
$._ + // "u"
$.$ + // "r"
$.$$; // "n"
这里用同样的方法得到了 "return"
是不是感觉得到函数的气息了。
¶part 4
$.$ =
($.___)[$.$_][$.$_]; // (0)["constructor"]["constructor"]
Wow! 数字的构造函数,不就是 Number()
么; Number()
的构造函数,矛头直指 Function()
啊!正是如此,这里漂亮地拿下了函数构造函数,也就是说,我们可以用 Function()
来创造自定义函数了。
Function(“alert(‘hello world!’)”); 相当于创建一个匿名函数 function(){ alert(‘hello world!’); }
¶part 5
$.$( // Function(
$.$( // Function(
$.$$ + // "return
"\"" + // "\""
$.$_$_ + // "a"
(![] + "")[$._$_] + // "l"
$.$$$_ + // "e"
"\\" + // "\\"
$.__$ + // "1"
$.$$_ + // "6"
$._$_ + // "2"
$.__ + // "t"
"(\\\"\\" + // "(\\\"\\"
$.__$ + // "1"
$.__$ + // "1"
$.__$ + // "1"
"\\" + // "\\"
$.$__ + // "4"
$.___ + // "0"
(![] + "")[$._$_] + // "l"
$._$ + // "o"
"\\" + // "\\"
$.__$ + // "1"
$.$$_ + // "6"
$.$$_ + // "6"
$.$$$_ + // "e"
"\\" + // "\\"
$.$__ + // "4"
$.___ + // "0"
"\\" + // "\\"
$.__$ + // "1"
$.$$$ + // "7"
$.__$ + // "1"
$._$ + // "o"
$._ + // "u"
".\\\"\\" + // ".\\\"\\""
$.$__ + // "4"
$.___ + // "0"
")" + // ")"
"\"" // "\""
)() // )()
)(); // )();
连函数构造函数都有了,万事具备只欠东风,接下来只要描述出想要执行的函数体就可以了。但是手头上只有 $=~[];{_+,:!"\.
这几个字符可以怎么办!?这时候就要请 ascii 大驾光临了。
在 javascript 的字符串中,可以使用
\xxx
的八进制来表示单个 ascii 字符。可以在这里查看 ascii 对应的八进制。
但是在现有条件下,我们只能先构造出能表达 ascii 的字符串,而不是函数体。
假设
var a=1,b=6,c=7
,我们得先得到"\\"+a+b+c = "\167"
然后才得到字符"a"
所以这里不得不嵌套调用 Function,在里面一层组装函数体的 ascii 码,然后运行后得到真正的函数体,在外面一层运行。
Function(
Function(
"return(\"+
/* function body ascii code */
+"\")"
)()
)();
至于函数体的构造,如果量不大的话,手工查表完成也不是难事,如果量大的话,可以写一个辅助程序来完成转换工作。于是我写了这么一个东西:
var code = "alert(\"My secret!\")";
var ret = "";
for (var i=0, len=code.length; i<len; i++) {
ret += "\\"+code.charCodeAt(i).toString(8);
}
ret = ret.replace(/./g, function(c){
return {
"\\": "\"\\\\\"+",
"0": "$.____+",
"1": "$.___$+",
"2": "$.__$_+",
"3": "$.__$$+",
"4": "$._$__+",
"5": "$._$_$+",
"6": "$._$$_+",
"7": "$._$$$+",
"8": "$.$___+",
"9": "$.$__$+"
}[c];
});
// output
console.log(ret);
¶Step 3. Reshape
现在代码分析完了,知道它的工作原理了。但是要怎么把它变回原来的心形呢?
我想到了一个很高科技的算法,首先去除代码中的间隔,分析可伸缩字符位置,然后动态构造心形,最后采用填充算法将代码填充进去,当然得保证代码最终可以运行……
即使是这样,我对如何实现它完全没有思路,看来只能靠纯体力活完成了。好再自己还是有点艺术细胞的,为了妹子,豁出去了!
最后,实际上排版工作只花了我半个多小时,把 1367 个字符拼成了3颗心~效果展示。