JavaScript基础(一)——位操作符

JavaScript基础(一)——位操作符

  • 位操作符用于在最基本的层次上,即按内存(Memory)中表示数值的位来操作数值。ECMAScript中的所有数值都以IEEE-754 64格式存储,但位操作符并不直接操作64位的值。而是先将64位的值转换成32位的整数,然后执行操作,最后再结果转换回64位。
  • 对于有符号的整数, 32位中的前31位用于表示整数的值第32位用于表示数值的符号: 0表示正数, 1表示负数。这个表示符号的位叫做符号位, 符号位的值决定了其他位数值的格式。 其中, 正数以纯二进制格式存储, 31位中的每一位都表示2的幂。第一位(叫做位0)表示2^0, 第二位表示2^1, 以此类推。没有用到的位以0填充, 即忽略不计。例如, 数值18的二进制表示是00000000000000000000000000010010, 或者更简洁的10010。这是5个有效位, 这5位本身就决定了实际的值。
1         0         0         1         0
(2^4*1) + (2^3*0) + (2^2*0) + (2^1*1) + (2^0*0)
16 + 0 + 0 + 2 + 0
18 
  • 负数同样以二进制码存储, 但使用的格式是二进制补码。计算一个数值的二进制补码, 需要经过下列3个步骤:
    • 求这个数值绝对值的二进制码(例如, 要求-18的二进制补码, 先求18的二进制码);
    • 求二进制反码, 即将0替换为1, 将1替换为0;
    • 得到的二进制反码加1。
  • 要根据这3个步骤求得-18的二进制码, 首先就要求得18的二进制码, 即:
0000 0000 0000 0000 0000 0000 0001 0010
  • 然后, 求其二进制反码, 即0和1互换:
1111 1111 1111 1111 1111 1111 1110 1101
  • 最后, 二进制反码加1:
1111 1111 1111 1111 1111 1111 1110 1101
                                      1
---------------------------------------
1111 1111 1111 1111 1111 1111 1110 1110
  • 这样, 就求得了-18的二进制表示, 即1111 1111 1111 1111 1111 1111 1110 1110。要注意的是, 在处理有符号整数时, 是不能访问位31的。
  • ECMAScript会尽力向我们隐藏所有这些信息。换句话说, 在以二进制字符串形式输出一个负数时, 我们看到的只是这个负数绝对值的二进制码前面加上一个负号。如下面的例子所示:
var num = -18;
console.log(num.toString(2)); // "-10010"
  • 要把数值-18转换成二进制字符串时, 得到的结果是"-10010"。这说明转换过程理解了二进制补码并将其以更合符逻辑的形式展示了出来。

    课外知识: 默认情况下, ECMAScript中的所有整数都是有符号整数。不过, 当然也存在无符号整数。对于无符号整数来说, 第32位不再表示符号, 因为无符号整数只能是正数。而且, 无符号整数的值可以更大, 因为多出的一位不再表示符号, 可以用来表示数值。

  • 在ECMAScript中, 当对数值应用位操作符时, 后台会发生如下转换过程: 64位的数值被转换成32位数值, 然后执行位操作符, 最后再将32位的结果转换回64位数值。这样, 表面上看起来就好像是在操作32位数值, 就跟在其他语言中以类似方式执行二进制操作一样。但这个转换过程也导致了一个严重的副效应, 即在对特殊的NaNInfinity值应用位操作符时, 这两个值都会被当成0来处理。

  • 如果对非数值应用操作符, 会先使用Number()函数将该值转换为一个数值(自动完成), 然后再应用位操作。得到的结果将是一个数值。

按位非(NOT)

  • 按位非操作符由一个波浪线(~)表示, 执行按位非的结果就是返回数值的反码。按位非是ECMAScript操作符中少数几个与二进制计算有关的操作符之一。下面看一个例子:
var num1 = 25; // 二进制 0000 0000 0000 0000 0000 0000 0001 1001
var num2 = -num1; //二进制 1111 1111 1111 1111 1111 1111 1110 0110
console.log(num2); //-26 
  • 这里, 对25执行按位非操作, 结果得到了-26。这也验证了按位非操作的本质: 操作数的负值减1。因此, 下面的代码也能得到相同的结果:
var num1 = 25;
var num2 = -num1 - 1;
console.log(num2); // -26
  • 虽然以上代码也能返回同样的结果, 但由于按位非是在数值表示的是最底层执行操作, 因此速度更快。

按位与(AND)

  • 按位与操作符由一个和号字符(&)表示,它有两个操作符数。从本质上讲,按位与操作就是将两个数值得每一位对齐,然后根据下表中的规则,对相同位置上的两个数执行AND操作:
第一个数值的位 第二个数值的位 结果
1 1 1
1 0 0
0 1 0
0 0 0

- 简而言之,按位与操作只在两个数值的对应位都是1时才返回1,任何一位是0,结果都是0。

var result = 25 & 3;
console.log(result); //1
  • 可见,对25和3执行按位与操作的结果为1,至于为什么了?我们下面进行分析
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3  = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND= 0000 0000 0000 0000 0000 0000 0000 0001
  • 原来,25和3的二进制码对应上只有一位同时是1,而其他位的结果自然都是0,因此最终结果等于1。

