1. JS 运算符

# 1. JS 运算符

除了标准的算术运算符(+, - ,* /),JavaScript还提供了下表中的算术运算符。

Operator Description Example
求余( % ) 二元运算符. 返回相除之后的余数. 12 % 5 返回 2。
自增( ++ ) 一元运算符. 将操作数的值加一. 如果放在操作数前面 ( ++x ), 则返回加一后的值; 如果放在操作数后面 ( x++ ), 则返回操作数原值, 然后再将操作数加一. var x=3; console.log(++x); //4 console.log(x); //4 var y=3; console.log(y++); //3 console.log(y); //4
自减( -- ) 一元运算符. 将操作数的值减一. 前后缀两种用法的返回值类似自增运算符. var x=3; console.log(--x); //输入2, x=2var y=3; console.log(y--); //输出3, x=2;
一元负值符( - ) 一元运算符, 返回操作数的负值. var x=3; console.log(-x); //输入-3
一元正值符(+) 一元运算符, 如果操作数在之前不是number,试图将其转换为number console.log( +'3' ); // 3 console.log( '3' ); // '3' console.log(+true); // 1
指数运算符(**) 计算 base(底数)exponent( 指数 )次方 , 表示为 baseexponent 2 ** 3 returns 8 . 10 ** -1 returns 0.1 .

# 位运算符

位运算符将它的操作数视为32位元的二进制串(0和1组成)而非十进制八进制或十六进制数。例如:十进制数字9用二进制表示为1001,位运算符就是在这个二进制表示上执行运算,但是返回结果是标准的JavaScript数值。

下表总结了 JavaScript 的位运算符。

Operator Usage Description
按位与[ AND] a & b 在a, b的位表示中,每一个对应的位都为1则返回1, 否则返回0.
按位或[ OR] a | b 在a, b的位表示中,每一个对应的位,只要有一个为1则返回1, 否则返回0.
按位异或[ XOR] a ^ b 在a, b的位表示中,每一个对应的位,两个不相同则返回1,相同则返回0.
按位非[ NOT] ~ a 反转被操作数的位。
左移[ shift] a << b 将a的二进制串向左移动b位, 右边移入0.
算术右移 a >> b 把a的二进制表示向右移动b位,丢弃被移出的所有位.(译注: 算术右移左边空出的位是根据最高位是0和1来进行填充的)
无符号右移(左边空出位用0填充) a >>> b 把a的二进制表示向右移动b位,丢弃被移出的所有位,并把左边空出的位都填充为0

# 位逻辑运算符

概念上来讲, 位逻辑运算符工作流程如下:

  • 操作数被转换为32bit整數,以位序列(0和1组成)表示. 若超過32bits,則取低位32bit,如下所示:

  • Before: 11100110111110100000000000000110000000000001 After: 10100000000000000110000000000001

  • 第一个操作数的每一位都与第二个操作数的对应位组对: 第一位对应第一位, 第二位对应第二位, 以此类推.

  • 运算符被应用到每一对"位"上, 最终的运算结果由每一对“位”的运算结果组合起来.

例如, 十进制数9的二进制表示是1001, 十进制数15的二进制表示是1111. 因此, 当位运算符应用到这两个值时, 结果如下:

表达式 结果 二进制描述
15 & 9 9 1111 & 1001 = 1001
15 | 9 15 1111 | 1001 = 1111
15 ^ 9 6 1111 ^ 1001 = 0110
~15 -16 ~ 00000000... 00001111 = 1111 1111 ... 11110000
~9 -10 ~ 00000000 ... 0000 1001 = 1111 1111 ... 1111 0110

注意位运算符“非”将所有的32位取反,而值的最高位(最左边的一位)为1则表示负数(2-补码表示法)。

# 移位运算符

移位运算符带两个操作数:第一个是待移位的数,第二个是指定第一个数要被移多少位的数。移位的方向由运算符来控制.

移位运算符把操作数转为32bit整数,然后得出一个与待移位数相同种类的值。

移位运算符列表如下。

移位运算符

