操作符
ECMAScript 中的操作符可用于各种值,包括字符串、数值、布尔值,甚至还有对象。
在应用给对象时,操作符通常会调用 valueOf / toString 方法来取得可以计算的值。
一元操作符
递增 & 递减
有两个版本:前缀版 ++num
和后缀版 num++
。区别在于,++num
会先自增,再参与运算;num++
会先参与运算,再自增。
let num1 = 1,
num2 = 1;
const number1 = num1++, // 先参与运算,再自增
number2 = ++num2; // 先自增,再参与运算
console.log(`number1 - ${number1}; `, `num1 - ${num1}`); // number1 - 1; num1 - 2
console.log(`number2 - ${number2}; `, `num2 - ${num2}`); // number2 - 2; num2 - 2
对于 string、boolean 类型的原始数据和 undefined、null,会被隐式转换成 number 类型:
let numStr = '2';
++numStr; // Number("2") → 2
console.log(numStr); // 3
let bool = false;
++bool; // Number(false) → 0
console.log(bool); // 1
let un = undefined;
++un; // Number(undefined) → NaN
console.log(un); // NaN
let n = null;
++n; // Number(null) → 0
console.log(n); // 1
对于引用类型的数据,会调用 valueOf / toString 方法来取得可以计算的值:
let o1 = {};
++o1; // Number({}) → NaN
console.log(o1); // NaN
let o2 = {
valueOf() {
return 1;
},
toString() {
return -1;
},
};
o2--;
console.log(o2); // 0 —— 说明 valueOf 优先级高于 toString
let o3 = {
toString() {
return -1;
},
};
o3--;
console.log(o3); // -2
加 & 减
对于原始类型数据,相当于使用 Number()
进行类型转换:
let s1 = '01';
console.log(+s1); // 1
let b = false;
console.log(+b); // 0
let un = undefined;
console.log(+un); // NaN
let n = null;
console.log(+n); // 0
对于引用类型数据,会调用它们的 valueOf / toString 方法以得到可以转换的值:
let o = {
valueOf() {
return -1;
},
};
console.log(+o); // -1
乘性操作符
ECMAScript 定义了 3 个乘性操作符:乘法 - *
、除法 - /
、取模 - %
。
乘法操作符规则
- 如果操作数都是数值,则执行常规的乘法运算。如果 ECMAScript 不能表示乘积,则返回 Infinity 或 -Infinity。
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 乘以 0,则返回 NaN。
- 如果是 Infinity 乘以非 0 的有限数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity。
- 如果是 Infinity 乘以 Infinity,则返回 Infinity。
- 如果有不是数值的操作数,则先在后台用
Number()
将其转换为数值,然后再应用上述规则。
除法操作符规则
- 如果操作数都是数值,则执行常规的除法运算。如果 ECMAScript 不能表示商,则返回 Infinity 或 -Infinity。
- 如果有任一操作数是 NaN,则返回 NaN。
- 如果是 Infinity 除以 Infinity,则返回 NaN。
- 如果是 0 除以 0,则返回 NaN。
- 如果是非 0 的有限值除以 0,则根据第一个操作数的符号返回 Infinity 或 -Infinity。
- 如果是 Infinity 除以任何数值,则根据第二个操作数的符号返回 Infinity 或-Infinity。
- 如果有不是数值的操作数,则先在后台用
Number()
函数将其转换为数值,然后再应用上述规则。
取模操作符规则
- 如果操作数是数值,则执行常规除法运算,返回余数。
- 如果被除数是无限值,除数是有限值,则返回 NaN。
- 如果被除数是有限值,除数是 0,则返回 NaN。
- 如果是 Infinity 除以 Infinity,则返回 NaN。
- 如果被除数是有限值,除数是无限值,则返回被除数。
- 如果被除数是 0,除数不是 0,则返回 0。
- 如果有不是数值的操作数,则先在后台用
Number()
函数将其转换为数值,然后再应用上述规则。
加性操作符
加性操作符,即加法和减法操作符。
加法操作符规则
- 如果有任一操作数是 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”。
减法操作符规则
- 如果是 Infinity 减 Infinity,则返回 NaN
- 如果是 -Infinity 减 -Infinity,则返回 NaN
- 如果是 Infinity 减 -Infinity,则返回 Infinity
- 如果是 -Infinity 减 Infinity,则返回 -Infinity
- 如果是 +0 减 +0,则返回 +0
- 如果是 +0 减 -0,则返回 -0
- 如果是 -0 减 -0,则返回 +0
- 如果有任一操作数是字符串、布尔值、null 或 undefined,则先在后台使用
Number()
将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是 NaN,则减法计算的结果是 NaN - 如果有任一操作数是对象,则调用其
valueOf()
方法取得表示它的数值。如果该值是 NaN,则减法计算的结果是 NaN。如果对象没有valueOf()
方法,则调用其toString()
方法,然后再将得到的字符串转换为数值。
布尔操作符
布尔操作符一共有:逻辑非 - !
、逻辑与 - &&
、逻辑或 - ||
对于非布尔类型的操作值,JS 会将其隐式转换为 boolean
类型。
基础用法
-
&&
:只有两边都是true
才返回true
,否则返回false
。 -
||
:只有两边都是false
才返回false
,否则返回true
。 -
!
:取反。(6 个负性值:0
、NaN
、""
、null
、undefined
、false
; 其余值转布尔类型都为true
,取反则为false
)
console.log(true && true); // true
console.log(false || false); // false
console.log(!true); // false
console.log(!''); // true
短路算法
&&
会先将第一个操作值转为 boolean 类型进行判断:
- 如果是 false,则直接返回第一个值,第二个值不执行;
- 如果为 true,则执行并返回第二个值。
console.log(false && 5); // false
console.log(null && 哈哈); // null - 因为第二个值不执行,所以出错了也不会抛出
console.log(3 && 5); // 5
console.log(true && 'a'); // a
||
会先将第一个值转为 boolean 类型进行判断:
- 如果为 true,则直接返回第一个值,第二个值不执行;
- 如果为 false,则执行并返回第二个值。
console.log(false || 5); // 5
console.log(null || 'a'); // a
console.log(true || 'a'); // true
console.log(3 || 哈哈); // 3 - 因为第二个值不执行,所以出错了也不会抛出
关系操作符
关系操作符执行比较两个值的操作,包括小于 - <
、大于 - >
、小于等于 - <=
、大于等于 - >=
如果比较运算成立,则返回 true
;不成立 / 比较不了,则返回 false
。
10 > NaN; // false
10 > 2; // true
- 如果操作数都是 number,则执行数值比较。
- 如果操作数都是 string,则逐个比较字符串中对应字符的编码。
- 如果有任一操作数是 number,则将另一个操作数转换为数值,执行数值比较。
- 如果有任一操作数是 boolean,则将其转换为数值再执行比较。
- 如果有任一操作数是对象,首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则强制类型转换为数字来进行比较。
var a = [42];
var b = ['43'];
a < b; // true
b < a; // false
var a = ['42'];
var b = ['043'];
a < b; // false
a
和 b
并没有被转换为数字,因为 ToPrimitive 返回的是字符串,所以这里比较的是 "42"
和 "043"
两个字符串,它们分别以 "4"
和 "0"
开头。因为 "0"
在字母顺序上小于 "4"
,所以最后结果为 false。
var a = [4, 2];
var b = [0, 4, 3];
a < b; // false
a
转换为 "4,2"
, b
转换为 "0,4,3"
,同样是按字母顺序进行比较。
var a = { b: 42 };
var b = { b: 43 };
a < b; // ? ?
结果还是 false,因为 a
是 "[object Object]"
,b
也是 "[object Object]"
,所以按照字母顺序 a < b 并不成立。
var a = { b: 42 };
var b = { b: 43 };
a < b; // false
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
引用类型数据的相等比较,只有在两个引用指向同一个对象时才会返回 true。因为 a
和 b
指向不同的对象,所以 a == b
返回 false。
根据规范 a <= b
被处理为 b < a
,然后将结果反转。因为 b < a
的结果是 false,所以 a <= b
的结果是 true。
相等操作符
- 相等运算符
==
:比较值是否相等,数据会发生隐式类型转换。 - 全等运算符
===
:比较值 & 数据类型是否相等。
6 == '6'; // true
6 === '6'; // false
6 != '6'; // false
6 !== '6'; // true
- 如果任一操作数是 boolean,则将其转换为 number 再比较是否相等。false - 0、true - 1。
var a = '42';
var b = true;
a == b; // false
- 如果一个操作数是 string,另一个操作数是 number,则尝试将 string 👉 number,再比较是否相等。
var a = 42;
var b = '42';
a === b; // false
a == b; // true
- 如果 Type(x) 是 number / string,Type(y) 是对象,则返回
x == ToPrimitive(y)
的结果。
var a = 42;
var b = [42];
a == b; // true
[42]
首先调用 ToPrimitive 抽象操作,返回 "42"
,变成 "42" == 42
,然后又变成 42 == 42
,最后二者相等。
var a = 'abc';
var b = Object(a); // 和 new String(a) 一样
a === b; // false
a == b; // true
a == b
结果为 true,因为 b
通过 ToPrimitive 进行强制类型转换,并返回标量基本类型值 "abc"
,与 a
相等。
var a = null;
var b = Object(a); // 和 Object() 一样
a == b; // false
var c = undefined;
var d = Object(c); // 和 Object() 一样
c == d; // false
var e = NaN;
var f = Object(e); // 和 new Number(e) 一样
e == f; // false
因为没有对应的封装对象,所以 null
和 undefined
不能够被封装,Object(null)
和 Object()
均返回一个常规对象。
NaN
能够被封装为数字封装对象,但拆封之后 NaN == NaN
返回 false,因为 NaN 不等于 NaN。
注意事项:
- 比较运算符不要连续使用。因为比较运算返回的是 boolean 值,可能会影响实际的比较结果
console.log(3 > 2 > 1); // 3 > 2 返回 true, Number(true) 为 1, 所以最终返回 false
- 特别的 1 :
null
和undefined
不能转换为其他类型的值再进行比较
console.log(null == 0); // false
console.log(undefined == 0); // false
- 特别的 2 :
null
与undefined
相等,但不全等(null === null
;undefined === undefined
)
console.log(null == undefined); // true → 因为 undefined 继承自 Null
console.log(null === undefined); // false → null 是 Null 类型,undefined 是 underfined 类型
- 特别的 3 :
NaN != 任何值
( 连自己都不等于 )
console.log(NaN == NaN); // false
比较少见的情况
- 返回其他数字
Number.prototype.valueOf = function () {
return 3;
};
new Number(2) == 3; // true
Number(2)
涉及 ToPrimitive 强制类型转换,因此会调用 valueOf()
。
- 假值的相等比较
'0' == null; // false
'0' == undefined; // false
'0' == false; // true -- 晕!
'0' == NaN; // false
'0' == 0; // true
'0' == ''; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ''; // true -- 晕!
false == []; // true -- 晕!
false == {}; // false
'' == null; // false
'' == undefined; // false
'' == NaN; // false
'' == 0; // true -- 晕!
'' == []; // true -- 晕!
'' == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!
0 == {}; // false
- 极端情况
[] == ![]; // true
根据 ToBoolean 规则,它会进行布尔值的显式强制类型转换。所以 [] == ![]
👉 [] == false
👉 [] == 0
👉 '' == 0
👉 0 == 0
👉 true
2 == [2]; // true
'' == [null]; // true
介绍 ToNumber 时我们讲过,==
右边的值 [2]
和 [null]
会进行 ToPrimitive 强制类型转换。因为数组的 valueOf()
返回数组本身,所以强制类型转换过程中数组会进行字符串化,以便能够和左边的基本类型值进行比较。
第一行中的 [2]
会转换为 "2"
,然后通过 ToNumber 转换为 2
。第二行中的 [null]
会直接转换为 ""
。所以最后的结果就是 2 == 2
和 "" == ""
。
赋值操作符
=
+=
-=
*=
/=
%=
>>=
<<=
>>>=
简单赋值用等于号(=
)表示,将右手边的值赋给左手边的变量。
复合赋值使用乘性、加性或位操作符后跟等于号(=
)表示。
num += n; // 等价于 num = num + n;
num += n + 1; // 等价于 num = num + n + 1;
条件操作符
格式:variable = boolean_expression ? true_value : false_value;
let max = num1 > num2 ? num1 : num2;
上例表示:如果 num1
大于 num2
(条件表达式为 true
),则将 num1
赋给 max
;否则,将 num2
赋给 max
。
指数操作符
ES7 新增的指数操作符 **
:数值 ** 次方数
,相当于 Math.pow(数值, 次方数)
。
2 ** 2; // 2 的 2 次方 = 4
2 ** 3; // 2 的 3 次方 = 4
指数操作符可以与赋值操作符 =
结合,形成一个新的赋值操作符 **=
:
a **= 2; // 等同于 a = a ** 2;
b **= 3; // 等同于 b = b ** 3;
逗号操作符
逗号操作符可以用来在一条语句中执行多个操作,如下所示:
const num1 = 1,
num2 = 2,
num3 = 3;
在一条语句中同时声明多个变量是逗号操作符最常用的场景。不过,也可以使用逗号操作符来辅助赋值。在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值:
const num = (5, 1, 4, 8, 0); // num 的值为 0
在这个例子中,num 将被赋值为 0,因为 0 是表达式中最后一项。逗号操作符的这种使用场景并不多见,但这种行为的确存在。
位操作符
位操作符用于操作二进制数据。它们对操作数中的每个位进行操作,并生成新的二进制结果。
- 按位与(
&
):当两个位都为 1 时,结果位为 1;否则为 0。
const a = 5; // 二进制表示为 0101
const b = 3; // 二进制表示为 0011
const result = a & b; // 二进制 0001,结果为 1
console.log(result); // 1
- 按位或(
|
):当两个位中至少有一个位为 1 时,结果位为 1;否则为 0。
const a = 5; // 二进制表示为 0101
const b = 3; // 二进制表示为 0011
const result = a | b; // 二进制 0111,结果为 7
console.log(result); // 7
- 按位异或(
^
):当两个位不相同时,结果位为 1;否则为 0。
const a = 5; // 二进制表示为 0101
const b = 3; // 二进制表示为 0011
const result = a ^ b; // 二进制 0110,结果为 6
console.log(result); // 6
- 按位非(
~
):将每个位取反。对于有符号整数,按位非运算将其转换为补码形式。
const a = 5; // 二进制表示为 0000 0000 0000 0101
const result = ~a; // 二进制 1111 1111 1111 1010,结果为 -6
console.log(result); // -6
- 左移(
<<
):将操作数的二进制表示向左移动指定的位数。左移时,右侧空出的位用 0 填充。
const a = 5; // 二进制表示为 0000 0000 0000 0101
const result = a << 2; // 二进制 0000 0000 0001 0100,结果为 20
console.log(result); // 20
- 右移(
>>
):将操作数的二进制表示向右移动指定的位数。右移时,左侧空出的位根据操作数的符号进行填充。
const a = -10; // 二进制表示为 1111 1111 1111 0110
const result = a >> 2; // 二进制 1111 1111 1111 1101,结果为 -3
console.log(result); // -3
- 无符号右移(
>>>
):将操作数的二进制表示向右移动指定的位数。右移时,左侧空出的位用 0 填充。
const a = -10; // 二进制表示为 1111 1111 1111 0110
const result = a >>> 2; // 二进制 0011 1111 1111 1101,结果为 1073741821
console.log(result); // 1073741821
运算符优先级
以下是一些常见运算符按照优先级从高到低的顺序:
- 小括号
()
:括号用于分组运算,具有最高的优先级。 - 指数运算符
**
:指数运算符用于进行指数运算。 - 一元运算符:包括逻辑非
!
、正号+
、负号-
、位非~
等。 - 乘法、除法和取模运算符:
*
、/
和%
。 - 加法和减法运算符:
+
和-
。 - 位移运算符:左移
<<
、右移>>
和无符号右移>>>
。 - 关系运算符:大于
>
、小于<
、大于等于>=
、小于等于<=
、instanceof
、in
等。 - 相等性运算符:严格相等
===
、严格不等!==
、抽象相等==
、抽象不等!=
。 - 位运算符:按位与
&
、按位或|
、按位异或^
。 - 逻辑运算符:逻辑与
&&
和逻辑或||
。 - 条件运算符:条件运算符
condition ? valueIfTrue : valueIfFalse
。 - 赋值运算符:赋值
=
、加等于+=
、减等于-=
、乘等于*=
等。