浅谈JavaScript位操做符

位操做符的基本概念

由于ECMAscript中全部数值都是以IEEE-75464格式存储,因此才会诞生了位操做符的概念.前端

位操做符做用于最基本的层次上,由于数值按位存储,因此位操做符的做用也就是操做数值的位.不过位操做符并不能操做64位的值.因此位操做符会先将64位的值转换成32位的值,而后执行操做,最后再将结果转换成64位的值.面试

但对于开发人员来讲,这整个过程就像是只存在32位的数值同样,这是由于64位存储格式是透明的.函数

固然这里所说的数值指的是整数.在对于有符号的整数中,32位的前31位用于表示整数的值,而第32位则表示整数的符号(即0表示正数,1表示负数),咱们把这第32个表示符号的位叫作符号位,符号位也决定了其它位的数值的格式.学习

正数都是按纯二进制格式存储的,在前31位中的每个位都表示2的幂.即第一位表示2^0(2的零次方),第二位表示2^1(2的1次方),依次类推.第一位也叫作位0,后面依次类推,第32位就叫作位31,其它没有用到的位都以0填充,也能够被忽略不计.spa

好比十进制整数10的二进制表示是0000 0000 0000 0000 0000 0000 0000 1010或者更简单的1010。这是4个有效位,这4位就决定了实际的值.在前面说到过能够用toString()方法指定参数能够表示将一个十进制数转换成二进制数.因此我在这里写了一个函数,表示将一个十进制数转换成二进制数,以下图所示:code

clipboard.png

既然二进制数1010就是十进制数10,那么咱们还能够将这个二进制数转换成十进制数,是如何计算的呢?很简单,由于二进制数最后一位表示符号,因此不计,这里的101各表明幂数为3,2,1,这也是为何十进制转换成二进制数要取余数倒排的缘由,而后将位上的数乘以基数2的幂数.也就是说能够写成等式2 ^ 3 * 1 + 2 ^ 2 * 0 + 2 ^ 1 * 1 = 10.(2 ^ *表示2*次方).对象

负数一样以二进制码存储,只不过与正数有点区别,区别就是负数的格式是二进制补码.在求二进制补码的时候,有如下三个规则:blog

(1).先求出这个负数的绝对值的二进制码.好比十进制数-17,就是先求17的二进制码.ip

(2).而后求二进制码的反码,就是将0变成1,1变成0.开发

(3)最后将获得的二进制反码加1.

好比说求十进制数-10的二进制码,咱们要先求10的二进制码,也就是
0000 0000 0000 0000 0000 0000 0000 1010,而后取反码就是
1111 1111 1111 1111 1111 1111 1111 0101,最后加1,但由于二进制数只能是1或者0表示,因此1+1大于2的话,就会向前进位1.因此这个反码加1最后获得的值应该是1111 1111 1111 1111 1111 1111 1111 0110.而这个也是-10的二进制表示.须要注意的是在处理有符号的整数的时候,是访问不到第32位的(也就是位31).

但在实际状况中,ECMAscript是会尽力向咱们隐藏全部的这些信息.也就是说在实际转换负数的二进制码时,它只会将这个负数的绝对值的二进制码前面加上一个负号,就表示这个负数的二进制码.以下图所示:

clipboard.png

这个转换过程说明ECMAscript解析引擎理解了二进制补码并将其以更合乎逻辑的形式展现出来.

在默认状况下,ECMAscript中的全部整数都是有符号整数.固然也存在无符号整数,对于无符号的整数来讲,第32位不会再表示符号,由于无符号整数只能是正整数.并且无符号整数的值能够更大,由于第32位再也不表示符号,而能够表示成数值.什么意思呢?就是说当咱们再将十进制数转换成二进制数时,必需要除到商为0时,才会倒排余数,而第32位刚好就是商为0的那个余数.而正整数值越大,咱们能够省略的有效位数就越多,此时值也就越大.