运算符 描述 范例
<< (左移位) 将第一个操作数向左移动指定数量的位. 左边移出位被抛弃. 左边移出的几位被丢弃. 右边多出的空位由0补齐 9<<2产生36,因为1001移位2比特向左变为100100,它是36。
>> (带符号右移) 将第一个操作数向右移动指定数量的位. 右边移出位被抛弃. 左边多出的空位由原值的最左边数字补齐. 9>>2产生2,因为1001移位2位向右变为10,其是2。同样,-9>>2产生-3,由于符号被保留。
>>> (补零右移) 将第一个操作数向右移动指定数量的位. 右边移出位被抛弃. 左边多出的空位由0补齐. 19>>>2 产生4,因为10011移位2位向右变为100,它是4。对非负数值,补零右移和带符号右移产生相同结果。

# 逻辑运算符 (opens new window)

逻辑运算符常用于布尔(逻辑)值之间; 当操作数都是布尔值时,返回值也是布尔值。 不过实际上 &&|| 返回的是一个特定的操作数的值,所以当它用于非布尔值的时候,返回值就可能是非布尔值。 逻辑运算符的描述如下。

逻辑运算符

运算符 范例 描述
逻辑与 && expr1 && expr2 (逻辑与) 如果expr1能被转换为false,那么返回expr1;否则,返回 expr2 。因此 ,&& 用于布尔值时,当操作数都为true时返回true;否则返回false.
逻辑或 || expr1 || expr2 (逻辑或) 如果expr1能被转换为true,那么返回expr1;否则,返回 expr2 。因此,||用于布尔值时,当任何一个操作数为true则返回true;如果操作数都是false则返回false。
逻辑非 (!) !expr (逻辑非) 如果操作数能够转换为true则返回false;否则返回true。能被转换为 false 的值有 null , 0 , NaN , 空字符串("")和 undefined 。(译者注:也可以称作”falsy“)

下面是&&(逻辑"与")操作符的示例。

var a1 = true && true; // t && t returns true
var a2 = true && false; // t && f returns false
var a3 = false && true; // f && t returns false
var a4 = false && (3 == 4); // f && f returns false
var a5 = "Cat" && "Dog"; // t && t returns Dog
var a6 = false && "Cat"; // f && t returns false
var a7 = "Cat" && false; // t && f returns false

Copy to Clipboard

下面是||(逻辑"或")操作符的示例。

var o1 = true || true; // t || t returns true
var o2 = false || true; // f || t returns true
var o3 = true || false; // t || f returns true
var o4 = false || (3 == 4); // f || f returns false
var o5 = "Cat" || "Dog"; // t || t returns Cat
var o6 = false || "Cat"; // f || t returns Cat
var o7 = "Cat" || false; // t || f returns Cat

Copy to Clipboard

下面是!(逻辑"非")操作符的示例。

var n1 = !true; // !t returns false
var n2 = !false; // !f returns true
var n3 = !"Cat"; // !t returns false

Copy to Clipboard

# 短路求值

作为逻辑表达式进行求值是从左到右,它们是为可能的“短路”的出现而使用以下规则进行测试:

  • false && anything // 被短路求值为false
  • true || anything // 被短路求值为true

逻辑的规则,保证这些评估是总是正确的。请注意,上述表达式的 anything 部分不会被求值,所以这样做不会产生任何副作用。

# 字符串运算符

除了比较操作符,它可以在字符串值中使用,连接操作符(+)连接两个字符串值相连接,返回另一个字符串,它是两个操作数串的结合。

# 这些 JS 中强大的操作符

JS 里的操作符大家每天都在使用,还有一些 ES2020、ES2021 新加的实用操作符,这些共同构成了 JS 灵活的语法生态。本文除介绍常用的操作符之外,还会介绍 JS 里一些不常用但是很强大的操作符,下面我们一起来看看吧~

# 1. 数值分割符 _

ES2021 引入了数值分割符 _ ,在数值组之间提供分隔,使一个长数值读起来更容易。Chrome 已经提供了对数值分割符的支持,可以在浏览器里试起来。

let number = 100_0000_0000_0000 // 0太多了不用数值分割符眼睛看花了
console.log(number) // 输出 100000000000000

