运算符
最后更新于:2022-04-01 23:53:12
## 运算符
ECMA-262描述了一组用于操作数据值的运算符,包括算术运算符、位运算符、关系运算符和相等运算符。
**1.1 一元运算符**
只能操作一个值的运算符叫做一元运算符。
**1.递增和递减**
递增(++)和递减(--)都有两种使用方式:**前置型**和**后置型**。
**前置型**指的是运算符放在要操作的变量之前,而**后置型**则是运算符放在要操作的变量之后。
```
var age = 10;
var age2 = 10;
++age;
--age;
console.log(age); // 11
console.log(age2); //9
```
执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的(也就是先自增或自减,再和其他值进行操作):
```
var num = 2;
var age = ++num + 2;
console.log(age); // 5
```
在上面的例子中,在与2相加之前,num已经加1变成了2。
后置型递增和递减运算符不变(依然是++和--),不过除了要放到变量之后外,包含它们的语句是被求值之后才执行(先和其他值进行操作后,再自增或自减):
```
var num = 2;
var age = (num++) + 2;
console.log(age); // 4
console.log(num); // 3
```
注意:由于JavaScript会自动进行分号补全,因此不能在后增量运算符和操作数之间插入换行符
```
num
++;
```
这会报错
这4个操作符对任何值都适用,不过,当应用于不同的值时,递减和递增操作符会遵循下列规则:
- 当操作数是一个包含有效数字字符的字符串,系统会将其转换为数字值,再执行递减或递增。
- 当操作数是一个不包含有效数字字符的字符串,系统将变量的值设置为NaN
- 当操作数是布尔值,会将其转为数值(true转为1,false转为0)再操作。
- 当操作数是浮点数值,直接执行递减或递增
- 当操作数是对象,先调用对象的valueOf()方法取得一个可供操作的值,然后再遵循上面的三条规则。如果结果是NaN,则在调用toString()方法后再遵循上面的规则转换。
```
var a = '2';
var b = 'a';
var c = false;
var d = 1.1;
var o = {
valueOf: function() {
return -1;
}
};
a++; // 3
b++; // NaN
c--; // -1
d--; // 0.10000000000000009 (浮点数操作结果,类似0.1+0.2 != 0.3)
o--; -2
```
**2.一元加和减运算符**
一元加(+)和减(-)运算符和数学上的含义一样,表示负正。
不过,对非数值应用一元加运算符时,该运算符就像Number()转型函数一样对这个值执行转换。(转换规则参考数据类型那一节中的Number()的转换规则)
一元减运算符对数值是表示负数,而对于非数值,转换规则和一元加(也就是和Number())一样,然后将转换后的数值转为负数。
**1.2 位运算符**
位运算符是按内存中表示数值的位来操作数值。
ECMAScript中的数值都以IEEE-75464位格式存储,但位运算符并不直接操作64位的值,而是先将64位的值转换为32位,然后执行操作,最后将结果转换回64位。
对于有符号的整数,32位(从右往左,最右边为第一位)中的前31位用于表示整数的值,第32位用于表示数值的符号:0表示正数,1表示负数。这个表示符号的位叫做符号位。符号位的值决定了其他位数值的格式。
正数以纯二进制格式存储,31位中的每一位(从右往左)都表示2的幂。第一位(叫做位0)表示2^0,第二位表示2^1,以此类推。没有用到的位以0填充,可以忽略。
```
18
二进制表示法:10010
2^4 × 1 + 2^3 × 0 + 2^2 × 0 + 2^1 × 1 + 2^0 × 0 = 18
```
负数同样以二进制码存储,但使用的格式是二进制补码。
如何计算一个数值的二进制补码?需要3个步骤:
* 求这个数值绝对值的二进制码
* 求二进制反码,即0替换1,将1替换0
* 得到二进制反码加1
```
-18
//第一步,求18的二进制码
0000 0000 0000 0000 0000 0000 0001 0010
// 求反码,后加1
1111 1111 1111 1111 1111 1111 1110 1101
1
1111 1111 1111 1111 1111 1111 1110 1110 // -18的二进制表示
```
如果对非数值应用位运算符,会先使用Number()函数将该值转换为一个数值(自动转换),再进行位运算符操作,最后得到一个数值
当你去求负数的二进制时,ECMAScript会尽力向我们隐藏所有这些信息。比如:
```
var num = -18;
console.log(num.toString(2)); // "-10010"
```
上面的代码对负数求二进制码,得到的结果只是这个负数绝对值的二进制码前面加上一个负号。
**(1)按位非(NOT)**
按位非运算符(~)进行`否运算`,执行按位非的结果就是返回数值的反码。
```
~ 3 // -4
```
上面的表达式对3进行否运算,得到-4。
```
//3的二进制码
00000000000000000000000000000011
// 取反码
11111111111111111111111111111100
//补码转为十进制的规则,在下面可以看到
11111111111111111111111111111011 //减1
00000000000000000000000000000100 //再次取反得到~3的十进制,也就是4,加上负号,就是-4
```
负数二进制码(补码)转换为十进制的规则:需要将这个数减去1,再取一次反,然后加上负号,才能得到这个负数对应的10进制值。
看起来是不是眼花缭乱,其实我们可以不用管里面的转换逻辑,只需记得按位非(否运算)就是**一个数与自身的取反值相加,等于-1**。
```
~3 + 3 = -1 => -1 - 3 = -4 == ~3
```
**2.按位与(AND)**
使用按位与(&)运算符来进行`与运算`,它有两个运算符数。
```
0 & 3 //0
0和1的二进制是00和11
00
11
00
```
运算规则是只有两个数值的对应位都是1时才返回1,其他情况返回0。
**按位或(OR)**
使用按位或(|)运算符进行`或运算`.
```
0 | 3 // 3
00
11
11
```
或运算的规则是只要有一个位是1就返回1,只有两个位都是0时才返回0
位运算只对整数有效,遇到小数时,会将小数部分舍去,只保留整数部分。所以,将一个小数与0进行或运算,等同于对该数去除小数部分,即取整数位。
**4.按位异或(XOR)**
使用按位异或(^)来进行`异或运算`。
```
0 ^ 3 // 3
00
11
11
```
异或运算的规则是两个数值的对应位上只有一个1时才返回1,其他情况返回0.
**5.左移**
左移运算符(<<),这个运算符会将所有位向左移动指定的位数,尾部补0。
```
2 << 5 // 64
2的二进制码
10
1000000
```
左移可看做是数值乘以2的指定次方
注意:左移不会影响操作数的符号位,也就是说,-2向左移动5位后,是-64
**6.右移**
**(1)有符号的右移**
有符号的右移(>>)将数值向右移动,但保留符号位(即正负号标记,也就是第32位)
```
64 >> 5 // 2
1000000
10
-64 >> 5 //-2
```
有符号的右移可看做是数值除以2的指定次方
**(2)无符号的右移**
无符号的右移(>>>),这个运算符会将数值的所有32位向右移动。对正数来说,无符号右移的结果与有符号右移相同:
```
64 >>> 5 //2
```
但负数就不一样了,无符号右移是以0来填充空位的,所以无符号右移得到的结果都是正数。
```
-64 >>> 5 // 134217726
```
有一点需要特别注意,位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。
**1.3 布尔运算符**
布尔运算符是用来比较两个值的关系的。
**1. 逻辑非(or)**
逻辑非(!)可以应用于JavaScript中的任何值,最终都会返回一个布尔值。
逻辑非运算符首先会将它的操作数转换为一个布尔值,然后再取反。
**逻辑非规则**:
* 如果操作数是一个对象、非空字符串、任意非0数值(包括Infinity),则返回false
* 如果操作数是空字符串、数值0、null、undefined、NaN,则返回true
如果同时使用两个(!!),就会像使用Boolean()转型函数一样的转换逻辑。
```
!false // true
!'tg' // false
!'' // true
!!0 //false
```
**2.逻辑与**
逻辑与(&&)有两个操作数,如果是布尔值,只有两个都是true时,才会返回true,否则返回false
```
var isTrue = true && false; //false
```
如果不是布尔值,它遵循下面的规则:
* 如果第一个操作数是对象,则返回第二个操作数
* 如果第二个操作数是对象,则只有在第一个操作数的求值为true时才会返回第二个操作数
* 如果有一个操作数是null,则返回null
* 如果有一个操作数是NaN,则返回NaN
* 如果有一个操作数是undefined,则返回undefined
逻辑与操作符也就是先将第一个操作数转换为Boolean类型判断是true或false,再根据结果决定是否执行第二个操作数
```
0 && 'tg' ; // 0
{} && 'tg'; // "tg"
```
逻辑与操作属于短路操作,也就是说如果第一个操作数能够决定结果(等于false时),就不会再对第二个操作数求值。
**3.逻辑或**
逻辑或(||)也有两个操作数。
如果两个操作数都是布尔值,则有一个为true时,就返回true,否则返回false。
如果有一个操作数不是布尔值,则遵循下列规则:
* 如果第一个操作数是对象,则返回第一个操作数
* 如果第一个操作数的求值结果是false,则返回第二个操作数
* 如果两个操作数都是对象,则返回第一个操作数
* 如果两个操作数都是null,则返回null
* 如果两个操作数都是NaN,则返回NaN
* 如果两个操作数都是undefined,则返回undefined
逻辑或也是短路运算符,也就是说,如果第一个操作数的求值结果为true,就不会对第二个操作数求值了。
```
true || 'tg'; // true
0 || 'tg'; // "tg"
```
一般情况下,我们可以使用逻辑或来避免变量赋null或undefined值:
```
function test(name){
name = name || 'tg';
console.log(name);
}
test(); // "tg"
test('tg2'); // "tg2"
```
上面的例子,表示当调用test()方法不传参时,name赋予一个默认值"tg";如果带有参数,则使用参数值。
注意:逻辑与(&&)和逻辑或(||)返回的都是运算值。
**1.4 乘性运算符**
ECMAScript定义了3个乘性运算符:乘法、除法和求模。
当操作数是非数值时,会执行自动的类型转换。如果操作数不是数值,会先使用Number()转型函数将其转换为数值(后台自动),再进行运算。
**1.乘法**
乘法运算符(`*`),用于计算两个数值的乘积。
处理特殊值时,乘法运算符会遵循下列规则:
* 如果操作数都是数值,但乘积超过了ECMAScript数值范围,则返回Infinity或-Infinity
* 如果有一个操作数是NaN,结果是NaN
* 如果是Infinity乘以0,结果是NaN
* 如果是Infinity与非0数值相乘,结果是Infinity或-Infinity,取决于非0数值的符号
* 如果是Infinity与Infinity相乘,结果是Infinity
* 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后遵循上面的规则
```
console.log(1 * NaN); // NaN
console.log( Infinity * 2); // Infinity
console.log(Infinity * 0); // NaN
console.log(Infinity * Infinity); // Infinity
```
**2.除法**
除法运算符(/),执行第二个操作数除第一个操作数计算。
处理特殊值,规则如下:
* 如果操作数都是数值,但商超过了ECMAScript的表示范围,则返回Infinity或-Infinity
* 如果有一个操作数是NaN,结果是NaN
* 如果是Infinity被Infinity除,结果是NaN
* 如果是零被零除,结果是NaN
* 如果是非零的有限数被零除,结果是Infinity或-Infinity,取决于有符号的操作数
* 如果是Infinity被任何非零数值除,结果是Infinity或-Infinity
* 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后遵循上面的规则。
```
console.log(NaN / 1); // NaN
console.log(0 / 0); // NaN
console.log(1 / 0); // Infinity
console.log(2 / Infinity); // 0
console.log(Infinity / Infinity); // NaN
console.log(Infinity / 2); // Infinity
```
**3.求模**
求模(余数)运算符(%)
处理特殊值,规则如下:
* 如果被除数是无穷大值而除数是有限大的数值,结果是NaN
* 如果被除数是有限大的数值而除数是零,结果是NaN
* 如果是Infinity被Infinity除,结果是NaN
* 如果被除数是有限大的数值而除数是无穷大的数值,结果是被除数
* 如果被除数是零,结果是零
* 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后遵循上面的规则。
```
console.log(5 % 3); // 2
```
**1.5加性运算符**
加性运算符也会在后台转换不同的数据类型。
**1.加法**
如果两个操作数都是数值,执行常规的加法计算,然后根据下面的规则返回结果:
* 如果有一个操作数是NaN,结果是NaN
* 如果Infinity加Infinity,结果是Infinity
* 如果是-Infinity加-Infinity,结果是-Infinity
* 如果是Infinity加-Infinity,结果是NaN
* 如果是+0加+0,结果是+0
* 如果是-0加-0,结果是-0
* 如果是+0加-0,结果是+0
如果有一个操作数是字符串,则遵循下列规则:
* 如果两个操作数都是字符串,则拼接两个字符串
* 如果只有一个操作数是字符串,则将另一操作数转换为字符串,然后拼接
* 如果有一个操作数是对象、数值或布尔值,则调用它们的toString()方法取得相应的字符串值,然后遵循上面的规则。对于undefined和null,则分别调用String()函数并取得字符串"undefined"和"null"。
```
5 + 5 //10
5 + '5" // "55"
```
如果你要在有字符串操作数的加法中执行常规的加法操作,应该使用圆括号将要相加的数值括起来:
```
'tg' + 5 + 5 // "tg55"
'tg" + (5 + 5) // "tg10"
```
**2.减法**
减法运算符(-)
对于特殊值,减法操作会遵循下列规则:
* 如果有一个操作数是NaN,结果是NaN
* 如果Infinity减Infinity,结果是NaN
* 如果是-Infinity减-Infinity,结果是NaN
* 如果是Infinity减-Infinity,结果是Infinity
* 如果是-Infinity减Infinity,结果是-Infinity
* 如果是+0减+0,结果是+0
* 如果是-0加-0,结果是+0
* 如果是+0减-0,结果是-0
* 如果有一个操作数是字符串、布尔值、null或undefined,则先在后台调用Number()将其转换为数值,然后遵循上面的规则进行计算。
* 如果有一个操作数是对象,则调用对象的valueOf()方法以取得表示该对象的数值;如果该对象没有valueOf()方法,则调用其toString()方法将得到的字符串转换为数值,然后遵循上面的规则进行计算。
```
5 - true; // 4 (true转换成1)
5 - '2' // 3
5 - null; // 5(null转换成0)
```
**1.6 关系运算符**
小于(<)、大于(>)、小于等于(<=)和大于等于(>=)四个关系运算符用来对两个值进行比较,最后返回一个布尔值。
当使用了非数值时,则会遵循下列规则:
* 如果两个操作数是字符串,则比较两个字符串对应的字符编码值
* 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后进行比较
* 如果一个操作数是对象,则调用这个对象的valueOf()方法,用得到的结果按照前面的规则比较。如果对象没有valueOf()方法,则调用toString()方法,并用得到的结果按照上面的规则进行比较。
* 如果一个操作数是布尔值,则先将其转换为数值,然后进行比较。
**注意**:在使用关系运算符比较两个字符串时,比较的是两个字符串中对应位置的每个字符的字符编码值(从左往右),经过逐个比较后,直到有胜负(也就是不相等时),再返还布尔值。
```
'abc' > 'abd' //false
'A' < 'a' // true
'23' < '3' // true
```
在上面的例子中,第一行代码会先比较"a"和"a",然后是"b"和"b",最后是"c"和"d",由于c的字符编码值是63,d的字符编码值是64,所以返还false。
由于小写字母的字符编码值总是大于大写字母的字符编码值,所以第二行代码返还true。
第三行代码也是字符串比较,所以比较的是字符编码值(2是50,3是51),所以返回true
注意:任何操作数与NaN比较,都会返回false。
**1.7 相等运算符**
相等运算符有两组:相等和不相等(先转换再比较)、全等和不全等(只比较不转换)
**1.相等和不相等**
相等(==),不相等(!=)
这两个运算符都会先转换操作数(强制转换),然后比较。
对于不同的数据类型,转换规则如下:
* 如果有一个操作数是布尔值,则在比较前先将其转换为数值(false转为0,true转为1)
* 如果一个操作数是字符串,另一个操作数是数值,先将字符串转为数值
* 如果一个操作数是对象,另一个不是,先调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较
* null和undefined是相等的
要比较之前,不能将null和undefined转换成其他任何值
* 如果有一个操作数是NaN,则相等运算符返回false,不相等运算符返回true。(要记住NaN不等于本身原则)
* 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等运算符返回true,否则返回false。
```
NaN == NaN // false
true == 1 // true
null == undefined // true
```
**2.全等和不全等**
除了在比较之前不转换操作数类型之外,全等和不全等与相等和不相等没什么区别。
```
'55' == 55 // true
'55' === 55 // false
```
**1.8 条件运算符**
条件运算符是ECMAScript中唯一的三元运算符。
```
variable = boolean_expression ? true_value : false_value;
```
如果boolean_expression返回true,则执行true_value,否则,执行false_value
```
var name = (1 > 2) ? 'tg' : 'tg2'; // "tg2"
```
上面的代码中1小于2,所以是false,则将"tg2"赋值给name。
**1.9 赋值运算符**
(=)是最简单的赋值操作,其作用是把右侧的值赋给左侧变量。
```
var name = 'tg';
```
复合赋值运算符
```
乘赋值(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) 等同于 x = x << y
有符号右移赋值(x >>= y) 等同于 x = x >> y
无符号右移赋值(x >>>= y) 等同于 x = x >>> y
```
例子:
```
var a = 1;
a += 1;
console.log(a); // 2
```
**1.10 逗号运算符**
使用逗号运算符可以在一条语句中执行多个操作:
```
var name = 'tg', age = 1;
```
逗号运算符多用于声明多个变量。
逗号运算符还可以用于赋值。在用于赋值时,逗号运算符总会返回表达式中的最后一项:
```
var num = (1,5,3); // num的值为3
```
3是表达式中的最后一项,因此num的值就是3.
**1.11 in运算符**
in运算符希望它的左操作数是一个字符串或可以转换成为字符串,希望它的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数值的属性名,则返回true。
```
var o = {x:1};
"x" in o //true
```
**1.12 instanceof运算符**
`instanceof`运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回true。
```
var a = new Array();
a instanceof Object; //true
```
注意:所有对象都是Object的实例
**1.13 typeof运算符**
`typeof`是一元运算符,用来判断数据类型:
```
typeof 1 // "number"
```
**1.14 delete运算符**
`delete`是一元运算符,它用来删除对象的属性或数组元素。
```
var o={x:1}
delete o.x;
"x" in o //false
```
**1.15 void运算符**
`void`是一元运算符,它出现在操作数之前,操作数可以是任意类型。操作数会照常计算,但忽略计算结果并返回undefined。
```
void 0 //undefined
void(0) //undefined
var a = 1;
void (a=2);
a //2
```
这个运算符主要是用于书签工具(bookmarklet),以及用于在超级链接中插入代码,目的是返回undefined可以防止网页跳转。
```
```
**2. 运算顺序**
**2.1 优先级**
JavaScript各种运算符的优先级别(Operator Precedence)是不一样的。优先级高的运算符先执行,优先级低的运算符后执行。
**2.2 圆括号**
圆括号(())可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的表达式会第一个运算。
注意:因为圆括号不是运算符,所以不具有求值作用,只改变运算的优先级。
对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的“左结合”(left-to-right associativity),即从左边开始计算。
但是少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的“右结合”(right-to-left associativity)。其中,最主要的是赋值运算符(=)和三元条件运算符(?:)。
';