木匣子

Web/Game/Programming/Life etc.

心形表白代码

情人节的时候,在推特上看到了一个很神奇的代码,整个代码构成了几颗心的形状。既然是代码,当然可以运行咯。将其复制到浏览器的 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 为变量合理命名,使得后面的引用变更有章可循。

_ _ _ _ 表示 0000; _ _ _ ᛰ 表示 0001; _ _ ᛰ _ 表示 0010;

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颗心~效果展示