此外,十进制的小数部分也可以使用数值分割符,二进制、十六进制里也可以使用数值分割符。

0x11_1 === 0x111 // true 十六进制
0.11_1 === 0.111 // true 十进制的小数
0b11_1 === 0b111 // true 二进制

# 2. 逗号运算符 ,

什么,逗号也可以是运算符吗?是的,曾经看到这样一个简单的函数,将数组的第一项和第二项调换,并返回两项之和:

function reverse(arr) {
    return [arr[0], arr[1]] = [arr[1], arr[0]], arr[0] + arr[1]
}
const list = [1, 2]
reverse(list) // 返回 3,此时 list 为[2, 1]

逗号操作符对它的每个操作数求值(从左到右),并返回最后一个操作数的值。

expr1, expr2, expr3...

会返回最后一个表达式 expr3 的结果,其他的表达式只会进行求值。

# 3. 零合并操作符 ??

零合并操作符 ?? 是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回右侧操作数,否则返回左侧操作数。

expr1 ?? expr2

空值合并操作符一般用来为常量提供默认值,保证常量不为 null 或者 undefined ,以前一般使用 || 来做这件事 variable = variable || 'bar' 。然而,由于 || 是一个布尔逻辑运算符,左侧的操作数会被强制转换成布尔值用于求值。任何假值( 0''NaNnullundefined )都不会被返回。这导致如果你使用 0''NaN 作为有效值,就会出现不可预料的后果。

正因为 || 存在这样的问题,而 ?? 的出现就是解决了这些问题, ?? 只会在左侧为 undefinednull 时才返回后者, ?? 可以理解为是 || 的完善解决方案。

可以在浏览器中执行下面的代码感受一下:

undefined || 'default' // 'default'
null || 'default' // 'default'
false || 'default' // 'default'
0 || 'default' // 'default'

undefined ?? 'default' // 'default'
null ?? 'default' // 'default'
false ?? 'default' // 'false'
0 ?? 'default' // 0

另外在赋值的时候,可以运用赋值运算符的简写 ??=

let a = {
    b: null,
    c: 10
}
a.b ?? = 20
a.c ?? = 20
console.log(a) // 输出 { b: 20, c: 10 }

# 4. 可选链操作符 ?.

可选链操作符 ?. 允许读取位于连接对象链深处的属性的值,而不必验证链中的每个引用是否有效。 ?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为 null 或者 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined

当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。

const obj = {
    a: 'foo',
    b: {
        c: 'bar'
    }
}

console.log(obj.b?.c) // 输出 bar
console.log(obj.d?.c) // 输出 undefined
console.log(obj.func?.()) // 不报错,输出 undefined

以前可能会通过 obj && obj.a && obj.a.b 来获取一个深度嵌套的子属性,现在可以直接 obj?.a?.b 即可。

可选链除了可以用在获取对象的属性,还可以用在数组的索引 arr?.[index] ,也可以用在函数的判断 func?.(args) ,当尝试调用一个可能不存在的方法时也可以使用可选链。

调用一个对象上可能不存在的方法时(版本原因或者当前用户的设备不支持该功能的场景下),使用可选链可以使得表达式在函数不存在时返回 undefined 而不是直接抛异常。

const result = someInterface.customFunc?.()

# 5. 私有方法/属性

在一个类里面可以给属性前面增加 # 私有标记的方式来标记为私有,除了属性可以被标记为私有外, getter/setter 也可以标记为私有,方法也可以标为私有。

class Person {
    getDesc() {
        return this.#name + ' ' + this.#getAge()
    }

    #getAge() {
        return this.#age
    } // 私有方法

    get #name() {
        return 'foo'
    } // 私有访问器
    #age = 23 // 私有属性
}
const a = new Person()
console.log(a.age) // undefined 直接访问不到
console.log(a.getDesc()) // foo 23

# 6. 位运算符 >> 与 >>>

有符号右移操作符 >> 将第一个操作数向右移动指定的位数,多余的位移到右边被丢弃,高位补其符号位,正数补 0,负数则补 1。因为新的最左位与前一个最左位的值相同,所以符号位(最左位)不会改变。

