操作符
运算符优先级
等于
===
x === y
:严格相等
严格相等运算符最大的特点是不会进行隐式的类型转换,它是直接基于类型相等的情况才会进行下去比较过程,类型不等直接就false
。
根据 ES 规范文档的描述,严格相等运算符的规则如下:
- 两边类型不一样,直接返回
false
; - 如果左边是
Number
或者BigInt
类型,那就执行Number::equal(x, y)
这个操作,如下- 如果 x 或者 y 是
NaN
,则返回false
; - 如果 x 或者 y 是 0,不论正负,返回
true
- 如果 x 和 y 是精确的数值(包括无理数 Π)且相等,则返回
true
- 如果 x 或者 y 是
- 剩余类型比较如下:
undefined
只和undefined
相等null
只和null
相等- Boolean,两边都是
true
或者都是false
- Symbol,仅当在使用
Symbol.for(key)
创建的两个Symbol
值时才会相等 - String,仅当两个字符串在相应的索引处具有相同的长度和相同的字符时才相等
- Object,仅当引用同一对象才相等
==
当两边比较的值是同种类型时,==
的行为和严格相等===
一致;但是两边类型不等时,==
有时候会进行一些隐式的类型转换。
根据 ES 规范文档的描述,其比较规则也是相当复杂,需要注意的是以下步骤是从上到下依次判断的:
如果两边类型相同,返回使用
===
比较的结果;undefined == null
;如果等号一侧是
Number
或BigInt
,另一边是String
,则String
需进行 ToNumber/StringToBigInt 转换,重复上述步骤进行比较;NaN
不和任何值相等,返回false
;如果等号一侧是
Boolean
类型,将布尔值进行ToNumber
转换,转成 1 或者 0,然后重复上面步骤进行比较;如果等号一侧是原始值类型
String, Number, BigInt, or Symbol
,而另一侧是Object
,需要将Object
进行ToPrimitive(hint number)
转换,然后重复上面步骤进行比较;如果是
BigInt
和Number
比较,一边出现正负无穷±∞
或NaN
,则返回false
;否则两边值相等则返回true
不符合上面比较情况的返回
false
// 1. false需要进行ToBoolea转换,变成比较 " " == 0
// 2. 空白字符串" "进行ToNumber转换,得到 0,变成比较 0 == 0
// 3. 两边类型相同,比较 0 === 0
" " == false
// 1. true进行ToNumber转换,变成比较1 == "1"
// 2. 字符串"1"进行ToNumber转换,得到 1
// 3. 最后变成1 === 1
true == "1" => true
// 1. Boolean转Number,变成比较 1 == {}
// 2. Object进行ToPrimitive(hint number)转换,也就是ToNumber,先获取valueOf得到的不是原始值,再尝试获取toString,得到字符串"[object, Object]",变成比较 1 == "[object, Object]"
// 3. "[object, Object]"转Number,得到NaN,变成比较 1 == NaN
true == {} => false
// 1. fasle转Number,变成比较 0 == undefined
// 2. 不符合上述任何条件比较,直接最后返回false
false == undefined => false
// 1. fasle转Number,变成比较 0 == []
// 2. []进行ToPrimitive(hint number)转换,尝试获取valueOf实际执行ToObject,得到原值[];再次尝试toString,实际内部执行[].join(","),得到空字符串,变成比较 0 == ""
// 3. 空字符串ToNumber,得到0,最后就是比较 0 === 0
false == [] => true
false == "0" => true
false == "" => true
"" == 0 => true
"" == [] => true
[] == 0 => true
// [null]转原始值得到空字符串
"" == [null] => true
// 换行符转number也是看作 0
0 == "\n" => true
Object.is
Object.is(x, y)
的判断过程不涉及任何类型转换;
x
和y
类型不相同,直接返回false
;x
和y
类型相同,- 如果都是
Number
或者BigInt
类型,只要两边长得不一样就返回false
,如果都是NaN
或±0
则返回true
; undefined
=undefined
;null
=null
;true
=true
Object
类型只有引用一致才相等
- 如果都是
Object.is(x, y)
和严格相等===
唯一的区别就是处理NaN
这个值
console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN); // false
console.log(Object.is(null, undefined)); // false
console.log(null === undefined); // false
加号
二元 +
二元相加运算符只会进行两种运算,要么是字符串连接,要么是数字相加,根据 ES 规范文档的描述,相加运算符是有明确规则的:
- 如果加号一边有
Symbol
类型对象,直接TypeError
; - 将
+
两边操作数都进行ToPrimitive
转换,得到原始值;- !!!这点尤其重要,此时的
ToPrimitive
转换并不会像 ToNumber 和 ToString 传递显式的 hint,默认是除了Date
类型外都默认进行ToPrimitive(hint number)
转换,Date
类型是ToPrimitive(hint string)
转换;
- !!!这点尤其重要,此时的
- 对原始值类型再进行判断,如果一边有
String
类型,则都进行ToString
转换,然后进行字符串连接; - 否则就都进行
ToNumber
转换,然后判断两边类型是否相同,如果不相同则TypeError
;相同则相加运算返回结果
// 1. ToPrimitive,都是原始值类型,所以直接返回;
// 2. 右边是String,则都ToString
// 3. 最后字符串连接
1 + '1' = '11';
// 1. ToPrimitive,都是原始值类型,所以直接返回;
// 2. 两边没有String类型,都进行ToNumber
// 3. null进行ToNumber,按照规范表中的指示,得到+0
// 4. 两边相加
1 + null = 1;
// undefined进行ToNumber,按照规范表中的指示,得到NaN
// NaN和任何数运算都是NaN
1 + undefined = NaN;
// 1. 左边ToPrimitive,直接返回
// 2. 右边ToPrimitive(number),也就是ToNumber,
// 3. 先尝试获取valueOf,返回原值 {},非原始值类型,
// 4. 继续尝试toString,直接调用Object.prototype.toString,返回字符串[object, Object]
// 5. 右边原始值得到字符串[object, Object],所以左边ToString
// 6. 最后做字符串连接
1 + {} = '1[object, Object]';
// 1. 右边ToPrimitive(number),也就是ToNumber,
// 2. 尝试获取valueOf,返回原值[],非原始值类型
// 3. 再次尝试toString,Array重写了toString,相当于调用join,则返回空字符串""
// 4. 左边ToString
// 5. 最后字符串连接
1 + [] = '1';
// 1. 右边ToPrimitive(string),优先调用Date.prototype[@@toPrimitive],返回一大串时间字符串
// 2. 左边ToString
// 3. 字符串连接
1 + new Date() = '1Mon Jul 27 2020 00:37:07 GMT+0800 (中国标准时间)';
判断下面函数结果
function sum(a, b) {
return a + b;
}
sum(1, '2'); // "12"
一元 +
一元加运算符,只会进行ToNumber
转换
console.log(+''); // 0
console.log(+'1212e0'); // 1212
console.log(+'12.3'); // 12.3
console.log(+'.89'); // 0.89
console.log(+'2.5²'); // NaN
console.log(+'2.5e8'); // 250000000
console.log(+'0b0110'); // 6
console.log(+'0o1244'); // 676
console.log(+'0xFFFF'); // 65535
console.log(+'0o1244.8'); // NaN 除了十进制,其它进制一概不允许出现小数点
console.log(+'1121afgrt'); // NaN 数字字母混合,直接NaN
"b"+"a"+ +"a"+"a"
无论什么时候,+
都是从左往右按顺序执行的,"b"+"a"+ +"a"+"a"
的执行过程如下:
"b"+"a"
:"ba"
"ba"+ (+"a")
:"baNaN"
"baNaN"+"a"
:"baNaNa"
console.log(('b' + 'a' + +'a' + 'a').toLowerCase());
// banana
逻辑运算符
!
根据 ES 规范文档的描述,逻辑非运算符规则如下:
- 对操作数执行ToBoolean转换
- 如果转换结果是 true,则返回 false
- 返回 true
类型 | 结果 |
---|---|
Undefined | false |
Null | false |
Number | ±0,NaN => false 其它都是 true |
String | 空字符串 => false 其它都是 true |
Symbol | true |
BigInt | 0n => false 其它都是 true |
Object | true |
&&和||
从 ES 规范文档的描述来看,&&
和||
主要是评估左侧表达式的值来决定输出结果。他们两个的共同特点是产生的值将始终是两个操作数表达式之一的值,并不一定都是Boolean
类型的。
对于a && b
,记为左侧为假就返回左侧:
首先将左侧
a
进行ToBoolean转换得到lbool
;如果
lbool
是false
,那么就返回a
,注意是返回a
;否则,就返回
b
对于a || b
,记为左侧为真就返回左侧:
首先将左侧
a
进行ToBoolean转换得到lbool
;如果
lbool
是true
,那么就返回a
,注意是返回a
;否则,就返回
b
console.log('cat' && 'dog'); // "dog"
console.log('' && 'dog'); // ""
console.log('cat' || 'dog'); // "cat"
console.log('' || 'dog'); // "dog"
还有一点是&&
的优先级高于||
console.log(true || (false && false)); // true
console.log((true || false) && false); // false
逻辑或||
是左侧为真则返回左侧;逻辑与&&
是左侧为真则返回右侧;但是 ES 规范定义的经过ToBoolean可以转换成false
的值有七个:
false
空字符串''
null
undefined
±0
NaN
?:
?:
:条件运算符,或者叫三元运算符
位于?
左侧的变量会先进行隐式类型转换成boolean
类型,然后再判断其是否为true
,因此要注意以下假值:
±0
NaN
null
undefined
''
false
?.
?.
:可选链操作符,ES2020 支持,目前也还是stage 4
的状态
主要用于对象获取属性,避免从undefined
上获取属性时报错的问题,当左侧引用的对象为null
或者undefined
的时候会直接返回,避免报错。在这个符号之前,你需要使用&&
来判断对象
obj && obj.first && obj.first.second;
obj?.first?.second;
??
??
:空值合并操作符,ES2020 支持
??
,空值合并表达式,其也是通过评估左侧表达式的值来决定输出结果的,但是??
不涉及任何类型转换,其规则用一句话总结就是:
当且仅当左侧为null
或undefined
的时候,才返回左侧的值(也就是返回null
或undefined
),其余情况都是返回右侧的值
console.log(null ?? ''); // ""
这个操作符最常用的就是和?.
一起使用,当?.
返回undefined
或者null
的时候,再使用??
补上一个恰当的值来保证整个运算的完整。
let obj: Object | null = {
a: 'oxygen',
};
const a = obj?.a ?? 'oxygen';