2024-08-27 10:14:31 +08:00

1371 lines
34 KiB
Markdown
Raw Permalink 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.

# 运算符
## 经典真题
- 下面代码中,*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`
**4undefined 和 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`
**3undefined 和 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*-