1371 lines
34 KiB
Markdown
1371 lines
34 KiB
Markdown
# 运算符
|
||
|
||
|
||
|
||
## 经典真题
|
||
|
||
|
||
|
||
- 下面代码中,*a* 在什么情况下会执行输出语句打印 *1* ?
|
||
|
||
```js
|
||
var a = ?;
|
||
if(a == 1 && a == 2 && a == 3){
|
||
console.log(1);
|
||
}
|
||
```
|
||
|
||
|
||
|
||
## 1. 算术运算符
|
||
|
||
|
||
|
||
*JavaScript* 共提供 *10* 个算术运算符,用来完成基本的算术运算。
|
||
|
||
- **加法运算符**:`x + y`
|
||
- **减法运算符**: `x - y`
|
||
- **乘法运算符**: `x * y`
|
||
- **除法运算符**:`x / y`
|
||
- **指数运算符**:`x ** y`
|
||
- **余数运算符**:`x % y`
|
||
- **自增运算符**:`++x` 或者 `x++`
|
||
- **自减运算符**:`--x` 或者 `x--`
|
||
- **数值运算符**: `+x`
|
||
- **负数值运算符**:`-x`
|
||
|
||
减法、乘法、除法运算法比较单纯,就是执行相应的数学运算。
|
||
|
||
下面介绍其他几个算术运算符,重点是加法运算符。
|
||
|
||
|
||
|
||
#### 加法运算符
|
||
|
||
**(1)基本规则**
|
||
|
||
加法运算符(`+`)是最常见的运算符,用来求两个数值的和。
|
||
|
||
```js
|
||
1 + 1 // 2
|
||
```
|
||
|
||
JavaScript 允许非数值的相加。
|
||
|
||
```js
|
||
true + true // 2
|
||
1 + true // 2
|
||
```
|
||
|
||
上面代码中,第一行是两个布尔值相加,第二行是数值与布尔值相加。这两种情况,布尔值都会自动转成数值,然后再相加。
|
||
|
||
比较特殊的是,如果是两个字符串相加,这时加法运算符会变成连接运算符,返回一个新的字符串,将两个原字符串连接在一起。
|
||
|
||
```js
|
||
'a' + 'bc' // "abc"
|
||
```
|
||
|
||
如果一个运算子是字符串,另一个运算子是非字符串,这时非字符串会转成字符串,再连接在一起。
|
||
|
||
```js
|
||
1 + 'a' // "1a"
|
||
false + 'a' // "falsea"
|
||
```
|
||
|
||
加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)。由于加法运算符存在重载,可能执行两种运算,使用的时候必须很小心。
|
||
|
||
```js
|
||
'3' + 4 + 5 // "345"
|
||
3 + 4 + '5' // "75"
|
||
```
|
||
|
||
上面代码中,由于从左到右的运算次序,字符串的位置不同会导致不同的结果。
|
||
|
||
除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。
|
||
|
||
```js
|
||
1 - '2' // -1
|
||
1 * '2' // 2
|
||
1 / '2' // 0.5
|
||
```
|
||
|
||
上面代码中,减法、除法和乘法运算符,都是将字符串自动转为数值,然后再运算。
|
||
|
||
|
||
|
||
**(2)对象相加**
|
||
|
||
如果运算子是对象,必须先转成原始类型的值,然后再相加。
|
||
|
||
```javascript
|
||
var obj = { p: 1 };
|
||
obj + 2 // "[object Object]2"
|
||
```
|
||
|
||
上面代码中,对象`obj`转成原始类型的值是`[object Object]`,再加`2`就得到了上面的结果。
|
||
|
||
对象转成原始类型的值,规则如下。
|
||
|
||
首先,自动调用对象的`valueOf`方法。
|
||
|
||
```javascript
|
||
var obj = { p: 1 };
|
||
obj.valueOf() // { p: 1 }
|
||
```
|
||
|
||
一般来说,对象的`valueOf`方法总是返回对象自身,这时再自动调用对象的`toString`方法,将其转为字符串。
|
||
|
||
```javascript
|
||
var obj = { p: 1 };
|
||
obj.valueOf().toString() // "[object Object]"
|
||
```
|
||
|
||
对象的`toString`方法默认返回`[object Object]`,所以就得到了最前面那个例子的结果。
|
||
|
||
知道了这个规则以后,就可以自己定义`valueOf`方法或`toString`方法,得到想要的结果。
|
||
|
||
```javascript
|
||
var obj = {
|
||
valueOf: function () {
|
||
return 1;
|
||
}
|
||
};
|
||
|
||
obj + 2 // 3
|
||
```
|
||
|
||
上面代码中,我们定义`obj`对象的`valueOf`方法返回`1`,于是`obj + 2`就得到了`3`。这个例子中,由于`valueOf`方法直接返回一个原始类型的值,所以不再调用`toString`方法。
|
||
|
||
下面是自定义`toString`方法的例子。
|
||
|
||
```javascript
|
||
var obj = {
|
||
toString: function () {
|
||
return 'hello';
|
||
}
|
||
};
|
||
|
||
obj + 2 // "hello2"
|
||
```
|
||
|
||
上面代码中,对象`obj`的`toString`方法返回字符串`hello`。前面说过,只要有一个运算子是字符串,加法运算符就变成连接运算符,返回连接后的字符串。
|
||
|
||
这里有一个特例,如果运算子是一个`Date`对象的实例,那么会优先执行`toString`方法。
|
||
|
||
```javascript
|
||
var obj = new Date();
|
||
obj.valueOf = function () { return 1 };
|
||
obj.toString = function () { return 'hello' };
|
||
|
||
obj + 2 // "hello2"
|
||
```
|
||
|
||
上面代码中,对象`obj`是一个`Date`对象的实例,并且自定义了`valueOf`方法和`toString`方法,结果`toString`方法优先执行。
|
||
|
||
|
||
|
||
#### 余数运算符
|
||
|
||
余数运算符(`%`)返回前一个运算子被后一个运算子除,所得的余数。
|
||
|
||
```javascript
|
||
12 % 5 // 2
|
||
```
|
||
|
||
需要注意的是,运算结果的正负号由第一个运算子的正负号决定。
|
||
|
||
```javascript
|
||
-1 % 2 // -1
|
||
1 % -2 // 1
|
||
```
|
||
|
||
所以,为了得到负数的正确余数值,可以先使用绝对值函数。
|
||
|
||
```javascript
|
||
// 错误的写法
|
||
function isOdd(n) {
|
||
return n % 2 === 1;
|
||
}
|
||
isOdd(-5) // false
|
||
isOdd(-4) // false
|
||
|
||
// 正确的写法
|
||
function isOdd(n) {
|
||
return Math.abs(n % 2) === 1;
|
||
}
|
||
isOdd(-5) // true
|
||
isOdd(-4) // false
|
||
```
|
||
|
||
余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。
|
||
|
||
```javascript
|
||
6.5 % 2.1
|
||
// 0.19999999999999973
|
||
```
|
||
|
||
|
||
|
||
#### 自增和自减运算符
|
||
|
||
自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量。
|
||
|
||
```javascript
|
||
var x = 1;
|
||
++x // 2
|
||
x // 2
|
||
|
||
--x // 1
|
||
x // 1
|
||
```
|
||
|
||
上面代码的变量`x`自增后,返回`2`,再进行自减,返回`1`。这两种情况都会使得,原始变量`x`的值发生改变。
|
||
|
||
运算之后,变量的值发生变化,这种效应叫做运算的副作用(side effect)。自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值。
|
||
|
||
自增和自减运算符有一个需要注意的地方,就是放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。
|
||
|
||
```javascript
|
||
var x = 1;
|
||
var y = 1;
|
||
|
||
x++ // 1
|
||
++y // 2
|
||
```
|
||
|
||
上面代码中,`x`是先返回当前值,然后自增,所以得到`1`;`y`是先自增,然后返回新的值,所以得到`2`。
|
||
|
||
|
||
|
||
#### 数值运算符,负数值运算符
|
||
|
||
数值运算符(`+`)同样使用加号,但它是一元运算符(只需要一个操作数),而加法运算符是二元运算符(需要两个操作数)。
|
||
|
||
数值运算符的作用在于可以将任何值转为数值(与`Number`函数的作用相同)。
|
||
|
||
```javascript
|
||
+true // 1
|
||
+[] // 0
|
||
+{} // NaN
|
||
```
|
||
|
||
上面代码表示,非数值经过数值运算符以后,都变成了数值(最后一行`NaN`也是数值)。具体的类型转换规则,参见《数据类型转换》一章。
|
||
|
||
负数值运算符(`-`),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符。
|
||
|
||
```javascript
|
||
var x = 1;
|
||
-x // -1
|
||
-(-x) // 1
|
||
```
|
||
|
||
上面代码最后一行的圆括号不可少,否则会变成自减运算符。
|
||
|
||
数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值。
|
||
|
||
|
||
|
||
#### 指数运算符
|
||
|
||
指数运算符(`**`)完成指数运算,前一个运算子是底数,后一个运算子是指数。
|
||
|
||
```javascript
|
||
2 ** 4 // 16
|
||
```
|
||
|
||
注意,指数运算符是右结合,而不是左结合。即多个指数运算符连用时,先进行最右边的计算。
|
||
|
||
```javascript
|
||
// 相当于 2 ** (3 ** 2)
|
||
2 ** 3 ** 2
|
||
// 512
|
||
```
|
||
|
||
上面代码中,由于指数运算符是右结合,所以先计算第二个指数运算符,而不是第一个。
|
||
|
||
|
||
|
||
#### 赋值运算符
|
||
|
||
赋值运算符(Assignment Operators)用于给变量赋值。
|
||
|
||
最常见的赋值运算符,当然就是等号(`=`)。
|
||
|
||
```javascript
|
||
// 将 1 赋值给变量 x
|
||
var x = 1;
|
||
|
||
// 将变量 y 的值赋值给变量 x
|
||
var x = y;
|
||
```
|
||
|
||
赋值运算符还可以与其他运算符结合,形成变体。下面是与算术运算符的结合。
|
||
|
||
```javascript
|
||
// 等同于 x = x + y
|
||
x += y
|
||
|
||
// 等同于 x = x - y
|
||
x -= y
|
||
|
||
// 等同于 x = x * y
|
||
x *= y
|
||
|
||
// 等同于 x = x / y
|
||
x /= y
|
||
|
||
// 等同于 x = x % y
|
||
x %= y
|
||
|
||
// 等同于 x = x ** y
|
||
x **= y
|
||
```
|
||
|
||
下面是与位运算符的结合(关于位运算符,请见后文的介绍)。
|
||
|
||
```javascript
|
||
// 等同于 x = x >> y
|
||
x >>= y
|
||
|
||
// 等同于 x = x << y
|
||
x <<= y
|
||
|
||
// 等同于 x = x >>> y
|
||
x >>>= y
|
||
|
||
// 等同于 x = x & y
|
||
x &= y
|
||
|
||
// 等同于 x = x | y
|
||
x |= y
|
||
|
||
// 等同于 x = x ^ y
|
||
x ^= y
|
||
```
|
||
|
||
这些复合的赋值运算符,都是先进行指定运算,然后将得到值返回给左边的变量。
|
||
|
||
|
||
|
||
## 2. 比较运算符
|
||
|
||
|
||
|
||
比较运算符用于比较两个值的大小,然后返回一个布尔值,表示是否满足指定的条件。
|
||
|
||
```
|
||
2 > 1 // true
|
||
```
|
||
|
||
上面代码比较`2`是否大于`1`,返回`true`。
|
||
|
||
> 注意,比较运算符可以比较各种类型的值,不仅仅是数值。
|
||
|
||
JavaScript 一共提供了8个比较运算符。
|
||
|
||
- `>` 大于运算符
|
||
- `<` 小于运算符
|
||
- `<=` 小于或等于运算符
|
||
- `>=` 大于或等于运算符
|
||
- `==` 相等运算符
|
||
- `===` 严格相等运算符
|
||
- `!=` 不相等运算符
|
||
- `!==` 严格不相等运算符
|
||
|
||
这八个比较运算符分成两类:相等比较和非相等比较。两者的规则是不一样的,对于非相等的比较,算法是先看两个运算子是否都是字符串,如果是的,就按照字典顺序比较(实际上是比较 Unicode 码点);否则,将两个运算子都转成数值,再比较数值的大小。
|
||
|
||
|
||
|
||
#### 非相等运算符:字符串的比较
|
||
|
||
字符串按照字典顺序进行比较。
|
||
|
||
```
|
||
'cat' > 'dog' // false
|
||
'cat' > 'catalog' // false
|
||
```
|
||
|
||
JavaScript 引擎内部首先比较首字符的 Unicode 码点。如果相等,再比较第二个字符的 Unicode 码点,以此类推。
|
||
|
||
```
|
||
'cat' > 'Cat' // true'
|
||
```
|
||
|
||
上面代码中,小写的`c`的 Unicode 码点(`99`)大于大写的`C`的 Unicode 码点(`67`),所以返回`true`。
|
||
|
||
由于所有字符都有 Unicode 码点,因此汉字也可以比较。
|
||
|
||
```
|
||
'大' > '小' // false
|
||
```
|
||
|
||
上面代码中,“大”的 Unicode 码点是22823,“小”是23567,因此返回`false`。
|
||
|
||
|
||
|
||
#### 非相等运算符:非字符串的比较
|
||
|
||
如果两个运算子之中,至少有一个不是字符串,需要分成以下两种情况。
|
||
|
||
**(1)原始类型值**
|
||
|
||
如果两个运算子都是原始类型的值,则是先转成数值再比较。
|
||
|
||
```javascript
|
||
5 > '4' // true
|
||
// 等同于 5 > Number('4')
|
||
// 即 5 > 4
|
||
|
||
true > false // true
|
||
// 等同于 Number(true) > Number(false)
|
||
// 即 1 > 0
|
||
|
||
2 > true // true
|
||
// 等同于 2 > Number(true)
|
||
// 即 2 > 1
|
||
```
|
||
|
||
上面代码中,字符串和布尔值都会先转成数值,再进行比较。
|
||
|
||
这里需要注意与`NaN`的比较。任何值(包括`NaN`本身)与`NaN`使用非相等运算符进行比较,返回的都是`false`。
|
||
|
||
```javascript
|
||
1 > NaN // false
|
||
1 <= NaN // false
|
||
'1' > NaN // false
|
||
'1' <= NaN // false
|
||
NaN > NaN // false
|
||
NaN <= NaN // false
|
||
```
|
||
|
||
|
||
|
||
**(2)对象**
|
||
|
||
如果运算子是对象,会转为原始类型的值,再进行比较。
|
||
|
||
对象转换成原始类型的值,算法是先调用`valueOf`方法;如果返回的还是对象,再接着调用`toString`方法,详细解释参见《数据类型的转换》一章。
|
||
|
||
```javascript
|
||
var x = [2];
|
||
x > '11' // true
|
||
// 等同于 [2].valueOf().toString() > '11'
|
||
// 即 '2' > '11'
|
||
|
||
x.valueOf = function () { return '1' };
|
||
x > '11' // false
|
||
// 等同于 [2].valueOf() > '11'
|
||
// 即 '1' > '11'
|
||
```
|
||
|
||
两个对象之间的比较也是如此。
|
||
|
||
```javascript
|
||
[2] > [1] // true
|
||
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
|
||
// 即 '2' > '1'
|
||
|
||
[2] > [11] // true
|
||
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
|
||
// 即 '2' > '11'
|
||
|
||
{ x: 2 } >= { x: 1 } // true
|
||
// 等同于 { x: 2 }.valueOf().toString() >= { x: 1 }.valueOf().toString()
|
||
// 即 '[object Object]' >= '[object Object]'
|
||
```
|
||
|
||
|
||
|
||
#### 严格相等运算符
|
||
|
||
*JavaScript* 提供两种相等运算符:`==`和`===`。
|
||
|
||
简单说,它们的区别是相等运算符(`==`)比较两个值是否相等,严格相等运算符(`===`)比较它们是否为“同一个值”。如果两个值不是同一类型,严格相等运算符(`===`)直接返回`false`,而相等运算符(`==`)会将它们转换成同一个类型,再用严格相等运算符进行比较。
|
||
|
||
|
||
|
||
**(1)不同类型的值**
|
||
|
||
如果两个值的类型不同,直接返回`false`。
|
||
|
||
```js
|
||
1 === "1" // false
|
||
true === "true" // false
|
||
```
|
||
|
||
上面代码比较数值的`1`与字符串的“1”、布尔值的`true`与字符串`"true"`,因为类型不同,结果都是`false`。
|
||
|
||
|
||
|
||
**(2)同一类的原始类型值**
|
||
|
||
同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回`true`,值不同就返回`false`。
|
||
|
||
```js
|
||
1 === 0x1 // true
|
||
```
|
||
|
||
上面代码比较十进制的`1`与十六进制的`1`,因为类型和值都相同,返回`true`。
|
||
|
||
需要注意的是,`NaN`与任何值都不相等(包括自身)。另外,正`0`等于负`0`。
|
||
|
||
```js
|
||
NaN === NaN // false
|
||
+0 === -0 // true
|
||
```
|
||
|
||
|
||
|
||
**(3)复合类型值**
|
||
|
||
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。
|
||
|
||
```js
|
||
{} === {} // false
|
||
[] === [] // false
|
||
(function () {} === function () {}) // false
|
||
```
|
||
|
||
上面代码分别比较两个空对象、两个空数组、两个空函数,结果都是不相等。原因是对于复合类型的值,严格相等运算比较的是,它们是否引用同一个内存地址,而运算符两边的空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是`false`。
|
||
|
||
如果两个变量引用同一个对象,则它们相等。
|
||
|
||
```js
|
||
var v1 = {};
|
||
var v2 = v1;
|
||
v1 === v2 // true
|
||
```
|
||
|
||
注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。
|
||
|
||
```js
|
||
var obj1 = {};
|
||
var obj2 = {};
|
||
|
||
obj1 > obj2 // false
|
||
obj1 < obj2 // false
|
||
obj1 === obj2 // false
|
||
```
|
||
|
||
上面的三个比较,前两个比较的是值,最后一个比较的是地址,所以都返回`false`。
|
||
|
||
|
||
|
||
**(4)undefined 和 null**
|
||
|
||
`undefined`和`null`与自身严格相等。
|
||
|
||
```js
|
||
undefined === undefined // true
|
||
null === null // true
|
||
```
|
||
|
||
由于变量声明后默认值是`undefined`,因此两个只声明未赋值的变量是相等的。
|
||
|
||
```js
|
||
var v1;
|
||
var v2;
|
||
v1 === v2 // true
|
||
```
|
||
|
||
|
||
|
||
#### 严格不相等运算符
|
||
|
||
严格相等运算符有一个对应的“严格不相等运算符”(`!==`),它的算法就是先求严格相等运算符的结果,然后返回相反值。
|
||
|
||
```js
|
||
1 !== '1' // true
|
||
// 等同于
|
||
!(1 === '1')
|
||
```
|
||
|
||
上面代码中,感叹号`!`是求出后面表达式的相反值。
|
||
|
||
|
||
|
||
#### 相等运算符
|
||
|
||
相等运算符用来比较相同类型的数据时,与严格相等运算符完全一样。
|
||
|
||
```js
|
||
1 == 1.0
|
||
// 等同于
|
||
1 === 1.0
|
||
```
|
||
|
||
比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。下面分成几种情况,讨论不同类型的值互相比较的规则。
|
||
|
||
|
||
|
||
**(1)原始类型值**
|
||
|
||
原始类型的值会转换成数值再进行比较。
|
||
|
||
```js
|
||
1 == true // true
|
||
// 等同于 1 === Number(true)
|
||
|
||
0 == false // true
|
||
// 等同于 0 === Number(false)
|
||
|
||
2 == true // false
|
||
// 等同于 2 === Number(true)
|
||
|
||
2 == false // false
|
||
// 等同于 2 === Number(false)
|
||
|
||
'true' == true // false
|
||
// 等同于 Number('true') === Number(true)
|
||
// 等同于 NaN === 1
|
||
|
||
'' == 0 // true
|
||
// 等同于 Number('') === 0
|
||
// 等同于 0 === 0
|
||
|
||
'' == false // true
|
||
// 等同于 Number('') === Number(false)
|
||
// 等同于 0 === 0
|
||
|
||
'1' == true // true
|
||
// 等同于 Number('1') === Number(true)
|
||
// 等同于 1 === 1
|
||
|
||
'\n 123 \t' == 123 // true
|
||
// 因为字符串转为数字时,省略前置和后置的空格
|
||
```
|
||
|
||
上面代码将字符串和布尔值都转为数值,然后再进行比较。
|
||
|
||
|
||
|
||
**(2)对象与原始类型值比较**
|
||
|
||
对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较。
|
||
|
||
具体来说,先调用对象的`valueOf()`方法,如果得到原始类型的值,就按照上一小节的规则,互相比较;如果得到的还是对象,则再调用`toString()`方法,得到字符串形式,再进行比较。
|
||
|
||
下面是数组与原始类型值比较的例子。
|
||
|
||
```
|
||
// 数组与数值的比较
|
||
[1] == 1 // true
|
||
|
||
// 数组与字符串的比较
|
||
[1] == '1' // true
|
||
[1, 2] == '1,2' // true
|
||
|
||
// 对象与布尔值的比较
|
||
[1] == true // true
|
||
[2] == true // false
|
||
```
|
||
|
||
上面例子中,JavaScript 引擎会先对数组`[1]`调用数组的`valueOf()`方法,由于返回的还是一个数组,所以会接着调用数组的`toString()`方法,得到字符串形式,再按照上一小节的规则进行比较。
|
||
|
||
下面是一个更直接的例子。
|
||
|
||
```js
|
||
const obj = {
|
||
valueOf: function () {
|
||
console.log('执行 valueOf()');
|
||
return obj;
|
||
},
|
||
toString: function () {
|
||
console.log('执行 toString()');
|
||
return 'foo';
|
||
}
|
||
};
|
||
|
||
obj == 'foo'
|
||
// 执行 valueOf()
|
||
// 执行 toString()
|
||
// true
|
||
```
|
||
|
||
上面例子中,`obj`是一个自定义了`valueOf()`和`toString()`方法的对象。这个对象与字符串`'foo'`进行比较时,会依次调用`valueOf()`和`toString()`方法,最后返回`'foo'`,所以比较结果是`true`。
|
||
|
||
|
||
|
||
**(3)undefined 和 null**
|
||
|
||
`undefined`和`null`只有与自身比较,或者互相比较时,才会返回`true`;与其他类型的值比较时,结果都为`false`。
|
||
|
||
```js
|
||
undefined == undefined // true
|
||
null == null // true
|
||
undefined == null // true
|
||
|
||
false == null // false
|
||
false == undefined // false
|
||
|
||
0 == null // false
|
||
0 == undefined // false
|
||
```
|
||
|
||
|
||
|
||
**(4)相等运算符的缺点**
|
||
|
||
相等运算符隐藏的类型转换,会带来一些违反直觉的结果。
|
||
|
||
```js
|
||
0 == '' // true
|
||
0 == '0' // true
|
||
|
||
2 == true // false
|
||
2 == false // false
|
||
|
||
false == 'false' // false
|
||
false == '0' // true
|
||
|
||
false == undefined // false
|
||
false == null // false
|
||
null == undefined // true
|
||
|
||
' \t\r\n ' == 0 // true
|
||
```
|
||
|
||
上面这些表达式都不同于直觉,很容易出错。因此建议不要使用相等运算符(`==`),最好只使用严格相等运算符(`===`)。
|
||
|
||
|
||
|
||
#### 不相等运算符
|
||
|
||
相等运算符有一个对应的“不相等运算符”(`!=`),它的算法就是先求相等运算符的结果,然后返回相反值。
|
||
|
||
```js
|
||
1 != '1' // false
|
||
|
||
// 等同于
|
||
!(1 == '1')
|
||
```
|
||
|
||
|
||
|
||
## 3. 布尔运算符(逻辑运算符)
|
||
|
||
|
||
|
||
布尔运算符用于将表达式转为布尔值,一共包含四个运算符。
|
||
|
||
|
||
|
||
- 取反运算符:`!`
|
||
- 且(并)运算符:`&&`
|
||
- 或运算符:`||`
|
||
- 三元运算符:`?:`
|
||
|
||
|
||
|
||
#### 取反运算符(!)
|
||
|
||
取反运算符是一个感叹号,用于将布尔值变为相反值,即`true`变成`false`,`false`变成`true`。
|
||
|
||
```js
|
||
!true // false
|
||
!false // true
|
||
```
|
||
|
||
对于非布尔值,取反运算符会将其转为布尔值。可以这样记忆,以下六个值取反后为`true`,其他值都为`false`。
|
||
|
||
- `undefined`
|
||
- `null`
|
||
- `false`
|
||
- `0`
|
||
- `NaN`
|
||
- 空字符串(`''`)
|
||
|
||
```js
|
||
!undefined // true
|
||
!null // true
|
||
!0 // true
|
||
!NaN // true
|
||
!"" // true
|
||
|
||
!54 // false
|
||
!'hello' // false
|
||
![] // false
|
||
!{} // false
|
||
```
|
||
|
||
上面代码中,不管什么类型的值,经过取反运算后,都变成了布尔值。
|
||
|
||
如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与`Boolean`函数的作用相同。这是一种常用的类型转换的写法。
|
||
|
||
```js
|
||
!!x
|
||
// 等同于
|
||
Boolean(x)
|
||
```
|
||
|
||
上面代码中,不管`x`是什么类型的值,经过两次取反运算后,变成了与`Boolean`函数结果相同的布尔值。所以,两次取反就是将一个值转为布尔值的简便写法。
|
||
|
||
|
||
|
||
#### 且运算符(&&)
|
||
|
||
且运算符(`&&`)往往用于多个表达式的求值。
|
||
|
||
它的运算规则是:如果第一个运算子的布尔值为`true`,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为`false`,则直接返回第一个运算子的值,且不再对第二个运算子求值。
|
||
|
||
```js
|
||
't' && '' // ""
|
||
't' && 'f' // "f"
|
||
't' && (1 + 2) // 3
|
||
'' && 'f' // ""
|
||
'' && '' // ""
|
||
|
||
var x = 1;
|
||
(1 - 1) && ( x += 1) // 0
|
||
x // 1
|
||
```
|
||
|
||
上面代码的最后一个例子,由于且运算符的第一个运算子的布尔值为`false`,则直接返回它的值`0`,而不再对第二个运算子求值,所以变量`x`的值没变。
|
||
|
||
这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代`if`结构,比如下面是一段`if`结构的代码,就可以用且运算符改写。
|
||
|
||
```js
|
||
if (i) {
|
||
doSomething();
|
||
}
|
||
|
||
// 等价于
|
||
|
||
i && doSomething();
|
||
```
|
||
|
||
上面代码的两种写法是等价的,但是后一种不容易看出目的,也不容易除错,建议谨慎使用。
|
||
|
||
且运算符可以多个连用,这时返回第一个布尔值为`false`的表达式的值。如果所有表达式的布尔值都为`true`,则返回最后一个表达式的值。
|
||
|
||
```js
|
||
true && 'foo' && '' && 4 && 'foo' && true
|
||
// ''
|
||
|
||
1 && 2 && 3
|
||
// 3
|
||
```
|
||
|
||
上面代码中,例一里面,第一个布尔值为`false`的表达式为第三个表达式,所以得到一个空字符串。例二里面,所有表达式的布尔值都是`true`,所以返回最后一个表达式的值`3`。
|
||
|
||
|
||
|
||
#### 或运算符(||)
|
||
|
||
或运算符(`||`)也用于多个表达式的求值。它的运算规则是:如果第一个运算子的布尔值为`true`,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为`false`,则返回第二个运算子的值。
|
||
|
||
```js
|
||
't' || '' // "t"
|
||
't' || 'f' // "t"
|
||
'' || 'f' // "f"
|
||
'' || '' // ""
|
||
```
|
||
|
||
短路规则对这个运算符也适用。
|
||
|
||
```js
|
||
var x = 1;
|
||
true || (x = 2) // true
|
||
x // 1
|
||
```
|
||
|
||
上面代码中,或运算符的第一个运算子为`true`,所以直接返回`true`,不再运行第二个运算子。所以,`x`的值没有改变。这种只通过第一个表达式的值,控制是否运行第二个表达式的机制,就称为“短路”(short-cut)。
|
||
|
||
或运算符可以多个连用,这时返回第一个布尔值为`true`的表达式的值。如果所有表达式都为`false`,则返回最后一个表达式的值。
|
||
|
||
```js
|
||
false || 0 || '' || 4 || 'foo' || true
|
||
// 4
|
||
|
||
false || 0 || ''
|
||
// ''
|
||
```
|
||
|
||
上面代码中,例一里面,第一个布尔值为`true`的表达式是第四个表达式,所以得到数值4。例二里面,所有表达式的布尔值都为`false`,所以返回最后一个表达式的值。
|
||
|
||
或运算符常用于为一个变量设置默认值。
|
||
|
||
```js
|
||
function saveText(text) {
|
||
text = text || '';
|
||
// ...
|
||
}
|
||
|
||
// 或者写成
|
||
saveText(this.text || '')
|
||
```
|
||
|
||
上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。
|
||
|
||
|
||
|
||
#### 三元条件运算符(?:)
|
||
|
||
三元条件运算符由问号(?)和冒号(:)组成,分隔三个表达式。它是 JavaScript 语言唯一一个需要三个运算子的运算符。如果第一个表达式的布尔值为`true`,则返回第二个表达式的值,否则返回第三个表达式的值。
|
||
|
||
```js
|
||
't' ? 'hello' : 'world' // "hello"
|
||
0 ? 'hello' : 'world' // "world"
|
||
```
|
||
|
||
上面代码的`t`和`0`的布尔值分别为`true`和`false`,所以分别返回第二个和第三个表达式的值。
|
||
|
||
通常来说,三元条件表达式与`if...else`语句具有同样表达效果,前者可以表达的,后者也能表达。但是两者具有一个重大差别,`if...else`是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用`if..else`。
|
||
|
||
```js
|
||
console.log(true ? 'T' : 'F');
|
||
```
|
||
|
||
上面代码中,`console.log`方法的参数必须是一个表达式,这时就只能使用三元条件表达式。如果要用`if...else`语句,就必须改变整个代码写法了。
|
||
|
||
|
||
|
||
## 4. 位运算符
|
||
|
||
|
||
|
||
按位运算符是将操作数换算成 *32* 位的二进制整数,然后按每一位来进行运算。例如:
|
||
|
||
*5* 的 *32* 位为:
|
||
|
||
```
|
||
00000000000000000000000000000101
|
||
```
|
||
|
||
*100* 的 *32* 位为:
|
||
|
||
```
|
||
00000000000000000000000001100100
|
||
```
|
||
|
||
*15* 的 *32* 位为:
|
||
|
||
```
|
||
00000000000000000000000000001111
|
||
```
|
||
|
||
|
||
|
||
#### 按位非
|
||
|
||
按位非运算符`~`会把数字转为32位二进制整数,然后反转每一位。所有的 1 变为 0,所有的 0 变为 1
|
||
|
||
例如:
|
||
|
||
5 的 32 位为:
|
||
|
||
```
|
||
00000000000000000000000000000101
|
||
```
|
||
|
||
~5 的 32 位为:
|
||
|
||
```
|
||
11111111111111111111111111111010
|
||
```
|
||
|
||
转换出来就为 -6
|
||
|
||
按位非,实质上是对操作数求负,然后减去1。
|
||
|
||
|
||
|
||
#### 按位与
|
||
|
||
按位或运算符`&`会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位与运算。按位与的规则如下表:
|
||
|
||
| 第一个数字 | 第二个数字 | 结果 |
|
||
| ---------- | ---------- | ---- |
|
||
| 1 | 1 | 1 |
|
||
| 1 | 0 | 0 |
|
||
| 0 | 1 | 0 |
|
||
| 0 | 0 | 0 |
|
||
|
||
具体示例:
|
||
|
||
```js
|
||
console.log(12 & 10); // 8
|
||
```
|
||
|
||
12 的 32 位二进制表示为:1100
|
||
10 的 32 位二进制表示为:1010
|
||
|
||
按位与的结果为:1000
|
||
|
||
|
||
|
||
#### 按位或
|
||
|
||
按位或运算符`|`会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位或运算。按位或的规则如下表:
|
||
|
||
| 第一个数字 | 第二个数字 | 结果 |
|
||
| ---------- | ---------- | ---- |
|
||
| 1 | 1 | 1 |
|
||
| 1 | 0 | 1 |
|
||
| 0 | 1 | 1 |
|
||
| 0 | 0 | 0 |
|
||
|
||
具体示例:
|
||
|
||
```js
|
||
console.log(12 | 10); // 14
|
||
```
|
||
|
||
12 的 32 位二进制表示为:1100
|
||
10 的 32 位二进制表示为:1010
|
||
|
||
按位或的结果为:1110
|
||
|
||
|
||
|
||
#### 按位异或
|
||
|
||
按位或运算符`^`会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位异或运算。运算规则为两位不同返回 1,两位相同返回 0,如下表:
|
||
|
||
| 第一个数字 | 第二个数字 | 结果 |
|
||
| ---------- | ---------- | ---- |
|
||
| 1 | 1 | 0 |
|
||
| 1 | 0 | 1 |
|
||
| 0 | 1 | 1 |
|
||
| 0 | 0 | 0 |
|
||
|
||
具体示例:
|
||
|
||
```js
|
||
console.log(12 ^ 10); // 6
|
||
```
|
||
|
||
12 的 32 位二进制表示为:1100
|
||
10 的 32 位二进制表示为:1010
|
||
|
||
按位异或的结果为:0110
|
||
|
||
按位异或如果是非整数值,如果两个操作数中只有一个为真,就返回 1,如果两个操作数都是真,或者都是假,就返回 0,示例如下:
|
||
|
||
```js
|
||
console.log(true ^ "Hello"); // 1
|
||
console.log(false ^ "Hello"); // 0
|
||
console.log(true ^ true); // 0
|
||
console.log("Hello" ^ "Hello"); // 0
|
||
console.log(false ^ false); // 0
|
||
console.log(true ^ false); // 1
|
||
```
|
||
|
||
注意这里的 Hello 被转换为了 NaN
|
||
|
||
|
||
|
||
#### 按位移位
|
||
|
||
按位移位运算符`<<`和`>>`会将所有位向左或者向右移动指定的数量,实际上就是高效率地将数字乘以或者除以 2 的指定数的次方。
|
||
|
||
`<<`:乘以 2 的指定数次方
|
||
|
||
```js
|
||
console.log(2<<2); // 8
|
||
```
|
||
|
||
2 乘以 2 的 2 次方
|
||
|
||
00000010 转换为 00001000
|
||
|
||
`>>`:除以 2 的指定数次方
|
||
|
||
```js
|
||
console.log(16>>1); // 8
|
||
```
|
||
|
||
16 除以 2 的 1 次方
|
||
|
||
00010000转换为00001000
|
||
|
||
|
||
|
||
## 5. 其他运算符
|
||
|
||
|
||
|
||
#### *void* 运算符
|
||
|
||
*void* 运算符的作用是执行一个表达式,然后不返回任何值,或者说返回 *undefined*。
|
||
|
||
```js
|
||
void 0 // undefined
|
||
void(0) // undefined
|
||
```
|
||
|
||
上面是 *void* 运算符的两种写法,都正确。建议采用后一种形式,即总是使用圆括号。
|
||
|
||
因为 *void* 运算符的优先性很高,如果不使用括号,容易造成错误的结果。
|
||
|
||
比如,*“void 4 + 7”* 实际上等同于 *“(void 4) + 7”*。
|
||
|
||
下面是 *void* 运算符的一个例子。
|
||
|
||
```js
|
||
var x = 3;
|
||
void (x = 5) //undefined
|
||
x // 5
|
||
```
|
||
|
||
这个运算符的主要用途是浏览器的书签工具(*Bookmarklet*),以及在超级链接中插入代码防止网页跳转。
|
||
|
||
请看下面的代码。
|
||
|
||
```js
|
||
<script>
|
||
function f() {
|
||
console.log('Hello World');
|
||
}
|
||
</script>
|
||
<a href="http://example.com" onclick="f(); return false;">点击</a>
|
||
```
|
||
|
||
上面代码中,点击链接后,会先执行 *onclick* 的代码,由于 *onclick* 返回 *false*,所以浏览器不会跳转到 *example.com*。
|
||
|
||
*void* 运算符可以取代上面的写法。
|
||
|
||
```js
|
||
<a href="javascript: void(f())">文字</a>
|
||
```
|
||
|
||
下面是一个更实际的例子,用户点击链接提交表单,但是不产生页面跳转。
|
||
|
||
```js
|
||
<a href="javascript: void(document.form.submit())">
|
||
提交
|
||
</a>
|
||
```
|
||
|
||
|
||
|
||
#### 逗号运算符
|
||
|
||
逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
|
||
|
||
```js
|
||
'a', 'b' // "b"
|
||
|
||
var x = 0;
|
||
var y = (x++, 10);
|
||
x // 1
|
||
y // 10
|
||
```
|
||
|
||
上面代码中,逗号运算符返回后一个表达式的值。
|
||
|
||
逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作。
|
||
|
||
```javascript
|
||
var value = (console.log('Hi!'), true);
|
||
// Hi!
|
||
|
||
value // true
|
||
```
|
||
|
||
上面代码中,先执行逗号之前的操作,然后返回逗号后面的值。
|
||
|
||
|
||
|
||
## 6. 运算顺序
|
||
|
||
|
||
|
||
#### 优先级
|
||
|
||
*JavaScript* 各种运算符的优先级别(*Operator Precedence*)是不一样的。优先级高的运算符先执行,优先级低的运算符后执行。
|
||
|
||
```js
|
||
4 + 5 * 6 // 34
|
||
```
|
||
|
||
上面的代码中,乘法运算符( * )的优先性高于加法运算符( + ),所以先执行乘法,再执行加法,相当于下面这样。
|
||
|
||
```js
|
||
4 + (5 * 6) // 34
|
||
```
|
||
|
||
如果多个运算符混写在一起,常常会导致令人困惑的代码。
|
||
|
||
```js
|
||
var x = 1;
|
||
var arr = [];
|
||
|
||
var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];
|
||
```
|
||
|
||
上面代码中,变量 *y* 的值就很难看出来,因为这个表达式涉及 *5* 个运算符,到底谁的优先级最高,实在不容易记住。
|
||
|
||
根据语言规格,这五个运算符的优先级从高到低依次为:小于等于( <= )、严格相等( === )、或( || )、三元( ?: )、等号( = )。因此上面的表达式,实际的运算顺序如下。
|
||
|
||
```js
|
||
var y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];
|
||
```
|
||
|
||
记住所有运算符的优先级,是非常难的,也是没有必要的。
|
||
|
||
|
||
|
||
#### 圆括号的作用
|
||
|
||
圆括号可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的表达式会第一个运算。
|
||
|
||
```js
|
||
(4 + 5) * 6 // 54
|
||
```
|
||
|
||
上面代码中,由于使用了圆括号,加法会先于乘法执行。
|
||
|
||
运算符的优先级别十分繁杂,且都是硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要。
|
||
|
||
顺便说一下,圆括号不是运算符,而是一种语法结构。它一共有两种用法:一种是把表达式放在圆括号之中,提升运算的优先级;另一种是跟在函数的后面,作用是调用函数。
|
||
|
||
注意,因为圆括号不是运算符,所以不具有求值作用,只改变运算的优先级。
|
||
|
||
```js
|
||
var x = 1;
|
||
(x) = 2;
|
||
```
|
||
|
||
上面代码的第二行,如果圆括号具有求值作用,那么就会变成 *1 = 2*,这是会报错了。但是,上面的代码可以运行,这验证了圆括号只改变优先级,不会求值。
|
||
|
||
这也意味着,如果整个表达式都放在圆括号之中,那么不会有任何效果。
|
||
|
||
```js
|
||
(expression)
|
||
// 等同于
|
||
expression
|
||
```
|
||
|
||
函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数。
|
||
|
||
```js
|
||
function f() {
|
||
return 1;
|
||
}
|
||
|
||
(f) // function f(){return 1;}
|
||
f() // 1
|
||
```
|
||
|
||
上面代码中,函数放在圆括号之中会返回函数本身,圆括号跟在函数后面则是调用函数。
|
||
|
||
圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错。
|
||
|
||
```javascript
|
||
(var a = 1)
|
||
// SyntaxError: Unexpected token var
|
||
```
|
||
|
||
|
||
|
||
#### 左结合和右结合
|
||
|
||
对于优先级别相同的运算符,同时出现的时候,就会有计算顺序的问题。
|
||
|
||
```javascript
|
||
a OP b OP c
|
||
```
|
||
|
||
上面代码中,*OP* 表示运算符。它可以有两种解释方式。
|
||
|
||
```javascript
|
||
// 方式一
|
||
(a OP b) OP c
|
||
|
||
// 方式二
|
||
a OP (b OP c)
|
||
```
|
||
|
||
上面的两种方式,得到的计算结果往往是不一样的。
|
||
|
||
方式一是将左侧两个运算数结合在一起,采用这种解释方式的运算符,称为“左结合”(*left-to-right associativity*)运算符;
|
||
|
||
方式二是将右侧两个运算数结合在一起,这样的运算符称为“右结合”运算符(*right-to-left associativity*)。
|
||
|
||
*JavaScript* 语言的大多数运算符是“左结合”,请看下面加法运算符的例子。
|
||
|
||
```javascript
|
||
x + y + z
|
||
|
||
// 引擎解释如下
|
||
(x + y) + z
|
||
```
|
||
|
||
上面代码中,*x* 与 *y* 结合在一起,它们的预算结果再与 *z* 进行运算。
|
||
|
||
少数运算符是“右结合”,其中最主要的是赋值运算符( = )和三元条件运算符( ?: )。
|
||
|
||
```javascript
|
||
w = x = y = z;
|
||
q = a ? b : c ? d : e ? f : g;
|
||
```
|
||
|
||
上面代码的解释方式如下。
|
||
|
||
```javascript
|
||
w = (x = (y = z));
|
||
q = a ? b : (c ? d : (e ? f : g));
|
||
```
|
||
|
||
上面的两行代码,都是右侧的运算数结合在一起。
|
||
|
||
另外,指数运算符(\**)也是右结合。
|
||
|
||
```javascript
|
||
2 ** 3 ** 2
|
||
// 相当于 2 ** (3 ** 2)
|
||
// 512
|
||
```
|
||
|
||
|
||
|
||
## 真题解答
|
||
|
||
|
||
|
||
- 下面代码中,*a* 在什么情况下会执行输出语句打印 *1* ?
|
||
|
||
```js
|
||
var a = ?;
|
||
if(a == 1 && a == 2 && a == 3){
|
||
console.log(1);
|
||
}
|
||
```
|
||
|
||
> 参考答案:
|
||
>
|
||
> 方法一:利用 *toString( )* 方法
|
||
>
|
||
> ```js
|
||
> var a = {
|
||
> i: 1,
|
||
> toString() {
|
||
> return a.i++;
|
||
> }
|
||
> }
|
||
> if (a == 1 && a == 2 && a == 3) {
|
||
> console.log('1');
|
||
> }
|
||
> ```
|
||
>
|
||
> 方法二:利用 *valueOf( )* 方法
|
||
>
|
||
> ```js
|
||
> var a = {
|
||
> i: 1,
|
||
> valueOf() {
|
||
> return a.i++
|
||
> }
|
||
> }
|
||
> if (a == 1 && a == 2 && a == 3) {
|
||
> console.log('1');
|
||
> }
|
||
> ```
|
||
|
||
|
||
|
||
-*EOF*-
|
||
|
||
|