按位或(OR)

  • 按位或操作符由一个竖线符号(|)表示,同样也有两个操作数。按位或操作遵循下面这个真值表。
第一个数值的位 第二个数值的位 结果
1 1 1
1 0 1
0 1 1
0 0 0

- 由此可见,按位或操作在有一个位是1的情况下就返回1,而只有在两个都为0的情况下才返回0。如果在前面按位与的例子中对25和3执行按位或操作,则代码如下所示:

var result = 25 | 3;
console.log(result); //27
  • 25与3按位或的结果是27:
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3  = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011
  • 这两个数值的都包含4个1,因此可以把每个1直接放到结果中。二进制码11011转换为十进制为27。

按位异或(XOR)

  • 按位异或操作符由一个插入符号(^)表示,也有两个操作数。以下是按位异或的真值表。
第一个数值的位 第二个数值的位 结果
1 1 0
1 0 1
0 1 1
0 0 0

- 按位异或与按位或的不同之处在于,这个操作在两个数值对应位上只有一个1时才返回1,如果对应的两位都是1或者都是0,则返回0。
- 对25和3执行按位异或操作的代码如下所示:

var result = 25 ^ 3;
console.log(result);
  • 25与3按位异或的结果是26,其底层操作如下所示:
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3  = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR= 0000 0000 0000 0000 0000 0000 0001 1010
  • 这两个数值都包含4个1,但第一位上则都是1,因此结果的第一位变成了0。而其他位上的1在另外一个数值中都没有对应的1,可以直接放到结果中。二进制码11010等于十进制26(注意这个结果比执行按位或时小1)。

左移

  • 左移操作符是由两个小于号(<<)表示,这个操作符会将数值的所有位向左移动指定的位数。例如,如果将数值2(二进制码为10 )向左移动5位,结果就是64(二进制码为1000000),代码如下所示:
var oldValue = 2; // 等于二进制的10
var newValue = oldValue << 5; // 等于二进制的1000000,十进制的64
  • 注意,在向左移位后,原数值的右侧多出了5个空位。左移操作会以0来填充这些空位,以便得到的结果是一个完整的32位二进制数。
数值2
0000 0000 0000 0000 0000 0000 0000 0010
|
隐藏的符号位
将数值2向左移5位(得到64)
0000 0000 0000 0000 0000 0000 0010 00000填充

注意,左移不会影响操作数的符号位。换句话说,如果将-2向左移动5位,结果将是-64,而非64。

-2 = 1111 1111 1111 1111 1111 1111 1111 1110
向左移动5位
1111 1111 1111 1111 1111 1111 1110 0000
数值为-64

有符号的右移

  • 有符号的右移操作由两个大于号(>>)表示,这个操作符会将数值向右移动,但保留符号位(即正负号标记)。有符号的右移操作与左移操作恰好相反,即如果将64向右移动5位,结果将变回2:
var oldValue = 64; // 等于二进制的1000000
var newValue = oldvalue >> 5; //等于二进制的10,也就是十进制的2
  • 同样,在移位过程中,原数值中也会出现空位。只不过这次的空位出现在原数值的左侧、符号位的右侧。而此时ECMAScript会用符号位的值来填充所有空位,以便得到一个完整的值。
数值64
0000 0000 0000 0000 0000 0000 0010 0000
|
隐藏的符号位
0000 0000 0000 0000 0000 0000 0000 0010
 |    |         0填充(符号位的值)

无符号右移

  • 无符号右移操作符由3个大于号(>>>)表示,这个操作符会将数值的所有32位都像右移动。对正数来书,无符号右移的结果与有符号右移相同。仍以前面有符号右移的代码为例,如果将64无符号右移5位,结果仍然还是2:
var oldValue = 64; // 等于二进制的1000000
var newValue = oldValue >>> 5; // 等于二进制的10,即十进制的2
  • 但是对于负数来说,情况就不一样了。首先,无符号右移是以0来填充空位,而不是像有符号右移那样以符号位的值来填充空位。所以,对正数的无符号右移与有符号右移结果相同,但对负数的结果就不一样了。其次,无符号右移操作符会把负数的二进制码当成正数的二进制码。而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常之大,如下面的例子所示:
var oldValue = -64; // 等于二进制的1111 1111 1111 1111 1111 1111 1111 1110 0000
var newValue = oldValue >>> 5; 
console.log(newValue); // 等于十进制的134217726
  • 这里,当对-64执行无符号右移5位的操作后,得到的结果是134217726。之所以结果如此之大,是因为-64的二进制码为1111 1111 1111 1111 1111 1111 1110 0000,而且无符号右移操作会把这个二进制码当成正数的二进制码,换算成十进制就是4294967232。如果把这个值右移5位,结果就变成了0000 0111 1111 1111 1111 1111 1111 1110,也就是十进制的134217726

JackDan Thinking

猜你喜欢

转载自blog.csdn.net/XXJ19950917/article/details/80211543