# JavaScript教程 - 5 运算符

在编写有逻辑的代码前,先学习一下 JS 中的运算符。

下面介绍 JS 中常见的运算符:算数运算符赋值运算符关系运算符


# 5.1 算数运算符

算数运算符是干嘛的?

就是进行数学运算的,就是小学学的加、减、乘、除等。

JS 中常见的算数运算符如下:

运算符 描述 示例
+ 加法 a + b
- 减法 a - b
* 乘法 a * b
/ 除法 a / b
% 取余,模运算 a % b
** 幂运算 a ** b

# 1 普通使用

举个例子:

let a = 10;
let b = 3;

console.log(a + b);  // 输出: 13
console.log(a - b);  // 输出: 7
console.log(a * b);  // 输出: 30
console.log(a / b);  // 输出: 3.3333333333333335
console.log(a % b);  // 输出: 1,10除以3,余数为1,取模就是取余数
console.log(a ** b); // 10的3次方,输出: 1000

console.log(100 ** 0.5); // 100的0.5次方,相当于100开平方,输出: 10
1
2
3
4
5
6
7
8
9
10
11

加减乘除在运算中用的是最多的,但是模运算(取余数)有时候也会用到,下面举个栗子:

判断一个数是否是 3 的倍数,那么判断这个数 % 3 是否等于0 就知道了。

let i = 27;
let j = i % 3;   // 除以3,余数是0,就是3的倍数
console.log(j);  // 结果为:0
1
2
3

同样,如果判断一个整数是否是偶数,那么可以将这个数 % 2,然后查看结果是否是 0,如果是 0 就是偶数,否则是奇数。


一个数除以0,在其他语言中一般都是报错的,因为除数不能为0,但是 JS 不同。

console.log(3 / 0);  // 结果为:Infinity
1
  • 结果为 Infinity,在 JS 中进行运算,要尽量避免出现 InfinityNaN

算数运算符也是有优先级的,和在小学的时候学习的一样。

let i = 3 + 2 * 4 ** 2;
console.log(i);  // 结果为:35
1
2

幂运算优先级 > 乘、除、取模运算 > 加、减运算

# 2 JS的变态之处

继续看一下算数运算,这些在其他语言就做不到,但是这是 JS !

let result = 10 - '5';  // 数值减去字符串
console.log('result:', result)  // 5
1
2
  • JS 是弱类型语言,在进行算数运算的时候,除了加法(会拼接字符串),操作数是非数值时,都会转换为数值后,再进行运算。
  • 所以上面会将 '5' 转换为数值 5 ,再进行运算。

再举几个例子:

console.log(10 - '')        // 10
console.log(10 - true)      // 9
console.log(10 - null)      // 10
console.log(10 - undefined) // NaN
1
2
3
4
  • 上面够变态吧,在数据转换章节讲到不同类型的数据,转换为数值时候对应的值,此时在进行算数运算的时候,会自动将其他数据类型转换为数值(加法除外)。
  • 空格转换为 0true 转换为 1null 转换为 0undefinedNaN。如果忘记了,看一下上一章的数据转换小节。

通过这种方式,我们可以轻松将字符串转换为数值,举个栗子:

let a = '123';  // a是字符串
a = a - 0;
console.log(a, typeof a);
1
2
3
  • 上面的代码会将 a 先转换为数值,然后减0,然后赋值给a,a就变成了数值。

还可以直接将两个字符串进行运算,会自动转换为数值进行运算:

console.log('10' - '5');  // 5
console.log('10' * '5');  // 20
console.log('10' / '5');  // 2
console.log('10' % '5');  // 0
console.log('10' ** '5');  // 100000
1
2
3
4
5

加法比较特别,下面单独看一下加法,字符串和任意数据做加法运算,会将其他的数据转换为字符串。

举个栗子:

console.log("hello" + 123); // 'hello123'
console.log("hello" + 1 + 2 + 3); // 'hello123'
console.log('' + 123); // '123' , 可以将数值转换为字符串
1
2
3
  • 上面字符串在前面,会将后面的数据都转换为字符串进行拼接。
  • 通过上面的方式,可以将数值转换为字符串,这是隐式的转换方式。

如果数值放在前面,则会先进行数值的计算,如下:

