当前位置: 首页 > 科技观察

一行神奇的javascript代码

时间:2023-03-12 19:27:05 科技观察

之所以写这篇文章是因为@黛除之前在群里发了一段js代码,如下:(!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]]*~+[]]让大家跑起来,结果会让人有些意外,请看:太骚了!如果有人诋毁前端,看不起js,那你可以把这段代码发给他~不过话又说回来,这是什么原理呢?为什么?堆符号计算的结果可以是两个字符,正好是sb!其实就是依赖于js类型转换的一些基本原理。本文将揭示“sb”是如何提炼的。相信如果你能想清楚这一点,遇到类型转换等问题,你可以瞬间秒杀。首先要应用的知识是js算子的优先级,因为这么长的操作是眼花缭乱的,所以我们要先把它按照优先级分成n个小节,然后一个一个打断。优先级排列如下表:优先级从高到低:根据这个规则,我们将这一系列操作分为以下16个子表达式:操作符用红色标记,可能不是每个人都实现。中括号[]也是一个运算符,用于通过索引访问数组项,也可以访问字符串的子字符,有点类似于charAt方法,如:'abcd'[1]//Return'b'。而且方括号的优先级还是最高的。预处理结束之后,接下来需要用到的就是javascript的类型转换知识。先说说什么时候需要类型转换。当运算符两边的操作数类型不一致或不是基本类型(也称原始类型)时,需要进行类型转换。先按运算符分类:减号-,乘号*,一定是数学运算,所以需要把操作数转成数字类型。加号+可能是字符串拼接,也可能是数学运算,所以可能转为数字或字符串一元运算,如+[],只有一个操作数,转为数字类型。我们来看看转换规则。1、对于非原始类型,使用ToPrimitive()将值转换为原始类型:ToPrimitive(input,PreferredType?)可选参数PreferredType为Number或String。返回值是任何原始值。如果PreferredType是Number,执行顺序如下:(参考:http://es5.github.io/#x9.1)如果输入是primitive,则返回,否则,输入是Object。调用对象。的价值()。如果结果是原始的,则返回它。否则,调用obj.toString()。如果结果是原始的,则返回它。否则,抛出TypeError。如果PreferredType是String,则第2步和第3步会互换。如果PreferredType不是,则Date实例设置为String,其他为Number2。通过ToNumber()将value转换成Number,直接看ECMA9.3的形式http://es5.github.io/#x9.3规则如下:3.使用ToString()转换value变成字符串,直接看ECMA9.8的形式http://es5.github.io/#x9.8规则太多了,我们来实践一下,按照我们划分的子表达式一步步执行这段神奇的代码多于。开始干活~看最简单的子表达式16:+[]只有一个操作数[],必须转成数字。根据上面规则2,[]是数组,对象类型是对象。所以必须先调用toPrimitive转换为原始类型,PreferredType为number。该参数表示更“倾向于”转换的类型,这里必须是number。然后首先调用数组的valueOf方法,调用valueOf的数组会返回自己,如下:见上面规则2的描述,继续调用toNumber将其转化为数字类型,如下:Number("")0大功告成!转换子表达式16,+[],最后得到0。看子表达式15:[~+""]空串""前面有两个一元运算符,但是操作数还是只有一个,所以最终要转换的类型是number。看规则2,用空字符串调用toNumber得到0,接下来是~,这是什么东东?是一个位运算符,其作用可以记为取负数再减一,所以~0为-1。别忘了,这个子表达式是用方括号括起来的,所以最后的值是[-1],这是一个只有一个元素-1的数组。接下来很容易看子表达式13,把15、16计算填入,就变成了这样:-[-1][0],取数组的第0个元素,然后自减,则结果是-2,很简单!继续往上走,子表达式14:[~+[]]其实利用15和16的原理就很明显了,答案[-1]继续找子表达式9,就变成了:-2*[-1],有细微的差别,不过没关系,我们还是按规矩来,运算符是乘号*,当然是数学运算,那么下面的[-1]得转成数字,类似16的方法,过程如下:①调用到Primitive,发现是对象类型②调用valueOf,返回自身[-1]③因为不是原始类型,继续调用toString,返回“-1”④“-1”是原始类型,再调用toNumber,返回-1⑤乘以-2,返回2子表达式10:~~!+[],话不多说,答案1.从右到左是一元计算。有了9和10,我们就到了子表达式4,现在看起来是这样的:2+1好了,我不多说了。继续看表达式7:!(~+[]),~+[]=-1,这个根据上面已经知道了,那么!-1是什么?这里要说一下感叹号,它是逻辑非,意思是表达式会被转成布尔类型。转换规则和js的Truthy和Falsy原则一样。如果后面是数字,除0外都是false,如果后面是字符串,除空字符串外都是false。这里的!-1当然是假的。下一个表达式3:false+{}有点关键。一个Boolean加一个对象,那么这个{}要先转为原始类型,过程如下:①调用toPrimitive,发现是对象类型②调用valueOf,返回自己{},③不是a原始类型,调用toString,返回“[objectObject]”④false和“[objectObject]”相加,false先转为字符串“false”⑤相加“false[objectObject]”的结果知道表达式3和4、我们可以看表达式1现在,它看起来是这样的:“false[objectObject]”[3],因为这个[]可以取字符串的子字符,像charAt,所以我们得到结果“s”后以上困难的过程,我们得到字符“s”,也就是图片的左半部分,剩下的“b”,原理一样可以做到,这里就不一一演示了,留给大家你去实践~复习这个过程其实并不复杂,但是有些东西是需要重复劳动的。只要掌握了操作的优先级,就可以把大字符串分解成小字符串,然后利用类型转换的知识,一一处理。怎么样,你还觉得很神奇吗?如果有人看不起js,请把这段代码发给他。如果他想知道答案,就把这篇文章发给他吧~