Web/04-JavaScript基础/10-运算符.md
2022-07-22 17:06:42 +08:00

883 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 10-运算符
publish: true
---
<ArticleTopAd></ArticleTopAd>
我们在前面讲过变量,本文讲一下**运算符**和表达式。
## 运算符的定义和分类
### 运算符的定义
**运算符**:也叫操作符,是一种符号。通过运算符可以对一个或多个值进行运算,并获取运算结果。
**表达式**:数字、运算符、变量的组合(组成的式子)。
表达式最终都会有一个运算结果,我们将这个结果称为表达式的**返回值**。
比如:`+`、`*`、`/`、`()` 都是**运算符**,而`3+5/2`则是**表达式**。
比如typeof 就是运算符,可以获得一个值的类型。它会将该值的类型以**字符串**的形式返回,返回值可以是 number、string、boolean、undefined、object。
### 运算符的分类
JS 中的运算符,分类如下:
- 算数运算符
- 自增/自减运算符
- 一元运算符
- 三元运算符(条件运算符)
- 逻辑运算符
- 赋值运算符
- 比较运算符
下面来逐一讲解。
## 算数运算符
**算术运算符**:用于执行两个变量或值的算术运算。
常见的算数运算符有以下几种:
| 运算符 | 描述 |
| :----- | :--------------------: |
| + | 加、字符串连接 |
| - | 减 |
| \* | 乘 |
| / | 除 |
| % | 获取余数(取余、取模) |
**求余的举例**
假设用户输入 345怎么分别得到 3、4、5 这三个数呢?
**答案**
```
得到3的方法345 除以100得到3.45然后取整得到3。即parseInt(345/100)
得到4的方法345 除以100余数是45除以10得到4.5取整。即parseInt(345 % 100 / 10)
得到5的方法345 除以10余数就是5。即345 % 10
```
### 算数运算符的运算规则
1`* / %` 的优先级高于 `+ -`
2无论是`+ - * / %`都是左结合性(从左至右计算)
2小括号`( )`:能够影响计算顺序,且可以嵌套。没有中括号、没有大括号,只有小括号。
举例 1取余
```javascript
console.log(3 % 5);
```
输出结果为 3。
举例 2注意运算符的优先级
```javascript
var a = 1 + ((2 * 3) % 4) / 3;
```
结果分析:
原式 = 1 + 6 % 4 / 3 = 1 + 2 / 3 = 1.66666666666666
### 取模(取余)运算
格式:
```js
余数 = m % n;
```
计算结果注意:
- 取余运算结果的正负性,取决于 m而不是 n。比如`10 % -3`的运算结果是 1。`-10 % 3`的运算结果是-1。
- 如果 n < 0那就先把 n 取绝对值后再计算等价于 m % (-n)。
- 如果 n 0那么结果是 NaN
- n > 0 的情况下:
- 如果 m>=n那就正常取余。
- 如果 m<n那结果就是 m
### 浮点数运算的精度问题
浮点数值的最高精度是 17 位小数但在进行算术计算时会丢失精度导致计算不够准确比如
```javascript
console.log(0.1 + 0.2); // 运算结果不是 0.3,而是 0.30000000000000004
console.log(0.07 * 100); // 运算结果不是 7而是 7.000000000000001
```
因此**不要直接判断两个浮点数是否相等**。前面的文章JavaScript 基础基本数据类型Number有详细介绍
### 隐式类型转换——运算符:加号 `+`
1. **字符串 + XX = 字符串**
任何值和字符串做加法运算都会先调用 String() 函数转换为字符串然后再做拼串操作最终的运算结果是字符串
比如
```javascript
result1 = 1 + 2 + '3'; // 字符串33
result2 = '1' + 2 + 3; // 字符串123
```
2. **Boolean + 数字 = 数字**
Boolean 型和数字型相加时 true 1 来算 false 0 来算这里其实是先调 Number() 函数 Boolean 类型转为 Number 类型然后再和 数字相加
3. **null + 数字 = 数字**
等价于0 + 数字
4. **undefined + 数字 = NaN**
计算结果NaN
5. 任何值和 **NaN** 运算的结果都是 NaN
### 隐式类型转换——运算符:`-`、`*`、`/`、`%`
任何非 Number 类型的值做`-`、`*`、`/`、`%`运算时会将这些值转换为 Number 然后再运算(内部调用的是 Number() 函数运算结果是 Number 类型
任何数据和 NaN进行运算结果都是NaN
比如
```js
var result1 = 100 - '1'; // 99
var result2 = true + NaN; // NaN
```
## 自增和自减运算符
### 自增运算符 `++`
作用可以快速对一个变量进行加1操作只能操作变量不能操作常量或者表达式
例如
```js
let a1 = 1;
let a2 = 2;
a1++;
const result = a1++ + a2; // result的结果为4
// (a1+a2)++; // 报错,没有这种写法
const a3 = 3;
a3++; // 报错,因为常量无法再自加
```
自增分成两种`a++``++a`。共同点
1无论是 `a++` 还是`++a`自增都会使原变量的值加 1
2**我们要注意的是**`a`是变量`a++``++a`**表达式**。
那这两种自增有啥区别呢区别是`a++` `++a`的值不同也就是说表达式的值不同
- `a++`这个表达式的值等于原变量的值a 自增前的值)。可以这样理解先把 a 的值赋值给表达式然后 a 再自增
- `++a`这个表达式的值等于新值 a 自增后的值)。 可以这样理解a 先自增然后把自增后的值赋值给表达式
### 自减运算符 `--`
作用可以快速对一个变量进行减1操作原理同自增运算符
开发时大多使用后置的自增/自减并且代码独占一行例如`num++`或者 `num--`
### 代码举例
```javascript
var n1 = 10;
var n2 = 20;
var result1 = n1++;
console.log(n1); // 11
console.log(result1); // 10
result = ++n1;
console.log(n1); //12
console.log(result); //12
var result2 = n2--;
console.log(n2); // 19
console.log(result2); // 20
result2 = --n2;
console.log(n2); // 18
console.log(result2); // 18
```
### 隐式类型转换
自增和自减时a的执行过程
1先调用`Number(a)`函数
2然后将`Number(a)`的返回结果进行 1 操作得到的结果赋值给 a
举例 1
```javascript
let a = '666'; // 这里不能用 const 定义,否则报错。
a++;
console.log(a); // 打印结果667
console.log(typeof a); // 打印结果: number
```
举例2
```javascript
let a = 'abc';
a++;
console.log(a); // 打印结果NaN。因为 Number('abc')的结果为 NaN再自增后结果依然是 NaN
console.log(typeof a); // 打印结果number
```
## 一元运算符
一元运算符只需要一个操作数常见的一元运算符如下
### typeof
> typeof 就是典型的一元运算符,因为后面只跟一个操作数。
`typeof()`表示“**获取变量的数据类型**”,它是 JS 提供的一个操作符返回的是小写语法为两种写法都可以
```javascript
// 写法1
typeof 变量;
// 写法2
typeof(变量);
```
typeof 这个运算符的返回结果就是变量的类型那返回结果的类型是什么呢是字符串
**返回结果**
| typeof 的语法 | 返回结果 |
| :--------------------------- | :-------: |
| typeof 数字 typeof NaN | number |
| typeof 字符串 | string |
| typeof 布尔型 | boolean |
| typeof 对象 | object |
| typeof 方法 | function |
| typeof null | object |
| typeof undefined | undefined |
备注 1为啥 `typeof null`的返回值也是 object 因为 null 代表的是**空对象**。
备注 2`typeof NaN`的返回值是 number上一篇文章中讲过NaN 是一个特殊的数字
**返回结果举例**
```javascript
var a = '123';
console.log(typeof a); // 打印结果string
console.log(typeof []); // 空数组的打印结果object
console.log(typeof {}); // 空对象的打印结果object
```
代码解释这里的空数组`[]`、空对象`{}` 为啥他们在使用 typeof 返回值也是 `object`因为空数组空对象都是**引用数据类型 Object**。
typeof 无法区分数组 instanceof 可以比如
```js
console.log([] instanceof Array); // 打印结果true
console.log({} instanceof Array); // 打印结果false
```
关于 instanceof 的详细内容以后讲对象的时候会详细介绍
### 正号/负号:`+a`、`-a`
> 注意,这里说的是正号/负号,不是加号/减号。
1不会改变原数值
1正号不会对数字产生任何影响比如说`2``+2`是一样的
2我们可以对其他的数据类型使用`+`来将其转换为 number重要的小技巧】。比如
```javascript
var a = true;
a = +a; // 注意这行代码的一元运算符操作
console.log('a' + a);
console.log(typeof a);
console.log('-----------------');
var b = '18';
b = +b; // 注意这行代码的一元运算符操作
console.log('b' + b);
console.log(typeof b);
```
打印结果
```
a1
number
-----------------
b18
number
```
3负号可以对数字进行取反
### 隐式类型转换——正号/负号
任何值做`+a`、`-a`运算时 内部调用的是 Number() 函数
**举例**
```javascript
const a = '666';
const b = +a; // 对 a 进行一元运算b是运算结果
console.log(typeof a); // 打印结果string。说明 a 的数据类型保持不变。
console.log(a); // 打印结果:"666"。不会改变原数值。
console.log(typeof b); // 打印结果number。说明 b 的数据类型发生了变化。
console.log(b); // 打印结果666
```
## 三目运算符
三目运算符也叫三元运算符条件运算符
语法
```
条件表达式 ? 语句1 : 语句2;
```
**执行流程**——条件运算符在执行时首先对条件表达式进行求值
- 如果该值为 true则执行语句 1并返回执行结果
- 如果该值为 false则执行语句 2并返回执行结果
如果条件表达式的求值结果是一个非布尔值会将其转换为布尔值然后再运算
## 逻辑运算符
逻辑运算符有三个
- `&&` )。两个都为真结果才为真特点一假则假
- `||` 只要有一个是真结果就是真特点特点: 一真则真
- `!` 对一个布尔值进行取反特点: 真变假, 假变真
注意能参与逻辑运算的都是布尔值
**连比的写法:**
来看看逻辑运算符连比的写法
举例 1
```javascript
console.log(3 < 2 && 2 < 4);
```
输出结果为 false
举例 2判断一个人的年龄是否在 18~65 岁之间
```javascript
const a = prompt('请输入您的年龄');
if (a >= 18 && a < 65) {
alert('可以上班');
} else {
alert('准备退休');
}
```
PS上面的`a>=18 && a<= 65`千万别想当然地写成` 18<= a <= 65`,没有这种语法。
### 非布尔值的与或运算【重要】
> 之所以重要,是因为在实际开发中,我们经常用这种代码做容错处理或者兜底处理。
非布尔值进行**与或运算**时,会先将其转换为布尔值,然后再运算,但返回结果是**原值**。比如说:
```javascript
var result = 5 && 6; // 运算过程true && true;
console.log('result' + result); // 打印结果6也就是最后面的那个值
```
上方代码可以看到,虽然运算过程为布尔值的运算,但返回结果是原值。
那么,返回结果是哪个原值呢?我们来看一下。
1、两个非布尔值做逻辑运算
**与运算**的返回结果:
- 如果第一个值为 false则只执行第一条语句并直接返回第一个值不会再往后执行。
- 如果第一个值为 true则继续执行第二条语句并返回第二个值无论第二个值的结果如何
**或运算**的返回结果:
- 如果第一个值为 true则只执行第一条语句并直接返回第一个值不会再往后执行。
- 如果第一个值为 false则继续执行第二条语句并返回第二个值无论第二个值的结果如何
2、三个及以上的非布尔值做逻辑运算
**与运算**的返回结果value1 && value2 && value3
- 从左到右依次计算操作数,找到第一个为 false 的值为止。
- 如果所有的值都为 true则返回最后一个值。
**或运算**的返回结果value1 || value2 || value3
- 从左到右依次计算操作数,找到第一个为 true 的值为止。
- 如果所有的值都为 false则返回最后一个值。
### 非布尔值的 `!` 运算
非布尔值进行**非运算**时,会先将其转换为布尔值,然后再运算,返回结果是**布尔值**。
举例:
```javascript
let a = 10;
a = !a;
console.log(a); // false
console.log(typeof a); // boolean
```
### 短路运算的妙用【重要】
> 下方举例中的写法技巧,在实际开发中,经常用到。这种写法,是一种很好的「容错、容灾、降级」方案,需要多看几遍。
1、JS 中的`&&`属于**短路**的与:
- 如果第一个值为 false则不会执行后面的内容。
- 如果第一个值为 true则继续执行第二条语句并返回第二个值。
举例:
```javascript
const a1 = 'qianguyihao';
// 第一个值为true会继续执行后面的内容
a1 && alert('看 a1 出不出来'); // 可以弹出 alert 框
const a2 = undefined;
// 第一个值为false不会继续执行后面的内容
a2 && alert('看 a2 出不出来'); // 不会弹出 alert 框
```
2、JS 中的`||`属于**短路**的或:
- 如果第一个值为 true则不会执行后面的内容。
- 如果第一个值为 false则继续执行第二条语句并返回第二个值。
实际开发中,我们经常是这样来做「容错处理」的,如下。
举例1
```js
const result; // 请求接口时,后台返回的内容
let errorMsg = ''; // 前端的文案提示
if (result & result.retCode == 0) {
errorMsg = '恭喜你中奖啦~'
}
if (result && result.retCode != 0) {
// 接口返回异常码时
errorMsg = result.msg || '活动太火爆,请稍后再试'; // 文案提示信息,优先用 接口返回的msg字段其次用 '活动太火爆,请稍后再试' 这个文案兜底。
}
if (!result) {
// 接口挂掉时
errorMsg = '网络异常,请稍后再试';
}
```
举例2当前端成功调用一个接口后返回的数据为 result 对象。这个时候,我们用变量 a 来接收 result 里的图片资源:
```javascript
if (result.retCode == 0) {
var a = result && result.data && result.data.imgUrl || 'http://img.smyhvae.com/20160401_01.jpg';
}
```
上方代码的意思是,获取返回结果中的`result.data.imgUrl`这个图片资源;如果返回结果中没有 `result.data.imgUrl` 这个字段,就用 `http://img.smyhvae.com/20160401_01.jpg` 作为**兜底**图片。这种写法,在实际开发中经常用到。
## 赋值运算符
赋值:将等号右侧的值赋给符号左侧的变量。
### 赋值运算符包括哪些
- `=` 直接赋值。比如 `var a = 5`。意思是,把 5 这个值,往 a 里面存一份。简称:把 5 赋值给 a。
- `+=`:比如 a += 5 等价于 a = a + 5。
- `-=`:比如 a -= 5 等价于 a = a - 5。
- `*=`:比如 a _ = 5 等价于 a = a -5。
- `/=`:比如 a /= 5 等价于 a = a / 5。
- `%=`:比如 a %= 5 等价于 a = a % 5。
### 注意事项
1算数运算符的优先级高于赋值运算符。举例
```js
const result = 1 + 2; // 先计算 1 + 2再把计算结果赋值给 result。因为算数运算符的优先级高于赋值运算符。
```
2赋值运算符的结合性是右结合性从右至左的顺序计算。举例
```js
const a1, a2;
a1 = a2 = 3; // 先将 3 复制给 a2再将 a2 的值赋值给 a1
```
## 比较运算符
比较运算符可以比较两个值之间的大小关系,如果关系成立它会返回 true如果关系不成立则返回 false。
比较运算符有很多种,比如:
```
> 大于号
< 小于号
>= 大于或等于
<= 小于或等于
== 等于
=== 全等于
!= 不等于
!== 不全等于
```
**比较运算符,得到的结果都是布尔值:要么是 true要么是 false**。如果关系成立就返回true如果关系不成立就返回false。
举例如下:
```javascript
const result = 5 > 10; // false
```
### 非数值的比较
1对于非数值进行比较时会将其转换为数值类型内部是调用`Number()方法`),再进行比较。
举例如下:
```javascript
console.log(1 > true); //false
console.log(1 >= true); //true
console.log(1 > '0'); //true
//console.log(10 > null); //true
//任何值和NaN做任何比较都是false
console.log(10 <= 'hello'); //false
console.log(true > false); //true
```
2特殊情况如果参与比较的都是字符串则**不会**将其转换为数字进行比较,比较的是字符串的**Unicode 编码**。【非常重要,这里是个大坑,很容易踩到】
比较字符编码时,是一位一位进行比较,顺序从左到右。如果大一样,则继续比较下一位。
比如说,当你尝试去比较`"123"`和`"56"`这两个字符串时,你会发现,字符串"56"竟然比字符串"123"要大(因为 5 比 1 大)。也就是说,下面这样代码的打印结果,其实是 true:(这个我们一定要注意,在日常开发中,很容易忽视)
```javascript
// 比较两个字符串时,比较的是字符串的字符编码,所以可能会得到不可预期的结果
console.log('56' > '123'); // true
```
**因此**:当我们想比较两个字符串型的数字时,**一定一定要先转型**再比较大小,比如 `parseInt()`
3任何值和 NaN 做任何比较都是 false。
### `==`符号的强调
`==`这个符号,它是**判断是否等于**,而不是赋值。注意事项如下:
1`== `这个符号,还可以验证字符串是否相同。例如:
```javascript
console.log('我爱你中国' == '我爱你中国'); // 输出结果为true
```
2`== `这个符号并不严谨,会做隐式转换,将不同的数据类型,**转为相同类型**进行比较。例如:
```javascript
console.log('6' == 6); // 打印结果true。这里的字符串"6"会先转换为数字6然后再进行比较
console.log(true == '1'); // 打印结果true
console.log(0 == -0); // 打印结果true
console.log(null == 0); // 打印结果false
```
3undefined 衍生自 null所以这两个值做相等判断时会返回 true。
```javascript
console.log(undefined == null); //打印结果true。
```
4NaN 不和任何值相等,包括它本身。
```javascript
console.log(NaN == NaN); //false
console.log(NaN === NaN); //false
```
问题:那如果我想判断 b 的值是否为 NaN该怎么办呢
答案:可以通过 isNaN()函数来判断一个值是否是 NaN。举例
```javascript
console.log(isNaN(b));
```
如上方代码所示,如果 b 为 NaN则返回 true否则返回 false。
### `===`全等符号的强调
**全等在比较时,不会做类型转换**。如果要保证**完全等于**(即:不仅要判断取值相等,还要判断数据类型相同),我们就要用三个等号`===`。例如:
```javascript
console.log('6' === 6); //false
console.log(6 === 6); //true
```
上述内容分析出:
- `==`两个等号,不严谨,"6"和 6 是 true。
- `===`三个等号,严谨,"6"和 6 是 false。
另外还有:**`==`的反面是`!=``===`的反面是`!==`**。例如:
```javascript
console.log(3 != 8); // true
console.log(3 != '3'); // false因为3=="3"是true所以反过来就是false。
console.log(3 !== '3'); // true应为3==="3"是false所以反过来是true。
```
## 不同数据类型之间的大小比较
这一段是比较运算符的延伸,内容繁琐,新手可以不用记,等以后用到的时候再查阅。
### 数值类型和其他类型比较
先将其他类型隐式转换为数值类型(内部是调用`Number()`方法),然后比较大小。代码举例:
```js
//字符串与数字比较
console.log('200' > 100); // true
console.log('a' > 100); // false。 'a' 被转换成 NaN 进行比较
console.log('110a' > 100); // false。 '110a' 被转换成 NaN 进行比较。说明`110a`在做隐式转换的时候,是调用了 Number('110a')方法,而不是调用 parseInt('110a')方法
// 布尔值与数字比较
console.log(true == 1); // true
console.log(false == 0); // true
// null 与数字进行比较
console.log(null < 0); // false
console.log(null == 0); // false
console.log(null > 0); // false
console.log(null <= 0); // true。这是一个很严重的bug
console.log(null >= 0); // true。同上
// undefined 与数字进行比较:结果都是 false
console.log(undefined > 0);
console.log(undefined == 0);
console.log(undefined < 0);
console.log(undefined >= 0);
```
### 日期大小比较
如果日期的格式为字符串,则比较字符串的**Unicode 编码**。代码举例:
```js
const myDate1 = new Date(2022, 8, 8);
const myDate2 = new Date(2022, 8, 9);
const myDate3 = new Date(2022, 9, 8);
const myDate4 = new Date(2023, 8, 8);
console.log(myDate1 < myDate2); // true
console.log(myDate1 < myDate3); // true
console.log(myDate3 < myDate4); // true
const date1 = '2022-08-08'; // "2022/08/08"同理
const date2 = '2022-08-09'; // "2022/08/09"同理
const date3 = '2022-09-08'; // "2022/09/08"同理
const date4 = '2023-08-08'; // "2023/08/08"同理
console.log(date1 < date2); // true
console.log(date1 < date3); // true
console.log(date3 < date4); // true
const time1 = '2022-08-08 08:00:00';
const time2 = '2022-08-08 08:00:01';
const time3 = '2022-08-08 08:01:00';
const time4 = '2022-08-08 09:00:00';
console.log(time1 < time2); // true
console.log(time1 < time3); // true
console.log(time1 < time4); // true
// 数据类型不同,此处是先将 myDate1 转为字符串类型,然后比较大小。可想而知,结果都是 false
console.log(myDate1 >= date1); // false
console.log(myDate1 <= date1); // false
// 虽然时间格式不同,但都是字符串,所以可以比较大小
console.log(date1 < time1); // true
```
参考链接:
- [【JavaScript】探究数据类型之间的隐式转换和大小比较](https://blog.csdn.net/w390058785/article/details/79957206)
## 逗号运算符
逗号运算符一般用于简化代码。逗号运算符的优先级是所有运算符中最低的。
逗号运算符也是一个运算符, 所以也有运算符结果。它的运算符结果是最后一个表达式的结果。
代码举例:
```js
// 利用逗号运算符同时定义多个变量
let a, b;
// 利用逗号运算符同时给多个变量赋值
a = 10, b = 5;
const res1 = (1 + 2, 3 + 4, 5 + 6); // 打印结果11
```
## 运算符的优先级
运算符的优先级如下:(优先级从高到低)
- `.`、`[]`、`new`
- `()`
- `++`、`--`
- `!`、`~`、`+`(单目)、`-`(单目)、`typeof`、`void`、`delete`
- `*`、`/`、`%`
- `+`(双目)、`-`(双目)
- `<<`、`>>`、`>>>`
- 比较运算符:`<`、`<=`、`>`、`>=`
- 比较运算符:`==`、`!==`、`===`、`!==`
- `&`
- `^`
- `|`
- 逻辑运算符:`&&` (注意:逻辑与 `&&` 比逻辑或 `||` 的优先级更高)
- 逻辑运算符:`||`
- `?:`
- `=`、`+=`、`-=`、`*=`、`/=`、`%=`、`<<=`、`>>=`、`>>>=`、`&=`、`^=`、`|=`
- `,`
备注:在实际写代码的时候,如果你不清楚哪个优先级更高,可以先尝试把括号用上。
## Unicode 编码
> 这一段中我们来讲引申的内容Unicode 编码的使用。
各位同学可以先在网上查一下“Unicode 编码表”。
1、在字符串中可以使用转义字符输入 Unicode 编码。格式如下:
```
\u四位编码
```
举例如下:
```javascript
console.log('\u2600'); // 这里的 2600 采用的是16进制
console.log('\u2602'); // 这里的 2602 采用的是16进制。
```
打印结果:
![](http://img.smyhvae.com/20181222_1218.png)
2、我们还可以在 HTML 网页中使用 Unicode 编码。格式如下:
```
&#四位编码;
```
PS我们知道Unicode 编码采用的是 16 进制,但是,这里的编码需要使用 10 进制。
举例如下:
```html
<h1 style="font-size: 100px;">&#9860;</h1>
```
打印结果:
![](http://img.smyhvae.com/20181222_1226.png)
## 赞赏作者
创作不易,你的赞赏和认可,是我更新的最大动力:
![](https://img.smyhvae.com/20220401_1800.jpg)