console.log(1 + 2 + 3 + "hello"); // '6hello'
console.log('' + 123); // '123' , 可以将数值转换为字符串
1
2

拼接 nullundefined 的结果如下:

let a = null;
console.log("hello" + a); // 'hellonull'

let b;
console.log("hello" + b); // 'helloundefined'
1
2
3
4
5

除了 NaN ** 0NaN 的 0 次幂)结果为 1,NaN 和其他非字符串数据的算术运算的结果都是 NaN。如果 NaN 和字符串相加,则是字符串拼接。

console.log(3 + NaN);  // NaN
console.log('3' - NaN);  // NaN
console.log('3' + NaN);  // 3NaN
console.log(NaN ** 0);  // 1
console.log(true + NaN);  // NaN
console.log(null + NaN);  // NaN
console.log(NaN + NaN);  // NaN
console.log(undefined + NaN);  // NaN
1
2
3
4
5
6
7
8

# 5.2 赋值运算符

赋值运算符就是用来给变量赋值的。

# 1 赋值运算符

运算符 描述 举例
= 赋值运算符 就是把 = 右边的值赋值给左边的变量,之前用过了。

例如:

let a = 2;
let b = 3;
let c = a + b;
c = c + a;
1
2
3
4

如果要交换两个变量的值,那么一般需要借助第三个变量:

let a = 2;
let b = 3;

// 交换两个变量的值
let temp = a;
a = b;
b = temp;
1
2
3
4
5
6
7

除了赋值运算符,下面还有复合赋值运算符。

# 2 复合赋值运算符

复合赋值运算符就是经过计算后,将值赋给前面的变量。

JS 中常用的复合赋值运算符如下:

运算符 描述 举例
+= 加法赋值运算符 c += a 等效于 c = c + a
-= 减法赋值运算符 c -= a 等效于 c = c - a
*= 乘法赋值运算符 c *= a 等效于 c = c * a
/= 除法赋值运算符 c /= a 等效于 c = c / a
%= 取模赋值运算符 c %= a 等效于 c = c % a
**= 幂运算赋值运算符 c **= a 等效于 c = c ** a

举个栗子:

let a = 5;
let b = 3;
a += b; // a = a + b
console.log(a); // 8

a = 5;
a -= b; // a = a - b
console.log(a); // 2

a = 5;
a *= b; // a = a * b
console.log(a); // 15
1
2
3
4
5
6
7
8
9
10
11
12

复合赋值运算符感觉这么写可读性还变差了,有什么好处呢?

  1. 更加简练;
  2. 有微小的性能优势;

# 3 空赋值运算符

空赋值运算符( ??= )是一种较新的特性,于 ECMAScript 2020(ES11)中引入。它的作用是为变量赋值,但仅当该变量的值为 nullundefined 时才会执行赋值操作。

举个栗子:

let num = null;
num ??= 10;  // num为null,所以此时会为num赋值为10;
num ??= 20;  // num此时为10,不为null和undefined,所以无法为num赋值为20,num的值仍然为10
console.log(num);  // 10
1
2
3
4

# 5.3 一元正负运算符

一元运算符就是只有一个参数的运算符,例如 typeof 就是只有一个参数, + 需要两个数相加,所以是二元运算符。

下面介绍的一元正负运算符,就是正负号,给一个数值添加正号,不会改变一个数,给一个数值添加负号,会取反,和初中学习的一样。

举个栗子:

正号如下:

let a = 10;
let b = +a;  // 添加+号不会影响数值
console.log(b); // 10

a = -10;
b = +a;  // 添加+号不会影响数值
console.log(b);  // -10
1
2
3
4
5
6
7
  • 正号添加了和没添加一样,看不出任何效果。

负号是取反,如下:

let a = 10;
let b = -a;  // 取反,变为-10
console.log(b); // -10

a = -10;
b = -a;  // 取反,变为10
console.log(b);  // 10
1
2
3
4
5
6
7

正号看不出效果,但是可以使用正号隐式的将字符串转换为数值。

举个例子:

let a = '123';
a = +a;
console.log(a, typeof a);  // 123 'number'
1
2
3
  • 上面通过 a = +a ,a 已经变为数值。和使用 Number() 函数是一样的,就是更简便一些。