(0b111 >> 1).toString(2) // "11"
(-0b111 >> 1).toString(2) // "-100" 感觉跟直觉不一样

正数的好理解,负数怎么理解呢,负数在计算机中存储是按照补码来存储的,补码的计算方式是取反加一,移位时将补码形式右移,最左边补符号位,移完之后再次取反加一求补码获得处理后的原码。

-111 // 真值
1 0000111 // 原码(高位的0无所谓,后面加不到)
1 1111001 // 补码
1 1111100 // 算数右移
1 0000100 // 移位后求补码获得原码
    -
    100 // 移位后的真值

一般我们用 >> 来将一个数除 2,相当于先舍弃小数位然后进行一次 Math.floor

10 >> 1 // 5
13 >> 1 // 6 相当于
13.9 >> 1 // 6
    -
    13 >> 1 // -7 相当于
    -
    13.9 >> 1 // -7

无符号右移操作符 >>> ,将符号位作为二进制数据的一部分向右移动,高位始终补 0,对于正整数和算数右移没有区别,对于负数来说由于符号位被补 0,成为正数后就不用再求补码了,所以结果总是非负的。即便右移 0 个比特,结果也是非负的。

(0b111 >>> 1).toString(2) // "11"
(-0b111 >>> 1).toString(2) // "1111111111111111111111111111100"

可以这样去理解

-111 // 真值
1 000000000000000000000000000111 // 原码
1 111111111111111111111111111001 // 补码
0 111111111111111111111111111100 // 算数右移(由于右移后成为正数,就不要再求补码了)
1073741820 // 移位后的真值

左移运算符 << 与之类似,左移很简单左边移除最高位,低位补 0:

(0b1111111111111111111111111111100 << 1).toString(2) // "-1000"
(0b1111111111111111111111111111100 << < 1).toString(2) // "-1000"

PS:JS 里面没有无符号左移,而且其他语言比如 JAVA 也没有无符号左移。

# 7. 位运算符 & 与 |

位运算符是按位进行运算, & 与、 | 或、 ~ 非、 ^ 按位异或:

&: 1010 |: 1010~: 1010 ^: 1010
0110 0110 0110
-- -- -- -- -- -- -- --
0010 1110 0101 1100

使用位运算符时会抛弃小数位,我们可以利用这个特性来给数字取整,比如给任意数字 & 上二进制的 32 个 1,或者 | 上 0,显而易见后者简单些。

所以我们可以对一个数字 | 0 来取整,负数也同样适用

1.3 | 0 // 1
    -
    1.9 | 0 // -1

判断奇偶数除了常见的取余 % 2 之外,也可以使用 & 1 ,来判断二进制数的最低位是不是 1,这样除了最低位之外都被置 0,取余的结果只剩最低位,是不是很巧妙。负数也同样适用:

const num = 3!!(num & 1) // true
    !!(num % 2) // true

# 8. 双位运算符 ~~

可以使用双位操作符来替代正数的 Math.floor( ) ,替代负数的 Math.ceil( ) 。双否定位操作符的优势在于它执行相同的操作运行速度更快。

Math.floor(4.9) === 4 // true
    // 简写为:
    ~~4.9 === 4 // true

不过要注意,对正数来说 ~~ 运算结果与 Math.floor( ) 运算结果相同,而对于负数来说与 Math.ceil( ) 的运算结果相同:

~~4.5 // 4
Math.floor(4.5) // 4
Math.ceil(4.5) // 5

    ~~-4.5 // -4
Math.floor(-4.5) // -5
Math.ceil(-4.5) // -4

PS:注意 ~~(num/2) 方式和 num >> 1 在值为负数时的差别

# 9. 短路运算符 && 与 ||

我们知道逻辑与 && 与逻辑或 || 是短路运算符,短路运算符就是从左到右的运算中前者满足要求,就不再执行后者了。

可以理解为:

  • && 为取假运算,从左到右依次判断,如果遇到一个假值,就返回假值,以后不再执行,否则返回最后一个真值
  • || 为取真运算,从左到右依次判断,如果遇到一个真值,就返回真值,以后不再执行,否则返回最后一个假值
