操作符
运算符优先级

等于
===
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=trueObject类型只有引用一致才相等
- 如果都是
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';