自限规则
在源码中不使用任何字母和数字,在控制台直接输出任意字母与数字组成的字符串。
实现思路
利用 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