ECMAscript中,当对数值应用位操做符的时候,虽而后台会发生将64位数值转换成32位数值,而后执行完操做以后,再转换成64位的数值这个转换过程.但正由于这个转换过程致使了一个严重的副效应,也就是说在对特殊的NaNInfinity值应用位操做符时,这两个值会被当成0来处理.

而若是对非数值应用位操做符,会自动使用Number()函数将其转换成一个数值来操做,而后再应用位操做符,获得的结果也将是一个数值.

总的说来,位操做符主要包含按位非(NOT),按位与(AND),按位或(OR),按位异或(XOR),左移,无符号右移和有符号右移7个操做符.接下来,我们就来一一分析这7个操做符.

a.按位非(NOT)

按位非用一个波浪线符号"~"表示,执行按位非的结果就是取得数值的反码.它也是ECMAscript中少数几个与二进制计算相关的操做符.

好比求10的按位非结果,那么按照求二进制获得10的二进制码是0000 0000 0000 0000 0000 0000 0000 1010,而后取反码就是1111 1111 1111 1111 1111 1111 1111 0101.而要将这个反码转换成十进制数,还须要如下过程:

此时,位31上的1表明符号为负,由于负数的补码就是反码加1,因此得知负数的反码就等于补码减1,因此此时求得负数的反码是1111 1111 1111 1111 1111 1111 1111 0100,因此负数的原码就是取反,变成了0000 0000 0000 0000 0000 0000 0000 1011,因此此时再将这个二进制数转换成十进制数就是-(2 ^ 3 * 1 + 2 ^ 2 * 0 + 2 ^ 1 * 1 + 2 ^ 0 * 1)=-11.要理清这个转换过程,须要知道什么是反码,什么是原码,什么又是补码,由于参与计算的是补码,而要转换的是求原码.也就是说,要想将二进制反码转换成十进制数,就必须求得二进制反码的原码,而后对原码直接按照二进制转换成十进制的方式来计算转换.如今咱们来验证一下是不是咱们所想的,以下图所示:

clipboard.png

再好比求-10的按位非结果,按照理论分析,咱们从前述能够得知最终-10的二进制码为1111 1111 1111 1111 1111 1111 1111 0110,取反码就变成了0000 0000 0000 0000 0000 0000 0000 1001,而此时的二进制反码的补码,原码都同样,因此直接计算就是2 ^ 3 * 1 + 2 ^ 2 * 0 + 2 ^ 1 * 0 + 2 ^ 0 * 1 = 9.以下图所示:

clipboard.png

经过以上示例还应该获得一个结论:正整数的二进制码的反码与原码补码不一致,而负整数的二进制码的反码就与原码补码一致.换句话说,就是正数的原码与补码同样,负数的原码与补码不同.

若是实在是不能理解原码,补码与反码,能够直接把这个操做符理解为数值加1取反.如101取反就变成-11,-101取反就变成9.

而实际上,对按位非的结果好比~10~-10,咱们还能够写成以下图所示的表示:

clipboard.png

咱们能够用变量来表示,以下图所示:

clipboard.png

虽然不用按位非操做符的以上所表示的代码也能输出一样的结果,但因为按位非是对底层进行操做,因此使用按位非操做符的速度会更快.

b.按位与(AND)

按位与操做符用一个和号字符(&)表示,它有两个操做数,从本质上讲,按位与操做就是将数值的每一位二进制码对齐,而后根据如下规则,对相同为止上的两个数执行AND操做.规则以下:

第一个数值的位               第二个数值的位             结果
    1                             1                     1
    1                             0                     0
    0                             1                     0
    0                             0                     0

简而言之,就是只在两个数值的位数都对应为1的时候,结果才为1,任何一位是0,结果都是0.

如如下示例:

clipboard.png

106进行按位与操做时返回2,这是为何呢?请看底层原理:

