# 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
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
2
3
同样,如果判断一个整数是否是偶数,那么可以将这个数 % 2
,然后查看结果是否是 0,如果是 0 就是偶数,否则是奇数。
一个数除以0,在其他语言中一般都是报错的,因为除数不能为0,但是 JS 不同。
console.log(3 / 0); // 结果为:Infinity
- 结果为
Infinity
,在 JS 中进行运算,要尽量避免出现Infinity
和NaN
。
算数运算符也是有优先级的,和在小学的时候学习的一样。
let i = 3 + 2 * 4 ** 2;
console.log(i); // 结果为:35
2
幂运算优先级 >
乘、除、取模运算 >
加、减运算。
# 2 JS的变态之处
继续看一下算数运算,这些在其他语言就做不到,但是这是 JS !
let result = 10 - '5'; // 数值减去字符串
console.log('result:', result) // 5
2
- JS 是弱类型语言,在进行算数运算的时候,除了加法(会拼接字符串),操作数是非数值时,都会转换为数值后,再进行运算。
- 所以上面会将
'5'
转换为数值 5 ,再进行运算。
再举几个例子:
console.log(10 - '') // 10
console.log(10 - true) // 9
console.log(10 - null) // 10
console.log(10 - undefined) // NaN
2
3
4
- 上面够变态吧,在数据转换章节讲到不同类型的数据,转换为数值时候对应的值,此时在进行算数运算的时候,会自动将其他数据类型转换为数值(加法除外)。
空格
转换为0
,true
转换为1
,null
转换为0
,undefined
为NaN
。如果忘记了,看一下上一章的数据转换小节。
通过这种方式,我们可以轻松将字符串转换为数值,举个栗子:
let a = '123'; // a是字符串
a = a - 0;
console.log(a, typeof a);
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
2
3
4
5
加法比较特别,下面单独看一下加法,字符串和任意数据做加法运算,会将其他的数据转换为字符串。
举个栗子:
console.log("hello" + 123); // 'hello123'
console.log("hello" + 1 + 2 + 3); // 'hello123'
console.log('' + 123); // '123' , 可以将数值转换为字符串
2
3
- 上面字符串在前面,会将后面的数据都转换为字符串进行拼接。
- 通过上面的方式,可以将数值转换为字符串,这是隐式的转换方式。
如果数值放在前面,则会先进行数值的计算,如下:
console.log(1 + 2 + 3 + "hello"); // '6hello'
console.log('' + 123); // '123' , 可以将数值转换为字符串
2
拼接 null
和 undefined
的结果如下:
let a = null;
console.log("hello" + a); // 'hellonull'
let b;
console.log("hello" + b); // 'helloundefined'
2
3
4
5
除了 NaN ** 0
( NaN
的 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
2
3
4
5
6
7
8
# 5.2 赋值运算符
赋值运算符就是用来给变量赋值的。
# 1 赋值运算符
运算符 | 描述 | 举例 |
---|---|---|
= | 赋值运算符 | 就是把 = 右边的值赋值给左边的变量,之前用过了。 |
例如:
let a = 2;
let b = 3;
let c = a + b;
c = c + a;
2
3
4
如果要交换两个变量的值,那么一般需要借助第三个变量:
let a = 2;
let b = 3;
// 交换两个变量的值
let temp = a;
a = b;
b = temp;
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
2
3
4
5
6
7
8
9
10
11
12
复合赋值运算符感觉这么写可读性还变差了,有什么好处呢?
- 更加简练;
- 有微小的性能优势;
# 3 空赋值运算符
空赋值运算符( ??=
)是一种较新的特性,于 ECMAScript 2020(ES11)中引入。它的作用是为变量赋值,但仅当该变量的值为 null
或 undefined
时才会执行赋值操作。
举个栗子:
let num = null;
num ??= 10; // num为null,所以此时会为num赋值为10;
num ??= 20; // num此时为10,不为null和undefined,所以无法为num赋值为20,num的值仍然为10
console.log(num); // 10
2
3
4
# 5.3 一元正负运算符
一元运算符就是只有一个参数的运算符,例如 typeof
就是只有一个参数, +
需要两个数相加,所以是二元运算符。
下面介绍的一元正负运算符,就是正负号,给一个数值添加正号,不会改变一个数,给一个数值添加负号,会取反,和初中学习的一样。
举个栗子:
正号如下:
let a = 10;
let b = +a; // 添加+号不会影响数值
console.log(b); // 10
a = -10;
b = +a; // 添加+号不会影响数值
console.log(b); // -10
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
2
3
4
5
6
7
正号看不出效果,但是可以使用正号隐式的将字符串转换为数值。
举个例子:
let a = '123';
a = +a;
console.log(a, typeof a); // 123 'number'
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
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
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
2
3
4
5
6
7
8
- 关系运算符得到的结果都是布尔类型。
如果比较的是两个字符串,会逐位比较每个字符的 Unicode 编码。
举个栗子:
console.log('a' > 'b'); // false
console.log('abc' > 'b'); // false
console.log('12' > '2'); // false
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
2
3
- 对不同的数据类型进行比较的时候,会先将其转换为数值,然后在进行比较。
需要注意,任何数值和 NaN
比较,结果都返回false。
let a = 'abc';
console.log(a >= 3); // false
console.log(a <= 3); // false
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;
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,离谱吧;
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;
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
2
3
4
- 逻辑非就是对一个布尔值取反,
true
变false
,false
变true
; - 所以上面
a
的值变为false
,对a
取反赋值给b
,b
的值变为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
2
3
4
5
6
7
- 取反的时候,会首先将其他数据类型转换为布尔值(参考
Boolean()
函数),然后取反。
通过上面的特性,可以隐式的实现将其他数据类型转换为布尔值,只需要进行两次取反就可以。
let a = !!123;
console.log(a, typeof a); // true 'boolean'
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
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
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
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
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
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
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
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
2
3
4
5
先运算 !a
,结果为 false
,然后运算 a && b
,结果为 false
,最后运算 ||
两边的结果,为 false
。
如果搞不清楚先后运算的顺序,就用括号 ()
括起来,准没错。
# 5 逻辑短路
逻辑短路就是在进行逻辑运算的时候,如果已经能够得到最后的值,就不会再继续进行判断了。
只有 与运算符 &&
和 或运算符 ||
存在逻辑短路。
举个栗子:
let num = 10;
let b = num > 20 && num++ < 40;
console.log(num); // 10
2
3
在执行上面代码的时候,num > 20
已经不成立了,而 &&
运算需要两边都满足,所以后面的条件成不成立都不会影响最终逻辑表达式的结果,所以 &&
后面的 num++ < 40
就不会再继续执行了,那么 num
的值就没有 ++
,这就是逻辑短路。
其实逻辑或就是找 false,找到了就不继续了。
同样 || 运算符:
let num = 10;
let b = num < 20 || num++ > 40;
console.log(num); // 10
2
3
在执行上面代码的时候,num < 20
已经成立了,||
运算只要一边满足即可,所以整个逻辑表达式肯定是成立的,所以 ||
后面的 num++ > 40
就不会继续执行了,那么 num
的值就没有 ++
。
逻辑或就是找 true,找到了就不执行了。
如果不想逻辑短路,可以使用 &
、|
运算符,一个&
或 |
时,左边无论真假,右边都进行运算。
以逻辑与,修改上面的代码:
let num = 10;
let b = (num > 20) & (num++ < 40);
console.log(num); // 11
2
3
使用 &
运算符,就不存在逻辑短路问题,无论左边的运算结果为 true
或 false
,右边都会执行运算。
&
、|
运算符用的不多,开发中主要使用 &&
和 ||
。
# 5.8 总结隐式类型转换
总结一下数据类型转换的方式:
类型转换 | 显式转换 | 隐式转换 |
---|---|---|
转换为字符串 | String(a) | '' + a |
转换为数值 | Number(a) | +a |
转换为布尔值 | Boolean(a) | !!a |
# 5.9 运算符优先级
上面那么多运算符,如果同时运算的时候,优先级是怎么样的呢?
优先级:算术运算符 > 关系运算符 > 逻辑运算符
再细分的话,优先级从高到低如下:
** 指数
* / % 乘除取模
+ - 加减
> < >= <= 比较
== != === !== 相等性
&& 逻辑与
|| 逻辑或
?? 空值合并
2
3
4
5
6
7
8
举个栗子:
let value = 5 + 3 * 2 ** 2 > 20 && 10 < 15 || false;
console.log(value); // 输出: false
2
看着上面的代码,眼不眼晕。在实际的开发中,不要这样写,可读性太差了。
怎么办?
加括号就行了,括号的优先级最高!
所以上面的代码,根据优先级括号,应该是这样的:
let value = ((5 + (3 * (2 ** 2))) > 20) && (10 < 15) || false;
console.log(value); // 输出: false
2
在开发中,不知道优先级高低,添加括号就完事了。