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