# 5.4 自增自减运算符

JS 中的自增自减运算符包括++--。它们可以用于对变量进行加1或减1操作。

举个栗子:

let a = 5;
a++; // a自增1
console.log(a); // 输出 6

a--; // a自减1
console.log(a); // 输出 5
1
2
3
4
5
6

需要注意的是,自增自减运算符可以放在变量的前面或后面,这会影响到表达式的值。

如果运算符放在变量的前面(a++),表示先加减,后使用;如果运算符放在变量的后面(++a),表示先使用,后加减。

举个栗子:

let a = 5;
let b = a++;  // 先使用a的值还是5,后加1,所以执行此行代码后,b为5,a为6
console.log(b);  // 5
console.log(a);  // 6

let c = 5;
let d = ++c;  // 先加减c+1变为6,后使用,d的值为6
console.log(d);  // 6
console.log(c);  // 6
1
2
3
4
5
6
7
8
9

-- 操作符也是一样的。

# 5.5 关系运算符

关系运算符也就是比较运算符,主要有以下几种:

运算符 > < >= <=
含义 大于 小于 大于或者等于 小于或者等于

举个栗子:

let a = 10;
let b = 3;
let c = a > b;

console.log(c); // true
console.log(a < b); // false
console.log(a >= b); // true
console.log(a <= b); // false
1
2
3
4
5
6
7
8
  • 关系运算符得到的结果都是布尔类型

如果比较的是两个字符串,会逐位比较每个字符的 Unicode 编码。

举个栗子:

console.log('a' > 'b');   // false
console.log('abc' > 'b');   // false
console.log('12' > '2');   // false
1
2
3
  • 在 Unicode 编码中 a 是 97, b 是 98 ,所以 'a' < 'b' 为false;
  • 在比较的时候,逐位比较,得到结果了就终止比较了,所以和字符串长度无关;
  • 即使比较的是字符串数字,因为两个都是字符串,所以不会转换为数值,1 在 Unicode 编码中是 49,2 在 Unicode 中是50,所以 '12' > '2' ,不会比较后面的字符了。

除了数值和数值、字符串和字符串之间可以进行比较,不同的数据类型之间也可以比较。

举个栗子:

console.log(5 < '10');   // true
console.log('1' > false);   // true
console.log(1 > null);   // true
1
2
3
  • 对不同的数据类型进行比较的时候,会先将其转换为数值,然后在进行比较。

需要注意,任何数值和 NaN 比较,结果都返回false。

let a = 'abc';
console.log(a >= 3);  // false
console.log(a <= 3);  // false
1
2
3
  • 上面是字符串和数值比较,会将 a 先转换为数值,变为 NaN,然后和 3 比较,会发现结果都是 false。

# 5.6 相等运算符

相等运算符也是关系运算符,判断两个值是否相等,但是 JS 中的相等运算符比较特别,单独说一下。

相等运算符有四个:

运算符 == != === !==
含义 相等 不相等 全等 不全等

先来说一下相等不相等

举个栗子:

let a = 1;
let b = 1;
let c = '1';
let d = true;

console.log(a == b);  // true;
console.log(a != b);  // false;
console.log(a == c);  // true;
console.log(a == d);  // true;
1
2
3
4
5
6
7
8
9
  • 相等运算符就是判断两个值是否相等,不相等就是相等的取反。
  • 在比较的时候,如果是不同的数据类型,会先转换为相同的数据类型(通常转换为数值),然后在进行比较。

有几种特殊的情况需要注意一下:

let a = null;
let b = undefined;
let c = NaN;
let d = NaN;

console.log(a == b);  // true;
console.log(c == d);  // false;
console.log(c == c);  // false,离谱吧;
1
2
3
4
5
6
7
8
  • 上面的情况比较特殊,当比较 null 或 undefined 的时候,并不是转换为数值,因为 null 转换为数值是 0,undefined 转换为数值是 NaN,应该不相等,但是实际**null 是 == undefined 的**。
  • 另外需要注意的是 ,NaN 不和任何值相等,包括 NaN 自己,够离谱。

全等运算符是用来比较两个数值是否全等,全等和相等的区别就是不会进行数据类型的转换

举个栗子:

