JavaScript 提供了几种运算符,可以用来对简单的值执行一些基本操作,如算术运算、赋值、逻辑运算、按位运算等。JavaScript 代码中赋值、算术和逻辑三种运算符经常会混合使用,但按位运算符就用得没那么多了。
~ - 按位非(NOT)
& - 按位与(AND)
| - 按位或(OR)
^ - 按位异或(XOR)
<< - 左移
这篇教程会逐一介绍 JavaScript 的按位运算符,并总结它们的运算机制。我们还会研究一些实际的 JavaScript 程序案例。这里还要了解一下 JavaScript 按位运算符是怎样将其操作数表示为有符号的 32 位整数的。
~ 运算符是一个一元运算符,只需要一个操作数。~ 运算符对其操作数的每一比特位都执行一次非(NOT)运算。非运算的结果称为反码。一个整数的反码就是整数的每个比特位都反转的结果。
对于给定的整数,比如 170,可以使用~ 运算符计算反码,如下所示:
//170 => 00000000000000000000000010101010
//--------------------------------------
//~ 00000000000000000000000010101010
//--------------------------------------
//= 11111111111111111111111101010101
//--------------------------------------
//= -171(decimal)
console.log(~170);//-171
JavaScript 按位运算符将其操作数转换为补码格式的 32 位有符号整数。因此对整数使用~ 运算符时,结果值是整数的补码。整数 A 的补码由 -(A + 1) 给出。
~170=> -(170+1) =>-171
关于 JavaScript 按位运算符使用的 32 位有符号整数有几点注意事项:
最高位(最左边)称为符号位。正整数的符号位始终为 0,负整数的符号位始终为 1。
除符号位之外的剩余 31 位用于表示整数。因此可以表示的最大 32 位整数是 (2^32 - 1),即 2147483647,而最小整数是 -(2^31),即 -2147483648。
对于超出 32 位有符号整数范围的整数,最高位将被逐一丢弃,直到整数落在范围内。
以下是一些重要数字的 32 位序列表示:
0=>00000000000000000000000000000000
-1=>11111111111111111111111111111111
2147483647=>01111111111111111111111111111111
-2147483648=>10000000000000000000000000000000
从上面的表示法中可以看出:
~0=>-1
~-1=>0
~2147483647=>-2147483648
~-2147483648=>2147483647
大多数 JavaScript 内置对象(如数组和字符串)都带有一些方法,可用于查找数组中的项目或字符串中的子字符串。下面是其中一些方法:
Array.indexOf()
Array.lastIndexOf()
Array.findIndex()
String.indexOf()
String.lastIndexOf()
String.search()
这些方法都返回项目或子字符串的从零开始的索引(如果找得到);否则会返回 -1。例如:
constnumbers = [1,3,5,7,9];
console.log(numbers.indexOf(5));// 2
console.log(numbers.indexOf(8));// -1
如果我们对找到的项或子字符串的索引不感兴趣,则可以使用布尔值,这样未找到项目或子字符串时返回 false 来代替 -1,而找到其他值时返回 true。代码如下:
functionfoundIndex(index) {
returnBoolean(~index);
}
在上面的代码中,~ 运算符在 -1 上使用时计算结果为 0,这是一个虚值。因此使用 Boolean() 将虚值转换为布尔值,返回 false。对于其他索引值则返回 true。因此上面的代码段可以修改如下:
constnumbers = [1,3,5,7,9];
console.log(foundIndex(numbers.indexOf(5)));// true
console.log(foundIndex(numbers.indexOf(8)));// false
& 运算符对其操作数的每对比特位执行与(AND)运算。仅当两个位都为 1 时,& 运算符才返回 1;否则它返回 0。因此,与操作的结果相当于将每对比特位相乘。
对于一对比特位来说,与运算可能的值如下所示。
(0&0) ===0// 0 x 0 = 0
(0&1) ===0// 0 x 1 = 0
(1&0) ===0// 1 x 0 = 0
(1&1) ===1// 1 x 1 = 1
& 运算符在位掩码应用中通常用来对特定的位序列关闭某些位。因为对于任意给定的位 A 来说:
(A&0 = 0) - 对应的位是 0 时,该位关闭。
(A&1 = A) - 对应的位是 1 时,该位不变。
例如我们有一个 8 位整数,我们希望关闭右数前 4 位(设置为 0),就可以用 & 运算符实现此目的:
首先创建一个位掩码,其效果是关闭 8 位整数的右数前 4 位。这个位掩码为 0b11110000。注意位掩码的右数前 4 位设置为 0,其他位设置为 1。
接下来对 8 位整数和刚创建的位掩码执行 & 操作:
constmask =0b11110000;
// 222 => 11011110
// (222 & mask)
// ------------
// 11011110
// & 11110000
// ------------
// = 11010000
// ------------
// = 208 (decimal)
console.log(222& mask);// 208
& 运算符还有其他一些位掩码用途。一种用途是检查给定的位序列是否设置了一个或多个位。例如我们要检查右数第五个比特位是否设置为某个十进制数字,就可以使用 & 运算符来执行此操作:
const mask =0b10000;
// 34 => 100010
// (34 & mask) => (100010 & 010000) = 000000
console.log((34& mask) === mask);// false
// 50 => 110010
// (50 & mask) => (110010 & 010000) = 010000
console.log((50& mask) === mask);// true
进一步扩展上面的用途,可以用 & 运算符检查给定的十进制数是偶数还是奇数。这里的位掩码是 1(以确定最右位是否设置过了)。
对于整数来说,可以使用最低位(最右位)来确定该数字是偶数还是奇数。如果最低位被打开了(设置为 1),则该数字为奇数;否则就是偶数。
functionisOdd(int){
return(int&1) ===1;
}
functionisEven(int){
return(int&1) ===0;
}
console.log(isOdd(34));// false
console.log(isOdd(-63));// true
console.log(isEven(-12));// true
console.log(isEven(199));// false
以下是一些 & 运算的实用等式(对于任何有符号的 32 位整数 A 来说):
(A & 0)===0
(A & ~A)===0
(A & A)=== A
(A & -1)=== A
| 运算符对其操作数的每对比特位执行或(OR)运算。只有当两个位都为 0 时|运算符才返回 0;否则它返回 1。
对于一对比特位来说,或运算的可能结果如下:
(0|0) ===0
(0|1) ===1
(1|0) ===1
(1|1) ===1
针对位掩码用途,| 运算符可用来打开一个位序列中的某些位(设置为 1)。因为对于任意给定的位 A 来说:
(A | 0 = A) - 对应的位是 0 时,该位保持不变。
(A | 1 = 1) - 对应的位是 1 时,该位打开。
例如我们有一个 8 位整数,我们希望所有偶数位(第二、四、六、八位)都打开(设置为 1)。于是可以用| 运算符实现此目的:
首先创建一个位掩码,其效果是打开 8 位整数的每个偶数位。这个位掩码是 0b10101010。注意位掩码的偶数位设置为 1,其他位设置为 0。
接下来对 8 位整数和刚创建的位掩码执行| 操作:
constmask =0b10101010;
// 208 => 11010000
// (208 | mask)
// ------------
// 11010000
// | 10101010
// ------------
// = 11111010
// ------------
// = 250 (decimal)
console.log(208| mask);// 250
以下是一些|的实用等式(对于任何有符号的 32 位整数 A 来说):
(A | 0)=== A
(A | ~A)===-1
(A | A)=== A
(A | -1)===-1
^ 运算符对其操作数的每对比特位执行异或(XOR)运算。如果两个位相同(0 或 1),^ 运算符返回 0;否则它返回 1。
对于一对比特位来说,异或运算的可能结果如下。
(0^0) ===0
(0^1) ===1
(1^0) ===1
(1^1) ===0
针对位掩码用途,^ 运算符通常用于翻转位序列中的某些位。因为对于任何指定的位 A 来说:
其对应的位是 0 时,该位保持不变。(A ^ 0 = A)
对应的位是 1 时,该位始终翻转。
(A ^ 1 = 1) - 如果 A 为 0
(A ^ 1 = 0) - 如果 A 是 1
例如我们有一个 8 位整数,我们希望每个位都被翻转,最低位和最高位除外。这里可以用 ^ 运算符实现,如下所示:
首先创建一个位掩码,其效果是翻转 8 位整数除最低位和最高位之外的每个位。这个位掩码是 0b01111110。注意,要翻转的位设置为 1,其他位设置为 0。
接下来对 8 位整数和刚创建的位掩码执行 ^ 操作:
constmask =0b01111110;
// 208 => 11010000
// (208 ^ mask)
// ------------
// 11010000
// ^ 01111110
// ------------
// = 10101110
// ------------
// = 174 (decimal)
console.log(208^ mask);// 174
下面是一些 ^ 运算的实用等式(对于任何有符号的 32 位整数 A 来说):
(A ^ 0)=== A
(A ^ ~A)===-1
(A ^ A)===0
(A ^ -1)=== ~A
如上所示,对 A 和 -1 执行异或运算相当于对 A 执行非运算。因此之前的 foundIndex() 函数也可以这样写:
functionfoundIndex(index) {
returnBoolean(index^ -1);
}
左移(<<)运算符需要两个操作数。第一个操作数是整数,而第二个操作数是第一个操作数要向左移位的位数。右面空出来的位用 0 填充,左边移出去的位会被丢弃。
例如对整数 170 来说,假设我们想要向左移三位,对其使用 << 运算符如下所示:
//170 => 00000000000000000000000010101010
//170 << 3
//--------------------------------------------
//(000)00000000000000000000010101010(***)
//--------------------------------------------
//=(***)00000000000000000000010101010(000)
//--------------------------------------------
//= 00000000000000000000010101010000
//--------------------------------------------
//= 1360(decimal)
console.log(170 << 3);//1360
可以使用以下 JavaScript 表达式定义左移位运算符(<<):
(A << B)=> A *(2 ** B)=> A * Math.pow(2, B)
套用前面的例子:
(170<<3) =>170* (2**3) =>170*8=>1360
左移(<<)运算符的一个常见用途是将颜色从 RGB 表示转换为十六进制表示。
RGB 颜色的每个分量的值在 0 到 255 之间。简单来说,每个颜色值刚好能用 8 位表示。
0=>0b00000000(binary) =>0x00(hexadecimal)
255=>0b11111111(binary) =>0xff(hexadecimal)
因此颜色可以用 24 位(红色,绿色和蓝色各 8 位)完美表示。右数前 8 位表示蓝色分量,接下来的 8 位表示绿色分量,最后的 8 位表示红色分量。
(binary) =>111111110010001100010100
(red) =>11111111=> ff =>255
(green) =>00100011=>23=>35
(blue) =>00010100=>14=>20
(hex) => ff2314
现在我们已经知道如何将颜色表示为 24 位序列了,下面探讨如何用各个分量值组成 24 位颜色。假设我们有一个由 rgb(255,35,20) 表示的颜色。下面是组合方法:
(red) =>255=>00000000000000000000000011111111
(green) =>35=>00000000000000000000000000100011
(blue) =>20=>00000000000000000000000000010100
// 重新排列位,补上必要的 0
// 使用左移运算符
(red <<16) =>00000000111111110000000000000000
(green <<8) =>00000000000000000010001100000000
(blue) =>00000000000000000000000000010100
// 使用或 (|) 运算符组合起来
// ( red << 16 | green << 8 | blue )
00000000111111110000000000000000
|00000000000000000010001100000000
|00000000000000000000000000010100
// -----------------------------------------
00000000111111110010001100010100
// -----------------------------------------
这样流程就很清楚了。这里用了一个简单的函数,其将颜色的 RGB 值作为输入数组,并根据上述过程返回对应的十六进制颜色表示:
function rgbToHex ([red=0,green=0,blue=0] = []) {
return`#${(red<<16|green<<8|blue).toString(16)}`;
}
符号传播右移(>>)运算符需要两个操作数。第一个操作数是一个整数,而第二个操作数是第一个操作数需要向右移的位数。
向右移多出来的位会被丢弃,左边空出来的位用符号位(最左位)的副本填充。结果整数的符号不变。这就是这个运算符名称的来历。
例如给定整数 170 和 -170。假设我们想要向右移三位。我们可以使用 >> 运算符操作如下:
//170 => 00000000000000000000000010101010
//-170 => 11111111111111111111111101010110
//170 >> 3
//--------------------------------------------
//(***)00000000000000000000000010101(010)
//--------------------------------------------
//=(000)00000000000000000000000010101(***)
//--------------------------------------------
//= 00000000000000000000000000010101
//--------------------------------------------
//= 21(decimal)
//-170 >> 3
//--------------------------------------------
//(***)11111111111111111111111101010(110)
//--------------------------------------------
//=(111)11111111111111111111111101010(***)
//--------------------------------------------
//= 11111111111111111111111111101010
//--------------------------------------------
//= -22(decimal)
console.log(170 >> 3);//21
console.log(-170 >> 3);//-22
符号传播右移位运算符(>>)可以通过以下 JavaScript 表达式来描述:
(A >> B)=> Math.floor(A / (2 ** B)) => Math.floor(A / Math.pow(2, B)
套回前面的例子:
(170>>3) => Math.floor(170/ (2**3)) => Math.floor(170/8) =>21
(-170>>3) => Math.floor(-170/ (2**3)) => Math.floor(-170/8) =>-22
右移(>>)运算符的一个常见用途是从颜色中提取 RGB 颜色值。当颜色以 RGB 表示时很容易区分红色、绿色和蓝色分量值。但对于十六进制的颜色表示来说就没那么直观了。
在上一节中,我们知道了从各个分量(红色、绿色和蓝色)组成颜色是怎样的过程。这个过程倒过来就能用来提取颜色的各个分量的值。下面来试一试。
假设我们有一个由十六进制表示为#ff2314 的颜色。以下是这个颜色的有符号 32 位表示:
(color) => ff2314 (hexadecimal) =>111111110010001100010100(binary)
// 32-bit representation of color
00000000111111110010001100010100
为了获得各个分量,我们将颜色位右移 8 的倍数,直到右数前 8 位是我们需要的分量为止。由于 32 位颜色的最高位是 0,我们可以安全地使用符号传播右移(>>)运算符。
color =>00000000111111110010001100010100
// Right shift the color bits by multiples of 8
// Until the target component bits are the first 8 bits from the right
red => color >>16
=>00000000111111110010001100010100>>16
=>00000000000000000000000011111111
green => color >>8
=>00000000111111110010001100010100>>8
=>00000000000000001111111100100011
blue => color >>0=> color
=>00000000111111110010001100010100
现在右起前 8 位就是我们的目标分量,我们需要一种方法来屏蔽掉前 8 位之外的位数。这里又要使用与(&)运算符。记住 & 运算符可用于关闭某些位。
先来创建所需的位掩码。如下所示:
mask =>00000000000000000000000011111111
=>0b11111111(binary)
=>0xff(hexadecimal)
位掩码准备就绪后,我们可以用它对先前右移操作的各个结果执行与(&)运算,以提取目标分量。
red => color >>16&0xff
=>00000000000000000000000011111111
=> &00000000000000000000000011111111
=> =00000000000000000000000011111111
=>255(decimal)
green => color >>8&0xff
=>00000000000000001111111100100011
=> &00000000000000000000000011111111
=> =00000000000000000000000000100011
=>35(decimal)
blue => color &0xff
=>00000000111111110010001100010100
=> &00000000000000000000000011111111
=> =00000000000000000000000000010100
=>20(decimal)
如上所示。这是一个简单的函数,它将十六进制颜色字符串(带有六个十六进制数字)作为输入,并返回对应的 RGB 颜色分量值数组。
function hexToRgb (hex) {
hex = hex.replace(/^#?([0-9a-f]{6})$/i, '$1');
hex = Number(`0x${hex}`);
return [
hex >>16&0xff,// red
hex >>8&0xff,// green
hex &0xff// blue
];
}
零填充右移(>>>)运算符的行为很像符号传播右移(>>)运算符。它们的关键区别在于左边填充的位数。
顾名思义,这里左边空出来的位都是用 0 填充的。因此 >>> 运算符始终返回无符号的 32 位整数,因为结果的符号位始终为 0。对于正整数来说,>> 和 >>> 将始终返回相同的结果。
例如对于整数 170 和 -170 来说,假设我们要向右移 3 位,可以使用 >>> 运算符操作如下:
//170 => 00000000000000000000000010101010
//-170 => 11111111111111111111111101010110
//170 >>> 3
//--------------------------------------------
//(***)00000000000000000000000010101(010)
//--------------------------------------------
//=(000)00000000000000000000000010101(***)
//--------------------------------------------
//= 00000000000000000000000000010101
//--------------------------------------------
//= 21(decimal)
//-170 >>> 3
//--------------------------------------------
//(***)11111111111111111111111101010(110)
//--------------------------------------------
//=(000)11111111111111111111111101010(***)
//--------------------------------------------
//= 00011111111111111111111111101010
//--------------------------------------------
//= 536870890(decimal)
console.log(170 >>> 3);//21
console.log(-170 >>> 3);//536870890
最后讨论另一个非常常见的按位运算符和位掩码应用:配置标志(flag)。
假设我们有一个函数,其接受一些 boolean 选项,这些选项可用于控制函数的运行方式或返回的值类型。一种方法是将所有选项作为参数传递给函数,可能还有一些默认值,如下所示:
function doSomething (optA=true,optB=true,optC=false,optD=true, ...) {
// 这个函数做一些事情……
}
当然这样不太方便。下面两种情况下这种方法会变得非常复杂:
假如有超过 10 个布尔选项。我们无法使用那么多参数定义我们的函数。
假如我们只想为第五个和第九个选项指定新的值,其他选项则保留其默认值。此时我们需要调用该函数,对其他选项的参数传递默认值,对第五个和第九个选项则传递指定的值。
解决这个问题的一种方法是使用一个对象作为配置选项,如下所示:
constdefaultOptions={
optA:true,
optB:true,
optC:false,
optD:true,
...
};
functiondoSomething(options=defaultOptions){
//做一些事情……
}
这种方法非常优雅,最为常见。但使用这种方法时 options 参数始终是一个对象,如果只是用来配置选项的话未免杀鸡用牛刀了。
如果所有选项都采用布尔值,我们可以使用整数而不是对象来表示选项。在这种情况下,整数的特定位将映射到指定的选项。如果某个位被打开(设置为 1),则指定选项的值为 true;否则为 false。
举一个简单的例子。假设我们有一个函数来规范化包含数字的数组列表的项目并返回规范化数组。返回的数组可以通过三个选项控制,即:
fraction:将数组中的每个项目除以数组中的最大项目。
unique:从数组中删除重复的项目。
sorted:从最低到最高排序数组的项目。
我们可以使用 3 位整数来表示这些选项,每个位都映射到一个选项。下面的代码是选项标志:
constLIST_FRACTION =0x1;// (001)
constLIST_UNIQUE =0x2;// (010)
constLIST_SORTED =0x4;// (100)
要激活一个或多个选项时,可以使用| 运算符根据需要组合对应的标志。例如我们可以创建一个激活所有选项的标志,如下所示:
constLIST_ALL = LIST_FRACTION | LIST_UNIQUE | LIST_SORTED;// (111)
假设我们只希望默认激活 fraction 和 sorted 选项。我们可以使用| 运算符操作如下:
const LIST_DEFAULT = LIST_FRACTION | LIST_SORTED; // (101)
只有三个选项时看起来还不错,但选项变多时就会变得乱七八糟,还需要激活很多默认选项。在这种情况下,更好的方法是使用 ^ 运算符停用不需要的选项:
const LIST_DEFAULT = LIST_ALL ^ LIST_UNIQUE; // (101)
这里我们用 LIST_ALL 标志来激活所有选项。然后我们使用 ^ 运算符停用某些选项,其他选项则保持激活状态。
现在我们已经准备好了选项标志,就可以继续定义 normalizeList() 函数:
function normalizeList (list, flag = LIST_DEFAULT) {
if (flag & LIST_FRACTION) {
const max = Math.max(...list);
list = list.map(value => Number((value / max).toFixed(2)));
}
if (flag & LIST_UNIQUE) {
list = [...new Set(list)];
}
if (flag & LIST_SORTED) {
list = list.sort((a, b) => a - b);
}
return list;
}
要检查选项是否已激活,我们使用 & 运算符检查选项的对应位是否已打开(设置为 1)。& 操作是使用传递给函数的 flag 参数和选项的对应标志来执行的,如下所示:
// Checking if the unique option is activated
// (flag & LIST_UNIQUE) === LIST_UNIQUE (activated)
// (flag & LIST_UNIQUE) === 0 (deactivated)
flag & LIST_UNIQUE
文章比较长,读起来有些枯燥,恭喜你坚持看完了。
你也看到了,虽然 JavaScript 按位运算符用得不多,但它有一些非常有趣的用例。希望大家能在实际编程工作中用到本文学到的内容。
英文原文: https://blog.logrocket.com/interesting-use-cases-for-javascript-bitwise-operators/
我们都知道通过Math.floor()方法可实现数值的向下取整,得到小于或等于该数字的最大整数。除了Math.floor方法,还可以使用位运算|,>>来实现向下取整哦
扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
位运算的方法在其它语言也是一样的,不局限于JS,所以本文提到的位运算也适用于其它语言。位运算是低级的运算操作,所以速度往往也是最快的
平常的数值运算,其本质都是先转换成二进制再进行运算的,而位运算是直接进行二进制运算,所以原则上位运算的执行效率是比较高的,由于位运算的博大精深,下面通过一些在js中使用位运算的实例
js实现:四舍五入、向上取整、向下取整等方法。parseInt、Math.ceil、Math.round、Math.floor、toFixed等的使用
JS经常会遇到延迟执行的动作,并且失败后自动尝试,尝试N次之后就不再尝试的需求,今天刚好又遇到,于是写个闭包,以后不断完善继续复用。检查并计数第一个参数用来标记是尝试哪个动作的,第二个参数是最大尝试次数
大多数语言都提供了按位运算符,恰当的使用按位运算符有时候会取得的很好的效果。在我看来按位运算符应该有7个:& 按位与、| 按位或、^ 按位异或、~ 按位非
PHP取整数函数常用的四种方法:1.直接取整,舍弃小数,保留整数:intval(); 2.四舍五入取整:round(); 3.向上取整,有小数就加1:ceil(); 4.向下取整:floor()。
ECMAScript 中的相等操作符由两个等于号 ( == ) 表示,如果两个操作数相等,则返回 true。相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性。
前端工作中经常遇到数字计算保留小数问题,由于不是四舍五入的方式不能使用toFixed函数,本文采用正则表达式匹配字符串的方式,解决对数字的向上或向下保留小数问题:
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!