首先10转换成二进制数就是0000 0000 0000 0000 0000 0000 0000 1010,而6转换成二进制数则是0000 0000 0000 0000 0000 0000 0000 0110.过程能够以下:

10 = 0000 0000 0000 0000 0000 0000 0000 1010
 6  = 0000 0000 0000 0000 0000 0000 0000 0110
——————————————————————————————————————————————
AND = 0000 0000 0000 0000 0000 0000 0000 0010

而后按位与结果转换成十进制数就是2 ^ 1 * 1 = 2.因此最终结果为2.

再好比求2 & 5的结果,如今我们按照步骤来计算出结果,而后再验证答案对不对.

首先求得2的二进制数为0000 0000 0000 0000 0000 0000 0000 0010,5的二进制数为0000 0000 0000 0000 0000 0000 0000 0101。 

2 = 0000 0000 0000 0000 0000 0000 0000 0010
 5  = 0000 0000 0000 0000 0000 0000 0000 0101
——————————————————————————————————————————————
AND = 0000 0000 0000 0000 0000 0000 0000 0000

而这个结果转换成十进制数就是0。因此得出结果是0,如今我们来验证一下,以下图所示:

clipboard.png

c.按位或(OR)

   按位或操做符由一个竖线符号(|)表示,一样也有两个操做数.从本质上讲,也能够说是将数值的二进制码对齐,但与按位与操做符有一点点区别,就是它的规则与按位与操做符不同,具体以下:

第一个数值的位               第二个数值的位               结果
    1                             1                      1
    1                             0                      1
    0                             1                      1
    0                             0                      0

简而言之,就是按位或操做符只有其对应的两个位都是0的状况下才是0,其它有一个位是1的状况下都是1.如如下示例:

clipboard.png

如今,咱们就来分析一下为何结果是7,其实与按位与的底层操做很类似,2和5的二进制数前述示例已求得:

2 = 0000 0000 0000 0000 0000 0000 0000 0010
5  = 0000 0000 0000 0000 0000 0000 0000 0101
——————————————————————————————————————————————
OR = 0000 0000 0000 0000 0000 0000 0000 0111

而将按位或的结果转换成十进制数就是2 ^ 2 * 1 + 2 ^ 1 * 1 + 2 ^ 0 * 1 = 7。因此结果7就是这么求来的。

d.按位异或(XOR)

 按位异或操做符由一个插入符号(^)表示,也有两个操做数,其本质也与按位与和按位或操做符相同,但其规则也不同,以下:

第一个数值的位          第二个数值的位               结果
    1                      1                        0
    1                      0                        1
    0                      1                        1
    0                      0                        0

也就是说,按位异或操做符只有在其中一个位为1时才返回1,不然就是0。如对2 ^ 5求结果以下图:

clipboard.png

如今来分析一下为何结果是7,过程也与求按位与和按位或结果一致.

2 = 0000 0000 0000 0000 0000 0000 0000 0010
5  = 0000 0000 0000 0000 0000 0000 0000 0101
——————————————————————————————————————————————
XOR = 0000 0000 0000 0000 0000 0000 0000 0111

这里由于对应位没有变化,因此最终结果才会和按位或结果一致。

e.左移

  左移操做符由两个小于号(<<)表示,也是两个操做数,第一个操做数就表示要左移的数值,第二个操做数表示左移的位数.因此左移操做符的含义就是将数值的全部位向左移动指定的位数.

而在向左移动了指定的左移位数以后,原数值的右侧会多出指定的位数个空位(好比指定左移4位,也就多出4个空位,依次类推)出来,不过左移操做会自动以0来填充这些空位.

如如下示例:

clipboard.png

如今来分析一下为何结果是40,首先5的二进制数是
0000 0000 0000 0000 0000 0000 0000 0101,指定的是向左移动3位,因此总体向左移动3位,就变成了0000 0000 0000 0000 0000 0000 0010  1000,而这个二进制转换成十进制数就是2 ^ 5 * 1 + 2 ^ 3 * 1 = 40.
因此最终结果就是40.