let a = 1;
let b = 1;
let c = '1';
let d = true;
let e = null;
let f = undefined;

console.log(a === b);  // true;
console.log(a !== b);  // false;
console.log(a === c);  // false;
console.log(a === d);  // false;
console.log(e === f);  // false;
1
2
3
4
5
6
7
8
9
10
11
12
  • 结果显而易见,不同的数据类型的值肯定是不全等的
  • 不全等就是对全等的取反。

推荐用全等来比较两个值,正常情况下比较两个数值的时候,最好先确定它们的数据类型是一致的。

# 5.7 逻辑运算符

逻辑运算符主要有以下三种:

运算符 && || !
含义 逻辑与,两边条件同时满足 逻辑或,两边条件满足一个即可 逻辑非,取反值,真为假,假为真

# 1 逻辑非

举个栗子:

let a = !true;
let b = !a;
console.log(a);  // false
console.log(b);  // true
1
2
3
4
  • 逻辑非就是对一个布尔值取反,truefalsefalsetrue
  • 所以上面 a 的值变为 false,对 a 取反赋值给 bb 的值变为 true

但是在 JS 中,逻辑非不仅可以对布尔值取反,还可以对其他数据类型取反。

举个栗子:

console.log(!123);        // false
console.log(!'');         // true
console.log(!'  ');       // false
console.log(!0);          // true
console.log(!NaN);        // true
console.log(!null);       // true
console.log(!undefined);  // true
1
2
3
4
5
6
7
  • 取反的时候,会首先将其他数据类型转换为布尔值(参考 Boolean() 函数),然后取反。

通过上面的特性,可以隐式的实现将其他数据类型转换为布尔值,只需要进行两次取反就可以。

let a = !!123;
console.log(a, typeof a);  // true 'boolean'
1
2
  • 123 进行一次取反是 false,再进行一次取反就变为 true 了,和 Boolean(123) 函数执行的结果一致,但是更方便。

# 2 逻辑与

逻辑与是对两个值进行运算,当左右两边的值都是 true,则返回true,否则返回false。

举个栗子:

let a = true;
let b = true;
let c = false;
console.log(a && b);  // true
console.log(a && c);  // false
1
2
3
4
5
  • a 和 b 都为 true,结果为 true;a 和 c 有一个不为 true,则结果为 false;

如果要判断一个数是否在 5 到10 之间,可以这样写:

let num = 7;
let result = num > 5 && num < 10;   // 判断 num 是否 大于5 同时 num 小于10
console.log(result);  // true
1
2
3
  • num > 5 && num < 10 表示同时满足左右两个条件,注意不能写成 5 < num < 10

逻辑与还可以对非布尔值数据进行运算,会将数据转换为布尔值,但是最终会返回数据的原值。

在进行逻辑与运算的时候,是找 false 的,当找到 false 了,就返回 false 数据的原值,如果一直没找到,就返回最后一个值。

举个栗子:

let a = 10;
let b = 0;
let c = NaN;
console.log(a && b);  // 0
console.log(b && a);  // 0
console.log(a && c && b);  // NaN
1
2
3
4
5
6
  • a && b :a 为 10,转换为 true,b 为 0,转换为 false,a 为true,继续找 false,b 为 false,找到了b,返回 b 的值 0;
  • b && a :b 转换为 false,找到了 false,返回 b 的值 0;
  • a && c && b :a 转换为 true,继续找,c 转换为 false,找到了false,返回 c 的值 NaN 。

# 3 逻辑或

逻辑或是对两个值进行运算,当左右两边的值有一个为 true,就返回true,否则返回false。

举个栗子:

let a = true;
let b = true;
let c = false;
let d = false;
console.log(a || b); // true
console.log(a || c); // true
console.log(c || d); // false
1
2
3
4
5
6
7
  • 两边都为 false ,结果才为 false。

如果要判断一个人岁数是否小于3 或者 大于 60,可以这样写:

let age = 48;
let result = age <= 3 || age >= 60;   // 判断age是否小于等于3或者大于等于60
console.log(result);  // false
1
2
3
  • age <= 3 || age >= 60 表示两边有一边满足即可。

逻辑或同样可以对非布尔值数据进行运算,运算的时候,是找 true 的,当找到 true 了,就返回 true 数据的原值,如果一直没找到,就返回最后一个值。

