上一章

JS,但是禁用字母和数字

  • 23 阅读
  • 948 字
下一章

自限规则

在源码中不使用任何字母和数字,在控制台直接输出任意字母与数字组成的字符串。

实现思路

利用 JS 神秘的隐式类型转换,我们可以首先得到这样一些打印结果:

js
1
2
3
4
5
6
7
8
![] + ![]
// 0

![] + !![]
// 1

{} + [] + ![] + !![]
// [object Object]falsetrue

在有了 0 和 1 的情况下,可以得到任意大小的数字;而对通过上述方式得到的字符串进行下标索引,则可以得到有限数量的字母。那么仅仅凭借这些,如何获取所有 26 个字母呢?

在这之前,先将数字以等比数列的方式存入变量中:

js
1
2
3
4
5
6
_ = { _: ![] + !![] };     // 1
_.__ = _._ + _._;          // 2
_.___ = _.__ * _.__;       // 4
_.____ = _.___ * _.__;     // 8
_._____ = _.____ * _.__;   // 16
_.______ = _._____ * _.__; // 32

我们成功构造了一个以 2 为公比,以 1 为首项的等比数列!之后便可以自由组合这些子属性,得到任意大小的数字了。

此外,上面得到的字符串也自然要存入变量中。然而仅仅这些是不够的,我们还需要一个 undefined,它包含了一个至关重要的字母 n

js
1
2
// [object Object]falsetrueundefined
$_ = {} + [] + ![] + !![] + _[_];

这时就可以开始着手最关键的步骤了。我们先将字符串 constructor 组合出来:

js
1
2
// constructor
$_$ = $_[_.___+_._] + $_[_._] + $_[_.______-_.__] + $_[_._____+_.__] + $_[_.___+_.__] + $_[_._____+_.___+_._] + $_[_._____+_.____] + $_[_.___+_._] + $_[_.___+_.__] + $_[_._] + $_[_._____+_.___+_._];

为什么是 constructor?考察这样一段代码:

js
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,便需要通过第一条代码得到:

js
1
2
// name
_$_ = $_[_.______-_.__] + $_[_._____] + ("" + _._[$_$])[_.____+_.__+_._] + $_[_.___];

于是一路畅通无阻,直接拼接出 toString 方法:

js
1
2
// toString
$__ = $_[_.___+_.__] + $_[_._] + ("" + ""[$_$][_$_]);

Tip:为了保持页面的简洁,不再给出复杂代码的混沌版本。

Number.prototype.toString() 方法可以通过传入一个整数来指定数字的基数,对于获取小写英文字母,只需要将其按照 36 进制转换即可:

js
1
2
3
4
5
6
$ = _ => (_ + 9).toString(36);

$(_._);          // a
$(_.__+_._);     // c
$(_.____-_._);   // g
$(_._____-_.__); // n

字母是能够得到了,但如何将它们输出到控制台呢?想要调用控制台,必须直接获取顶层对象,而这是通过从局部变量出发的方式无论如何都获取不到的。在当前环境下,任何可获取对象的原型属性都不指向 globalThis,我们无法再根据以往的写法按部就班——

于是,前面得到的 Function 构造器就派上用场了。学过 JS 的都知道,eval() 方法能够直接将传入的字符串作为代码运行。而我们虽然不能得到 eval(),却有着几乎拥有同样功能的 Function()

js
1
(() => {}).constructor("return console")()

看,这不就得到 console 了吗?当然,我们可以更进一步直接得到 windowglobal 对象。

至于大写字母,同样可以利用 Function 构造器:

js
1
2
3
4
5
6
$$ = _ => (() => {}).constructor(`return "${"\\x" + (_ + 0x40).toString(16)}"`)();

$$(_._);          // A
$$(_.__+_._);     // C
$$(_.____-_._);   // G
$$(_._____-_.__); // N

至此,我们不仅能够输出大小写字母和数字,还获得了一个 API 完全的纯符号框架,它能够在不输入字母与数字的同时实现 JS 的大部分功能!

评论0

60FPS