一、位运算符
位运算符处理 32 位数
该运算中的任何数值运算数都会被转换为 32 位数,结果会被转换回 JavaScript 数
位运算符用于直接对二进制进行计算,共有七个运算符:
或运算(or):符号为 |,若两个二进制位都为0,则结果为0,否则为 1;
与运算符(and):符号为 & ,若两个二进制位都为 1,则结果为1,否则为 0;
否运算(not):符号为 ~ ,对一个二进制位取反;
异或运算(xor):符号为 ^ ,若两个二进制位不相同,则结果为1,否为0。
左移运算(left shift):符号为 <<
右移运算(right shift):符号为 >>
带符号位的右移运算(zero filled right shift):符号为 >>>
二、位运算符简介
位运算符直接处理每一个比特位(bit),所以是非常底层的运算。
好处:运算速度极快;
缺点:运算很不直观,许多场合不能使用,否则会使代码难以理解和查错。
注意:位运算符只对整数起作用,如果一个运算子不是整数,则会自动转成整数后再执行。
在JavaScript内部,数值都是以64位浮点数的形式储存的,但是在做位运算的时候,是以32位带符号的整数进行运算的,并且返回的值也是一个32位带符号的整数。
三、各位运算符的特点
(一)或运算 和 与运算
逐个位数比较两个运算子
或运算:两个二进制位中只要有一个为 11,就返回 1,否则就返回 0;
与运算:两个二进制位中只要有一位为 0,就返回为 0,否则就返回 1;
0|3; //3 0&3; //0 3|4 //7 3&4 //0
在上面的 0和 3的表达式中,0和 3的二进制形式分别为 00 和 11,所以进行"或运算" 会得到 11(即3);进行“与运算” 会得到 00(即0);
在上面的 3和 4的表达式中,3和 4的二进制形式分别为 11 和 001,所以进行"或运算" 会得到 111(即7);进行“与运算” 会得到 0(即0);
注意:
位运算只对整数有效,遇到小数时,会将小数部分舍去,只保留整数部分;
所以将一个小数与 0 进行“或运算”,等同于对该数去除小数部分,即取整数位数。(这种取整方式不适用于超过二进制32位整数最大值 2147483648 的数)
2.7|0; //2 -3.5|0; //-3 2147483647.4 | 0; //2147483647 2147483648.4 | 0; //-2147483648 2147483649.4 | 0; //-2147483647
(二)否运算
否运算:将每个二进制位都变成相反数(即 0 变为 1,1 变为 0),运算机制不是很明显所以有时候会难以理解。
~ 3 // 4
在这个表达式中对 3进行 ‘否运算’ 后,得到了-4。
原因是位运算时,JavaScript 内部将所有的运算子都转换为 32位的二进制整数然后再进行运算。
3 在JavaScript内部是 0000,0000,0000,0000,0000,0000,0000,0011(共32位),否运算后得到11111111111111111111111111111100 ,由于第一位是 1,所以这个数是负数。JavaScript内部采取补码形式表示负数,即需要将这个数减去1,再取一次反,然后加上符号,才能得到这个负数对应的十进制值。这个数减去1等于 11111111111111111111111111111011 ,再取一次反得到 000000000000000000000000000000100,加上负号就是 -4。
这个过程这样计算未免太过麻烦,也可以简单记忆成,一个数与自身的取反值相加,等于 -1。(3的取反值为-4,-3的取反值是2)
注意:
1、对一个整数连续两次 ‘否运算’,可以得到它自身;
2、所有的位运算只对整数有效。否运算遇到小数时,也会将小数部分舍去,只保留整数部分,所以,对一个小数连续两次否运算,就可以达到取整的效果。
3、使用否运算取整,是所有取整方法中最快的一种。
~ -3 // 2 ~~3 // 3 ~~2.9 // 2 ~~47.11 // 47 ~~1.9999 // 1 ~~3 // 3
1、字符串类型:对字符串进行否运算,JavaScript引擎会先调用 Number 函数,将字符串转为数值。
下面例子相当于 ~Number('011')
~'011' // -12 ~'42 cats' // -1 ~'0xcafebabe' // 889275713 ~'deadbeef' // -1
下面例子相当于 ~~Number('011')
~~'011'; // 11 ~~'42 cats'; // 0 ~~'0xcafebabe'; // -889275714 ~~'deadbeef'; // 0
2、数值的处理是:超出 32位的整数将会被截去超出的位数,NaN 和 Infinity 转为 0;
3、对于其他类型的参数,否运算也是先用 Number 转为数值,然后再进行处理。
~~[] // 0 ~~NaN // 0 ~~null // 0
(三)异或运算
异或运算:在两个二进制位不同时返回 1,相同时返回 0;
0 ^ 3 // 3
在这个表达式中,0 的二进制形式是 00,3的二进制形式是 11,它们每一个二进制位都不同,所以得到 11(即3)
异或运算特殊运用:
连续对两个数 a和b 进行 3 次异或运算(a^b, b^a, a^b),可以互换他们的值。这意味着,使用 异或运算 可以在不引用临时变量的前提下,互换两个变量的值。
运用异或运算互换两个变量的值。这是互换变量值最快的方法。
var a = 10; var b = 99; a ^= b, b ^= a, a ^= b; a // 99 b // 10
通过引用临时变量,互换两个变量的值。
var a = 10; var b = 99; var c; c=a;a=b;b=c; console.log(a) // 99 console.log(b) // 10
异或运算也可以用来取整,与 0 一起运算
12.9 ^ 0 // 12
(四)左移运算符
左移运算符:表示将一个数的二进制值向左移动指定的位数,在尾部补 0,即乘以 2 的指定次方(最高位即符号位不参加移动)
// 4 的二进制形式为100, // 左移一位为1000(即十进制的8) // 相当于乘以2的1次方 4 << 1 // 8 -4 << 1 // -8
这个例子中, -4左移一位得到 -8,是因为 -4 的二进制形式是 11111111111111111111111111111100 ,左移一位后得到 11111111111111111111111111111000,把这个数字转换为十进制(减去1后取反,再加上符号)结果为 -8;
如果左移 0 位,就相当于将该数值转换为 32位整数,等同于取整,对于正数和负数 都有效。
13.5 << 0 // 13 -13.5 << 0 // -13
左移运算用于二进制取值非常方便:
实例:
使用左移运算符,将颜色的RGB值转换为 HEX值
var colo {r: 186, g: 218, b: 85}; // RGB to HEX // (1 << 24)的作用为保证结果是6位数 var rgb2hex = function(r, g, b) { return '#' + ((1 << 24) + (r << 16) + (g << 8) + .toString(16) .substr(1); }rgb2hex(color.r,color.g,color.b) // "#bada55"
(五)右移运算符
右移运算符:表示将一个数的二进制值向右移动指定的位数,头部补 0,即除以 2 的指定次方(最高位即符号位不参与移动)
4 >> 1
在这个例子中,因为 4的二进制形式为 00000000000000000000000000000100,向右移动一位之后(在头部补 0 ),得到 00000000000000000000000000000010,即为十进制的 2。
-4 >> 1
在这个例子中,-4 的二进制形式为 11111111111111111111111111111100,右移一位,头部补 1(二进制采取补码的形式表示负数),得到 11111111111111111111111111111100,即为十进制的 -2。
使用右移运算符模拟 2 的倍数
5 >> 1 // 相当于 5 / 2 = 2,101转换为10,即为2 21 >> 2 // 相当于 21 / 4 = 5,10101转换为101,即为5 21 >> 3 // 相当于 21 / 8 = 2,10101转换为10,即为2 21 >> 4 // 相当于 21 / 16 = 1,10101转换为1,即为1
(六)带符号位的右移运算符(>>>)
带符号的右移运算符:表示将一个数的二进制形式向右移动,包括符号位也参与移动,头部补 0。
所以,该运算总是得到正数。对于正数,该运算的结果与右移运算符(>>)完全一致,区别只要在于负数。
4>>>1
-4>>>1
在这个例子中,-4 的二进制形式为 11111111111111111111111111111100,带符号位的右移一位,头部补0,得到 0111111111111111111111111111110,即为十进制的 2147483646。
这个运算实际上将一个值转为 32 位无符号整数。
查看一个负整数在计算机内部的储存形式,最快的方法就是使用这个运算符。
-1>>>1
-1 作为 32 位整数时,内部储存形式使用无符号正数格式解读,值为4294967295(即 ( 2^32)-1,等于11111111111111111111111111111111)
四、设置对象属性开关
位运算符可以用作设置对象属性的开关。
加定某个对象有四个开关,每个开关都是一个变量。那么就可以设置一个四位的二进制数,它的每个位都对应一个开关。
var flag_A = 1; // 0001 var flag_B = 2; // 0010 var flag_C = 4; // 0100 var flag_D = 8; // 1000
上面代码设置 A、B、C、D四个开关,每个开关分别占了有一个二进制位。然后,就可以使用 ‘与运算’ 检验,当前设置是否打开了指定开关。
var flags = 5; // 二进制的0101 if (flags & FLAG_C) { // ... } // 0101 & 0100 => 0100 => true
上面代码检验是否打开了 开关C。如果打开,返回 true,否则返回 false。
现在假设 需要打开ABD三个开关,可以构建一个掩码变量
var mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011
上面代码对A B D三个变量进行 ‘或运算’,得到掩码值为二进制的1011。有了掩码,‘或运算’ 可以确保打开指定的开关。
flags = flags | mask;
“与运算” 可以将当前设置中凡是与开关设置不一样的项,全部关闭
flags = flags & mask;
“异或运算” 可以切换(toggle)当前设置,即第一次执行可以得到当前设置的相反值,再执行一次,可以得到原来的值。
flags = flags ^ mask;
“否运算”可以翻转当前设置,即原设置为 0 ,运算后变为 1 ;原设置为 1 ,运算后变为 0;
flags = ~flag