let param1 = expr1 && expr2
let param2 = expr1 || expr2
运算符 示例 说明
&& expr1&&expr2 如果 expr1 能转换成 false 则返回 expr1,否则返回 expr2。 因此,在 Boolean 环境中使用时, 两个操作结果都为 true 时返回 true,否则返回 false
|| expr1||expr2 如果 expr1 能转换成 true 则返回 expr1,否则返回 expr2。 因此, 在 boolean 环境(在if的条件判断中)中使用时, 二者操作结果中只要有一个为 true, 返回 true;二者操作结果都为 false 时返回 false
! !expr 如果单个表达式能转换为 true 的话返回 false,否则返回 true

因此可以用来做很多有意思的事,比如给变量赋初值:

let variable1
let variable2 = variable1 || 'foo'

如果 variable1 是真值就直接返回了,后面短路就不会被返回了,如果为假值,则会返回后面的 foo

也可以用来进行简单的判断,取代冗长的 if 语句:

let variable = param && param.prop
// 有了可选链之后可以直接 param?.prop

如果 param 如果为真值则返回 param.prop 属性,否则返回 param 这个假值,这样在某些地方防止 paramundefined 的时候还取其属性造成报错。

# 10. void 运算符

void` 运算符 对给定的表达式进行求值,然后返回 `undefined

可以用来给在使用立即调用的函数表达式(IIFE)时,可以利用 void 运算符让 JS 引擎把一个 function 关键字识别成函数表达式而不是函数声明。

function iife() {
    console.log('foo')
}() // 报错,因为JS引擎把IIFE识别为了函数声明
void

function iife() {
    console.log('foo')
}() // 正常调用
~ function iife() {
    console.log('foo')
}() // 也可以使用一个位操作符
(function iife() {
    console.log('foo')
})() // 或者干脆用括号括起来表示为整体的表达式

还可以用在箭头函数中避免传值泄漏,箭头函数,允许在函数体不使用括号来直接返回值。这个特性给用户带来了很多便利,但有时候也带来了不必要的麻烦,如果右侧调用了一个原本没有返回值的函数,其返回值改变后,会导致非预期的副作用。

const func = () => void customMethod() // 特别是给一个事件或者回调函数传一个函数时

安全起见,当不希望函数返回值是除了空值以外其他值,应该使用 void 来确保返回 undefined ,这样,当 customMethod 返回值发生改变时,也不会影响箭头函数的行为。

# 11. 其他常用操作符

  1. 三元表达式:很简单了,大家经常用,expr ? expr1 : expr2 如果 expr 为真值则返回 expr1,否则返回 expr2
  2. 赋值运算符简写:加法赋值 +=、减法赋值 -=、乘法赋值 *=、除法赋值 /=、求幂赋值 **=、按位或复制 |=、按位与赋值 &=、有符号按位右移赋值 >>=、无符号按位右移赋值 >>>=、逻辑空赋值 ??= ....
  3. 求幂运算符var1 ** var2 相当于 Math.pow,结果为 var1var2 次方

# 12. 操作符优先级

正因为有操作符优先级,所以 variable = 1, 2 的含义是将变量先赋值为 1,再返回数字 2,而不是变量赋值给 1, 2 的返回值 2,这是因为 = 运算符的优先级高于 , 逗号运算符。再比如表达式 6 - 2 * 3 === 0 && 1- * === && 这四个运算符优先级最高的 * 先运算,然后 - 运算符结果为 0, === 运算符优先级高于 &&true && 1 的结果为 1,所以这就是运算的结果。

下面的表将运算符按照优先级的不同从高(20)到低(1)排列,但这个不是最新的,至少没包括可选链,建议参考这个表 (opens new window)或者 MDN (opens new window)

网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出。

参考文档:

  1. 运算符优先级 - JavaScript | MDN (opens new window)
  2. JS 中可以提升幸福度的小技巧 (opens new window)
  3. 这些 JS 中强大的操作符,总有几个你没听说过 (opens new window)
  4. 4个未听说过的强大JavaScript操作符 (opens new window)
  5. 聊聊JavaScript中的二进制数 (opens new window)