注意,左移操做并不会影响操做数的符号位,换句话说,若是将-5左移3位,结果将是-40,而不是40.

f.右移操做符

 右移操做符又分为无符号右移有符号右移操做符.

(1).有符号的右移操做符。

 有符号的右移操做符由两个大于符号表示(>>),这个操做符的含义就是将数值的位向右移指定的位数,同时保留符号位的值(正负号标记),有符号的右移操做符与左移操做符恰好相反,好比40向右移动3位就是5.

一样的,在移位的过程当中,也会出现空位,而这时候,ECMAscript会用符号位的值来填充全部空位,也就是说每向右移动一位,移走的位上的数不论是1,仍是0都会消失了,则会在数值的左侧补充一位,而这位的值就是符号位的值,即若是是正数,补充0,负数补充1.

如如下一个示例:

clipboard.png

如今,我们就来分析分析为何最终结果为0.首先由前述能够得知40的二进制数为0000 0000 0000 0000 0000 0000 0010  1000,指定的是向右移动3位,那么总体向右移就变成了0000 0000 0000 0000 0000 0000 0000 0101,这个转换成十进制数也就是5.因此才会说有符号的右移与左移结果相反.

再来看一个示例:

clipboard.png

如今,我们就来分析分析为何最终结果为0.首先由前述能够得知5的二进制数为0000 0000 0000 0000 0000 0000 0000 0101,指定的是向右移动3位,那么总体向右移3位,左侧就要补充符号位的值,由于是正数(正数符号表示为0),因此补充30,就变成了
0000 0000 0000 0000 0000 0000 0000 0000.因此最终结果为0

若是这样不能理解的话,那么假设向右移动一位,也就是求5 >> 1的结果,一样在最左侧补充一个符号位的值0,右移走了末位的1.因此变成了0000 0000 0000 0000 0000 0000 0000 0010.这个转换成十进制数就是2.如今我们来操做验证一下,以下图:

clipboard.png

(2).无符号右移操做符。

   无符号右移操做符由三个大于符号表示(>>>).这个操做符也是会将全部的32位都总体向右移动指定的位数.对于正数来讲,其实无符号右移操做符和有符号右移操做符的结果一致.

5 >>> 1仍然是2,按照一样的过程步骤分析.

对于正数没有什么变化,但对于负数来讲,变化可就大了,首先无符号右移操做符是以0填充空位,而不是像有符号右移操做符那样以符号位的值填充.因此才会正数与有符号右移操做符的结果相同.可是负数就不同了,无符号右移操做符会把负数的二进制码当成正数的二进制码,并且负数是由其绝对值的二进制补码表示,所以致使无符号右移以后结果会很大.换句话说,就是对负数进行无符号右移操做时只会返回正数.

如求-5 >>> 3.咱们先本身求一遍,首先-5的二进制补码为1111 1111 1111 1111 1111 1111 1111 1010,而由于无符号右移会把这个补码当成正数的二进制码,因此转换成十进制数就是(口算不太现实,太大了,仍是让计算机来算吧)以下图所示:

clipboard.png

因此就会被当成4294967290,而后这个正数的二进制码右移3位变成了0001 1111 1111 1111 1111 1111 1111 1111,转换成十进制数就是以下图所示:

clipboard.png

因此最终结果就是536870911.如今,咱们来验证一下,以下图所示:

clipboard.png

前端面试题分析

知道了位操做符以后,如今我们来分析一道题,有这样一道前端面试题,写一个函数用于判断一个非负整数是不是2的非负整数次幂.而有人曾经这样写,以下图所示:

clipboard.png

那么为何这样写呢,咱们来分析一下这其中原理,首先什么是函数,使用function关键字声明的均可以被叫作函数,而这里定义的函数名也比较语义化,叫作isPowerOfTwo,圆括号中的n叫作函数的参数,顾名思义,这里的参数就是传入一个非负整数.而这个函数的做用就是要判断传入的参数(即非负整数)是不是2的非负整数次幂.

