有趣的 Js运算符用法

更新日期: 2019-08-18阅读: 1.8k标签: 运算

JavaScript 提供了几种运算符,可以用来对简单的值执行一些基本操作,如算术运算、赋值、逻辑运算、按位运算等。JavaScript 代码中赋值、算术和逻辑三种运算符经常会混合使用,但按位运算符就用得没那么多了。


JavaScript 按位运算符

  1. ~ - 按位非(NOT)

  2. & - 按位与(AND)

  3. | - 按位或(OR)

  4. ^ - 按位异或(XOR)

  5. << - 左移

    • 符号传播(有符号)右移
    • 零填充(无符号)右移

这篇教程会逐一介绍 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

检查设置位

& 运算符还有其他一些位掩码用途。一种用途是检查给定的位序列是否设置了一个或多个位。例如我们要检查右数第五个比特位是否设置为某个十进制数字,就可以使用 & 运算符来执行此操作:

  • 首先创建一个位掩码,用于检查目标位(这里是第五位)是否设置为 1。位掩码上的其他位都设置为 0,但目标位设置为 1。二进制数字字面量如下:
const mask =0b10000;
  • 接下来,使用十进制数和位掩码作为操作数执行 & 操作,并将结果与​​位掩码对比。如果所有目标位都设置为这个十进制数,则 & 操作的结果将等于位掩码。注意,由于 A&0 = 0,位掩码中的 0 位将关闭十进制数中的对应位。
// 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 表示转换为十六进制表示。

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/

链接: https://www.fly63.com/article/detial/4981

js除了Math.floor方法,还可以通过位运算|,>>实现向下取整

我们都知道通过Math.floor()方法可实现数值的向下取整,得到小于或等于该数字的最大整数。除了Math.floor方法,还可以使用位运算|,>>来实现向下取整哦

es6 扩展运算符 三个点(...)

扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

巧用JS位运算

位运算的方法在其它语言也是一样的,不局限于JS,所以本文提到的位运算也适用于其它语言。位运算是低级的运算操作,所以速度往往也是最快的

js中使用位运算,让执行效率更高

平常的数值运算,其本质都是先转换成二进制再进行运算的,而位运算是直接进行二进制运算,所以原则上位运算的执行效率是比较高的,由于位运算的博大精深,下面通过一些在js中使用位运算的实例

js各种取整方式及方法_四舍五入、向上取整、向下取整

js实现:四舍五入、向上取整、向下取整等方法。parseInt、Math.ceil、Math.round、Math.floor、toFixed等的使用

JavaScript循环计数器

JS经常会遇到延迟执行的动作,并且失败后自动尝试,尝试N次之后就不再尝试的需求,今天刚好又遇到,于是写个闭包,以后不断完善继续复用。检查并计数第一个参数用来标记是尝试哪个动作的,第二个参数是最大尝试次数

js 位运算符_js按位运算符及其妙用

大多数语言都提供了按位运算符,恰当的使用按位运算符有时候会取得的很好的效果。在我看来按位运算符应该有7个:& 按位与、| 按位或、^ 按位异或、~ 按位非

PHP取整、四舍五入取整、向上取整、向下取整、小数截取

PHP取整数函数常用的四种方法:1.直接取整,舍弃小数,保留整数:intval(); 2.四舍五入取整:round(); 3.向上取整,有小数就加1:ceil(); 4.向下取整:floor()。

JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )

ECMAScript 中的相等操作符由两个等于号 ( == ) 表示,如果两个操作数相等,则返回 true。相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性。

关于js开发中保留小数位计算函数(以向上取整或向下取整的方式保留小数)

前端工作中经常遇到数字计算保留小数问题,由于不是四舍五入的方式不能使用toFixed函数,本文采用正则表达式匹配字符串的方式,解决对数字的向上或向下保留小数问题:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!