再说这行代码之前,咱们先来预习一下知识.
我们都知道计算机操作系统分为32位或者64位.那么这个32位或64位指的是什么意思呢?其实,要想解释它并不难,其实这就是计算机处理数据的机制,32位表示计算机的CPU(*处理
器)一次性能够处理32位的数据,计算机数据都是按位来存储的,单位就是8位(bit)=1字节(b).然后依次向上推,1kb (千字节)= 1024b(字节),1mb(兆字节)=1024kb(千字节),1GB(千兆字节) =
1024mb(兆字节).
说了这么多,与今天讲的有什么关系吗?那当然是有关系啦.
我们先来看一串代码,如下图所示:
然后再在浏览器上运行查看效果,如下图所示:
是不是很神奇?就特么一行常用的符号就能够输出"sb"这两个字母,简直太牛叉了.接下来,我就来分析其中原理.
首先在分析之前,我们还需要知道一点知识,那就是既然计算机是按位处理数据的,那么浏览器处理js代码中的数据自然也不例外.所以js当中就有一个基本的概念,那就是位操作符.
什么是位操作符?简单点来说,就是在最基本的层次上,按内存中数值的位来操作数值.通过计算机的位操作原理,我们就能明白位操作符的含义.但是有一点需要区别,那就是位操作符不能
直接操作64位的数值,而是先将64位的数值转换成32位的数值来操作.
而转换的数值有且只有两个,那就是0和1.0表示的是正数,1表示的是负数.由0和1组成的一长串的如0000 0000 0000 0000 0000 0000 0000 0000 (4 X 8 = 32)的数字就是所谓的机器码.
机器码就是按照二进制格式来存储数据的.所谓二进制,也就是只有0和1两个数字来存储.
而前31位都表示的是整数,第32位则表示一个数值的符号.也就是+或-号,你可以把它理解成数学上的正负数的符号.
正数可以直接二进制存储,负数则需要二进制补码.也就是说咱们先取负数的绝对值,也就是将负数转换为正数来取二进制码,然后再取反,这里的取反意思就是因为二进制只有0和1两种
数字,所以不言而喻,取反就是0变成1,1变成0.
然后在加上一个1,当二进制最后一位是1时,加一就需要进位前面的一个0变成1,然后这个最后一位1就变成0.
我们通常都是十进制数,如10,那么如何转化为二进制数呢,通过除以2,然后取余数,最后把余数倒排.,要一直除到商等于0时才不用继续除下去,然后取它们的余数,颠倒顺序排列.
比如10/2 商5,余数是0,商5不等于0,继续除下去,得商2,取余数为1,商还不等于0,继续除下去,得商为1,余数是0,再除一次,得商0,余数为1,此时商也是0,也就不能再除,然后把得到的余数排
列就是0101,然后倒排就是1010,没有用到的位用0补上,或者不补,这里为了便于理解,我们不上前面的28个0 得0000 0000 0000 0000 0000 0000 0000 1010.所以得出10的二进制数就
是1010.那么-10的二进制数就应该是先将10的二进制数求出得1010,然后取反0变1,1变0,得0101.因为最后一位是1,按照上面的说法,然后加一个1,得0110.
说了这么多,与今天的代码有什么意义吗?那是当然.在了解了位操作符之后,我们还要了解一个知识,那就是js运算符的优先级,如下图:
首先需要耐心看完以上符号对应的含义,至少把上面那串代码用到的对应的符号的含义搞清楚.然后我们就可以根据运算符的优先级规则,把上面那一串代码给分解成多个子表达式.
重点解释一下~表示对先对数字求负,然后再减1.!返回一个布尔值.然后就是转换规则问题,对于一个非原始数据类型的,会先将其转换为原始数据类型,原始数据类型.实际上就是
ToPrimitive()这个方法来转换.它有两个参数,第一个参数表示数据类型,第二个参数是数值或者字符串.如果第一个参数是原始数据,那么就返回原始值,即本身值.否则就是返回object对
象.至于什么是原始数据类型,实际上就是基本数据类型(字符串,布尔值,数值...相信后面不用我多说了吧).而第二个参数可返回任何原始值,如果是对象也返回它本身.
所以,接下来,我们把以上的长串代码给拆分.
先来看最左边的(!(~+[])+{}),我们知道()表示一个函数的调用或者是一个表达式的分组,所以我再拆分得到(~+[]),好了先看[]这个符号,其实就是一个空数组,空数组会被转换为0,为什么呢?
首先它调用了对象底层valueOf()方法返回数组本身,实际上此时就得到的是一个空值,空值在遇到+这样的运算符的时候就会先调用Number()函数,当然实际上最底层还有一个
toNumber()函数转换成一个可转换的数值型数据,然后再通过Number()函数转换成数值0.随后就变成了~ + 0,然后就是先取负,变-0,再减1变成-1,然后就变成了!-1,就会返回一个false的
布尔值(可通过查看!逻辑非的转换返回相关知识得知这里为何会返回false).
接下来又是一个表达式(false + {}),这个比较关键,首先来看{}这是一个空对象,很明显就会调用ToPrimitive()这个方法,首先数据类型是对象,所以返回object,其次调用这个方法时,发现第
二个参数也还是对象,所以也就返回object,所以(false + {})也就成了false[object,object].
好接下来继续,由于第二个有点长,我们知道一对中括号包起来表示一个数组的下标.
那么[--[~+""][+[]]*[~+[]]+~~!+[]]这么一长串,我们就可以这样,先看[~ + ""],首先""空串其实跟null一样都被转换成了数值0,然后~+0则就是-1,--运算符就是先自减1再参与运算所以就是-1-1
变成了-2.
再看[+[]]实际上通过以上的规则不难得出就是[0]所以也就变成了[-2][0],这个表示啥意思,就是一个数组只有-2一个元素,而现在要取下标为0也就是第一个元素即-2,所以也就返回了-2,接
下来看[~+[]]不言而喻可以得出是[-1],[-2]*[-1]这很明显会当成-2*(-1)自然也就得出了2.
接下来看~~!+[]这里实际上都是从右往左计算的,因为运算符的优先级问题,通过上面的图可以看出,所以这里就是先转换!+[]返回true,然后再转换为1,然后~~1,根据~是先对数字求反再减
1的规律得到这里实际上就是1,然后这么一长串符号就变成了[2+1]即[3],然后就变成了
false[object object][3],通过这么艰难的分析就得出了s这个字母,然后再来看这部分({}+[])[[~!+[]]*~+[]],先运算表达式里面,很明显对象+一个对象会得出什么,自然是[object,object],这里的
+很明显被当成了拼接的作用,不要忘了它不只是做加法运算,还做拼接符号呢.
然后后面依次得出结论[-2 * -1]得出[2],然后就是[object,object][2],取出b,所以sb这两个字由此而来.