jsfuck 6 个字符写 js 代码(一)

JavaScript 是一门灵活的语言,表达力很丰富。JSFuck 使得我们可以只使用 [,],(,),!,+ 等 6 个字符来写 JavaScript 代码。虽然实际应用中根本不会有人这么写代码,但是这种做法很有趣,对其原理的探讨也很有助于加深我们对 JavaScript 基础知识的理解。

例如:

alert(1)

以上代码,用这 6 个字符,可以这么写:

[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()

打开浏览器控制台(console),复制粘贴以上代码,就可以看到弹出一个内容为‘1’的对话框。现在看起来好像挺不可思议的,但是看完下文以及后面几篇文章的分析后,我们就能理解上面的写法了。

那我们就从 JavaScript 类型转换说起吧。

JavaScript 变量数据可以发生类型转换。类型转换又分为“强制转换”和“自动转换”两种。“强制转换”主要指使用 Number、String 和 Boolean 等三个构造函数手动将各种类型的值,转换成数字、字符串或者布尔值。“自动转换”是指预期什么类型的值,就调用该类型对应的强制转换函数,自动完成转换过程。

为了更加直观地说明数据转换的过程。我们以 + 运算符为例展开讨论:

1. + 作为一元运算符

这时候,会自动调用 Number 函数,将运算子转成数值。Number 函数的返回值分为以下几种情况:

(1) 参数是数值,返回还是原来的数值;
(2) 参数是字符串,除整个非字符串能被解析成对应的数值,否则返回 NaN;
(3) 参数是布尔值,true 转成 1,false 转成 0;
(4) undefined 转成 NaN,null 转成 0;
(5) 参数是对象,处理流程如下:

【1】调用 valueOf 方法
调用对象自身的 valueOf 方法。如果返回前 4 种类型的值,则对该值使用 Number 函数,不再进行后续步骤;

【2】调用 toString 方法
如果 valueOf 方法返回的还是对象,则改为调用对象自身的 toString 方法。如果返回前 4 种类型的值,则对该值使用 Number 函数,不再进行后续步骤。

【3】报错
如果 toString 方法返回的是也是对象,就报错。

+ 'abc'        // NaN
+ '324'        // 324
+ '324abc'     // NaN
+ true         // 1
+ undefined    // NaN
+ null         // 0

+ ("1e1000")
-> 1e1000
-> Infinity

// 空字符串转为0
+ ''           // 0

+ {a: 1}       // NaN
// 默认情况下,
// 对象的 valueOf 方法返回对象本身,
// 对象的 toString 方法返回字符串 "[object Object]"

Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// 2

1. + 作为二元运算符

分以下几种情况:

【1】运算子之中存在字符串
只要两个运算子中有一个是字符串,则不管另一个什么类型,都会被自动转换为字符串,然后执行字符串的连接运算;

【2】运算子均为数值或布尔值
两个运算子都是数值或者布尔值,布尔值转换为数值(true 为 1,false 为 0),然后执行加法运算;

【3】运算子中存在对象
两个运算子中存在对象(非原始类型数据),先调用该对象的 valueOf 方法,如果返回值为原始类型,然后按上面 2 条规则执行;否则调用该对象的 toString 方法,然后按上面 2 条规则执行。

那么,如果一个运算子是字符串,一个运算子是对象,怎么处理呢?

'a' + {
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
}
// "a2"

我们看到,对象是采用了 valueOf 方法的返回值,所以,以上规则 【3】 优先级高于规则 【1】。

说到这里, 我们需要记住 valueOf 和 toString 方法的区别:

① 数值运算中优先调用 valueOf(),如 Number() 函数优先调用对象的 valueOf();
② 字符串运算中优先调用 toString(),如 String() 函数优先调用对象的 toString();
③ valueOf() 返回对象的原始值;toString() 返回对象的原始字符串表示。

有了以上的理论基础,下面看4个特殊的表达式,小试牛刀。

(1) 空数组 + 空数组

[] + []

两个运算子都是非原始类型的数据(数组对象),先对两个 [] 均执行 valueOf 方法,返回该 [] 本身(其实,默认情况下,对象的 valueOf 方法都返回该对象本身),然后对 [] 调用 toString 方法方法(数组执行 toString 方法一般返回数组的字符串形式,如 [1,2,3].toString() 返回 “1,2,3”),返回 “”,所以相当于 “” + “”,最终结果为 “”,空字符串。

(2) 空数组 + 空对象

[] + {}

分析方法同上,不过默认情况下,对象的 toString 方法,返回 “[object Object]”,所以,以上表达式相当于 “” + “[object Object]”,最终结果为 “[object Object]”。

(3) 空对象 + 空对象

{} + {}
({}) + {}

首先看 {} + {},JavaScript 引擎将第一个空对象视为一个空代码块,忽略,变成了 +{},这里的 + 为一元运算符,它相当于调用 Number 方法,强制将运算子转为数值。而这里的运算子是对象 {},+{} 相当于,先对 {} 调用 valueOf 方法得到其自身,然后调用 toString 方法,得到 “[object Object]”,最后相当于 +”[object Object]”,这个字符串转换为数值为 NaN,即最终结果是 NaN。

再看 ({}) + {},两个 {} 均调用 toString 方法,相当于 “[object Object]” + “[object Object]”,最终结果是 “[object Object][object Object]”。

(4) 空对象 + 空数组

{} + []
({}) + []

先看 {} + [],{} 视作空代码块被忽略,相对于 +[] ,[] 也是对象的一种,所以调用 toString 方法,相当 +””,最终结果为 0。

再看 ({}) + [],两个运算子均为对象,分别调用 toString 方法,相当于 “[object Object]” + “”,最终结果为 “[object Object]”。

以上是最基本的数组类型转换,也是 jsFuck 的基本原理之一。暂时先到这里,后面我们会继续探讨这个话题。

参考:
[1] http://javascript.ruanyifeng.com/grammar/conversion.html
[2] http://www.jsfuck.com/
[3] https://gold.xitu.io/entry/5834a964570c35006c4ac205

views