举个栗子:

let a = 10;
let b = 0;
let c = 'hello';
console.log(a || b);  // 10
console.log(b || a);  // 10
console.log(b || c || a);  // hello
1
2
3
4
5
6
  • a || b :a 转换为 true,找到了 true,则返回 a 的值 10 ;
  • b || a :b 转换为 false,则继续找 true,找到了 a,转换为 true,找到了 true,则返回 a 的值 10 ;
  • c || b || a :b 转换为 false,继续找,c 转换为 true,找到了true,则返回 b 的值 'hello' 。

# 4 逻辑运算符的优先级

优先级:! > && > ||

举个栗子:

let a = 3;
let b = 12;
let c = 6;
let d = 10;
console.log(a > 5 && b > 10 || c > 5 && d > 10); // false
1
2
3
4
5

当一个逻辑运算语句中同时包含与、或 、 非运算的时候,会按照优先级进行运算。

所以上面先运算 a > 5 && b > 10,然后运算 c > 5 && d > 10,最后运算 || 两边的结果。


再举个例子:

let a = true;
let b = false;

let result = !a || a && b;
console.log(result); // false
1
2
3
4
5

先运算 !a ,结果为 false,然后运算 a && b,结果为 false,最后运算 || 两边的结果,为 false

如果搞不清楚先后运算的顺序,就用括号 () 括起来,准没错。

# 5 逻辑短路

逻辑短路就是在进行逻辑运算的时候,如果已经能够得到最后的值,就不会再继续进行判断了。

只有 与运算符 && 和 或运算符 || 存在逻辑短路。

举个栗子:

let num = 10;
let b = num > 20 && num++ < 40;
console.log(num); // 10
1
2
3

在执行上面代码的时候,num > 20 已经不成立了,而 && 运算需要两边都满足,所以后面的条件成不成立都不会影响最终逻辑表达式的结果,所以 && 后面的 num++ < 40 就不会再继续执行了,那么 num 的值就没有 ++,这就是逻辑短路。

其实逻辑或就是找 false,找到了就不继续了。


同样 || 运算符:

let num = 10;
let b = num < 20 || num++ > 40;
console.log(num); // 10
1
2
3

在执行上面代码的时候,num < 20 已经成立了,|| 运算只要一边满足即可,所以整个逻辑表达式肯定是成立的,所以 || 后面的 num++ > 40 就不会继续执行了,那么 num 的值就没有 ++

逻辑或就是找 true,找到了就不执行了。


如果不想逻辑短路,可以使用 &| 运算符,一个&| 时,左边无论真假,右边都进行运算。

以逻辑与,修改上面的代码:

let num = 10;
let b = (num > 20) & (num++ < 40);
console.log(num); // 11
1
2
3

使用 & 运算符,就不存在逻辑短路问题,无论左边的运算结果为 truefalse,右边都会执行运算。

&| 运算符用的不多,开发中主要使用 &&||

# 5.8 总结隐式类型转换

总结一下数据类型转换的方式:

类型转换 显式转换 隐式转换
转换为字符串 String(a) '' + a
转换为数值 Number(a) +a
转换为布尔值 Boolean(a) !!a

# 5.9 运算符优先级

上面那么多运算符,如果同时运算的时候,优先级是怎么样的呢?

优先级:算术运算符 > 关系运算符 > 逻辑运算符

再细分的话,优先级从高到低如下:

** 指数
* / % 乘除取模
+ - 加减
> < >= <= 比较
== != === !== 相等性
&& 逻辑与
|| 逻辑或
?? 空值合并
1
2
3
4
5
6
7
8

举个栗子:

let value = 5 + 3 * 2 ** 2 > 20 && 10 < 15 || false;
console.log(value); // 输出: false
1
2

看着上面的代码,眼不眼晕。在实际的开发中,不要这样写,可读性太差了。

怎么办?

加括号就行了,括号的优先级最高!

所以上面的代码,根据优先级括号,应该是这样的:

let value = ((5 + (3 * (2 ** 2))) > 20) && (10 < 15) || false;
console.log(value); // 输出: false
1
2

在开发中,不知道优先级高低,添加括号就完事了。