return也是一个关键字,表示返回一个值,用在函数当中,而要记住的是,若是在函数当中写入了return关键字,在这个关键字表示的语句结束后面再写其它语句是没有效果的,以下图所示:

clipboard.png

如上图所示,alert()方法表示弹出一个原生的弹出框,但实际上在调用这个定义的判断函数以后,是不会执行弹出框的,这就是return关键字在这里起到的做用.

如今再来分析一下里面的结构,叹号(!)也就是逻辑非的意思,这个操做符会把一个操做数转换成布尔值,而后取反.

再来看圆括号里面的和字符号&,在学了位操做符以后,咱们就应该知道这个符号就是按位与的意思,而按位与是操做二进制数的位的,对应规则也应该知道,就是当两个操做数(在这里指nn-1)的对应位都是1时,最终返回的对应位结果才是1,不然就是0.按位与的做用就是将位对齐.因此,在返回这个结果以前,咱们还须要知道如何转换成二进制数.

咱们应该知道对象的toString()方法,能够为其指定一个参数为基数2,就能够将一个操做数转换成二进制数返回,固然这里也是返回一个字符串.而为了方便,我将这个方法封装在一个函数中,以下图所示:

clipboard.png

如今咱们再来看看一个非负整数若是是2的幂,会有什么特色,咱们能够调用以上的定义函数将一个非负整数转换成二进制数,而一个非负整数若是是2的幂,咱们应该知道2的幂有2 ^ 0 = 1,2 ^ 1 = 2,2 ^ 2 = 4......依次类推,咱们从而得知1,2,4,8,16....等就是2的幂,而咱们将这些值转换成二进制数,就能够知道有什么样的关系了,好比1转换成二进制就是1,2转换成二进制是10,4转换成二进制是100......依此类推,不信我们能够用上面定义好的函数来验证,以下图:

clipboard.png

如今咱们就应该知道规律了,若是一个非负整数是2的非负整数次幂的话,那么这个数必定是上一个2的非负整数次幂的二进制数左移了一位.而经过以前知道的左移操做符,咱们知道,左移就是将位往左移动一位,而后在移动后的空位中以0填充.

如今,咱们再来看看n-1,假设是2的非负整数次幂的非负整数,减1,而后再将其转换成二进制数,好比12的非负整数次幂,1 - 1 = 0.转换成二进制就是0(这里是简写),再好比2 - 1 = 1的二进制就是1,4 - 1 = 3的二进制就是11,7就是111.不信咱们能够经过以上定义的函数来验证,以下图所示:

clipboard.png

经过使用按位与操做符取得非负整数与非负整数减1的结果,不言而喻,始终都会返回0,为何呢?由于对应位的关系,咱们取其中一个为例子,以下:

0 = 0000 0000 0000 0000 0000 0000 0000 0000
1 = 0000 0000 0000 0000 0000 0000 0000 0001
——————————————————————————————————————————————
AND = 0000 0000 0000 0000 0000 0000 0000 0000

因此最终结果就是二进制数0000 0000 0000 0000 0000 0000 0000 0000,转换成十进制数就是0.

这样,咱们就应该知道了,若是这个非负整数是2的非负整数次幂的话,那么它与它减1两个操做数取按位与结果就应该是0.

而咱们知道逻辑非操做符对数值0会返回true的布尔值,因此当若是传入的参数是非负整数,而且仍是2的非负整数次幂的话,那么这个函数最终就会返回true.咱们能够直接调用这个函数,以下图所示:

clipboard.png

理解和掌握JavaScript位操做符,有助于咱们研究底层原理。

鄙人建立了一个QQ群,供你们学习交流,但愿和你们合做愉快,互相帮助,交流学习,如下为群二维码:

clipboard.png

相关文章
相关标签/搜索