自限规则
在源码中不使用任何字母和数字,在控制台直接输出任意字母与数字组成的字符串。
实现思路
利用 JS 神秘的隐式类型转换,我们可以首先得到这样一些打印结果:
1 2 3 4 5 6 7 8
![] + ![];
// 0

![] + !![];
// 1

{} +[] + ![] + !![];
// [object Object]falsetrue
在有了 0 和 1 的情况下,可以得到任意大小的数字;而对通过上述方式得到的字符串进行下标索引,则可以得到有限数量的字母。那么仅仅凭借这些,如何获取所有 26 个字母呢?
在这之前,先将数字以等比数列的方式存入变量中:
1 2 3 4 5 6
_ = { _: ![] + !![] }; // 1
_.__ = _._ + _._; // 2
_.___ = _.__ * _.__; // 4
_.____ = _.___ * _.__; // 8
_._____ = _.____ * _.__; // 16
_.______ = _._____ * _.__; // 32
我们成功构造了一个以 2 为公比,以 1 为首项的等比数列!之后便可以自由组合这些子属性,得到任意大小的数字了。
此外,上面得到的字符串也自然要存入变量中。然而仅仅这些是不够的,我们还需要一个 undefined,它包含了一个至关重要的字母 n:
1 2
// [object Object]falsetrueundefined
$_ = {} + [] + ![] + !![] + _[_];
这时就可以开始着手最关键的步骤了。我们先将字符串 constructor 组合出来:
1 2
// constructor
$_$ = $_[_.___ + _._] + $_[_._] + $_[_.______ - _.__] + $_[_._____ + _.__] + $_[_.___ + _.__] + $_[_._____ + _.___ + _._] + $_[_._____ + _.____] + $_[_.___ + _._] + $_[_.___ + _.__] + $_[_._] + $_[_._____ + _.___ + _._];
为什么是 constructor?考察这样一段代码:
1 2 3 4 5 6 7 8
"" + (0).constructor;
// function Number() { [native code] }

"".constructor.name
// String

(() => {}).constructor;
// ƒ Function() { [native code] }
注意到了吗?我们竟然同时得到了 String 字符串和 Function 构造器!而 name 属性中的字母 m,便需要通过第一条代码得到:
1 2
// name
_$_ = $_[_.______ - _.__] + $_[_._____] + ("" + _._[$_$])[_.____ + _.__ + _._] + $_[_.___];
于是一路畅通无阻,直接拼接出 toString 方法:
1 2
// toString
$__ = $_[_.___ + _.__] + $_[_._] + ("" + ""[$_$][_$_]);
Tip:为了保持页面的简洁,不再给出复杂代码的混沌版本。
Number.prototype.toString() 方法可以通过传入一个整数来指定数字的基数,对于获取小写英文字母,只需要将其按照 36 进制转换即可:
1 2 3 4 5 6
$ = (_) => (_ + 9).toString(36);

$(_._); // a
$(_.__ + _._); // c
$(_.____ - _._); // g
$(_._____ - _.__); // n
字母是能够得到了,但如何将它们输出到控制台呢?想要调用控制台,必须直接获取顶层对象,而这是通过从局部变量出发的方式无论如何都获取不到的。在当前环境下,任何可获取对象的原型属性都不指向 globalThis,我们无法再根据以往的写法按部就班——
于是,前面得到的 Function 构造器就派上用场了。学过 JS 的都知道,eval() 方法能够直接将传入的字符串作为代码运行。而我们虽然不能得到 eval(),却有着几乎拥有同样功能的 Function():
1
(() => {}).constructor("return console")();
看,这不就得到 console 了吗?当然,我们可以更进一步直接得到 window 或 global 对象。
至于大写字母,同样可以利用 Function 构造器:
1 2 3 4 5 6
$$ = (_) => (() => {}).constructor(`return "${"\\x" + (_ + 0x40).toString(16)}"`)();

$$(_._); // A
$$(_.__ + _._); // C
$$(_.____ - _._); // G
$$(_._____ - _.__); // N
至此,我们不仅能够输出大小写字母和数字,还获得了一个 API 完全的纯符号框架,它能够在不输入字母与数字的同时实现 JS 的大部分功能!
评论0