接着上篇 【CSDN】重学JS—好有趣的类型转换(上),继续聊聊JS的类型转换
==比较和一元操作符时时类型转换
一般情况
主要参考 【精】从206个console.log()完全弄懂数据类型转换的前世今生(下)
==
大家都知道是抽象相等判断, 一元操作符是啥?就是 +
、-
、 *
、 /
、 %
== 比较时类型转换
先来点眼熟的题
[] !== [] //true
({}) == true //false
({}) == '[object Object]' //true
为啥,继续往下看。
Ecma 抽象相等比较规则
关于上篇 toPrimitive 也提到了转换规则,方便查看,这里再提一下
比较运算 x==y, 其中x和 y是值,产生true或者false。这样的比较按如下方式进行:
若Type(x)与Type(y)相同, 则
若Type(x)为Undefined, 返回true。
若Type(x)为Null, 返回true。
若Type(x)为Number, 则
若x为NaN, 返回false。
若y为NaN, 返回false。
若x与y为相等数值, 返回true。
若x 为 +0 且 y为−0, 返回true。
若x 为 −0 且 y为+0, 返回true。
返回false。
若Type(x)为String, 则当x和y为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。 否则, 返回false。
若Type(x)为Boolean, 当x和y为同为true或者同为false时返回true。 否则, 返回false。
当x和y为引用同一对象时返回true。否则,返回false。
若x为null且y为undefined, 返回true。
若x为undefined且y为null, 返回true。
若Type(x) 为 Number 且 Type(y)为String, 返回comparison x == ToNumber(y)的结果。
若Type(x) 为 String 且 Type(y)为Number,
返回比较ToNumber(x) == y的结果。
若Type(x)为Boolean, 返回比较ToNumber(x) == y的结果。
若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果。
若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。
返回false。
简要总结一下:当使用==进行比较的时候,会有以下转换规则(判断规则):
-
比较双方类型如果相同,值相等则相等,如 2 == 3肯定是为
false
的了,NaN
不等于NaN
(NaN
与任何值都不相等) -
比较的双方都为基本数据类型:
- 若是一方为
null
、undefined
,则另一方必须为null
或者undefined
才为true,也就是null == undefined
为true
或者null == null
为true,因为undefined
派生于null
- 其中一方为
String
,是的话则把String
转为Number
再来比较 - 其中一方为
Boolean
,是的话则将Boolean
转为Number
再来比较
- 若是一方为
-
比较的一方有引用类型:
- 将引用类型遵循
ToNumber
的转换形式来进行比较(实际上它的hint
是default
,也就是toPrimitive(obj, 'default')
,但是default
的转换规则和number
很像 - 两方都为引用类型,则判断它们是不是指向同一个对象
- 将引用类型遵循
具体还是参考那张呆兄大佬的图
(图来自 掘金 LinDaiDai_霖呆呆 的文章 【精】从206个console.log()完全弄懂数据类型转换的前世今生(上),注意图中有公众号,大家关注就对了,挺大的一个佬!)
判断顺序
- 1.类型相同
null
,undefined
,NaN
情况
// 注意 /为伪代码, 表示两者结果一样
1==1 //true
1==2 //false
null/undefined==3 //false
null/undefined=={} //false
null/undefined==false //false
null/undefined=='' //false
null/undefined ==[] //false
null/undefined ==Symbol(1) //false
undefined == null//true
null==null //true
undefined==undefined //true
!NaN/NaN==NaN //false
- 2.
String==Number
时,前后顺序不影响
'11'==11 //true
'011'==11 //true
11=='011' // true
' 11 '==11 //true
'11a'==11 //false
'0x11'==11 //false
'0xb'==11 //true
'false'==0 //false
'NaN'==NaN //false
- 3.
Boolean==Other
,Boolean
先转数字再作比较。 前后顺序不影响
true ==1 //true
false==0 //true
true=='' //false
true==undefined //false
false==undefined //false
false ==null //false
true=='ewrwer' //false
需要注意的是 if('ethan')
时,其实执行的是 Boolean('ethan')
/ !!'ehtan'
- 4.Object(含数组)==
String
/Number
/Symbol
=>String
==String
/Number
/Symbol
1.对象情况
String({}) === '[object Object]' //true
然后再进行 Number == String/Number/Symbol/Boolean
参考上面就行了
所以与基本类型都不相等。除了上面那个特殊字符串。
2.数组情况
String([]) === '' //true
String([2]) === '2' //true
String([2,3]) === '2,3' // true
其它情况
!
运算
!
时不执行 valueOf
/ toString
方法, 先转 boolean
值,再取反
var O = {
valueOf: function () {
console.log('valueOf')
return '1'
},
toString: function () {
console.log('toString')
return '2'
}
}
console.log(O == 1) //valueOf true
console.log(!O== 1) //false
看看几个案例
!null == !0 // true
!undefined == !0 // true
!!null == !!0) // true
!{} == {} // false
!{} == [] // true
!{} == [0] // true
分析 !{} == {}
!{} => false
,false =={}
时, 参考Boolean==Other
(基本类型) 情况,false => 0
0=={}
, 一方有对象/数组,先将对象转为字符串。{} => “[object Object]”0=="[object Object]"
, 参考Number == String
,将字符串转数字,String => NaN
0==NaN
,结果为false
.
分析 !{} == []
!{} => false
,false ==[]
, 参考Boolean==Other
(基本类型) 情况,false => 0
0==[]
, 一方有对象/数组,先将对象转为字符串。{} => ""
0==""
, 参考Number == String
,将字符串转数字,"" => 0
0==0
,结果为true
.
!{} == [0]
和 !{} == []
相同
当函数(function A(){})存在 valueOf
,取函数时(A==1), 调用的就是 A.valueOf()
(如果 valueOf
方法存在)。
总结一下,
双方为基本类型时
- 一方为
Boolean
/String
, 转Number
比较,null
,undefined
,NaN
比较特殊 - 一方存在引用类型时, 调用
ToPrimitive => valueof => toString,
两边都为基本类型时,再进行第一种情况 - 怪不得现在提倡使用
===
严格相等,不然==
水那么深,你能把握住?🐶
+、-、*、/、%的类型转换
基本类型,会把运算符两边都转为数字再计算,不做说明
isNaN
更新于 2023年07月28日15:50:42
此方法是用来是判断是不是一个非数字(NaN)。
如果 isNaN
函数的参数不是Number类型, isNaN
函数会首先尝试将这个参数转换为数值,然后才会对转换后的结果是否是NaN进行判断.
关于NaN, 当算术运算返回一个未定义的或无法表示的值时,NaN就产生了。但是,NaN并不一定用于表示某些值超出表示范围的情况。将某些不能强制转换为数值的非数值转换为数值的时候,也会得到NaN。
例如,0 除以 0 会返回NaN —— 但是其他数除以 0 则不会返回NaN。
存在引用类型时
存在引用类型, 会将运算符两边都转为数字再计算。+
号有特殊情况后面说明
[3,4]/2 //NaN
({})/2 //NaN
[1]/2 //0.5
[]/2 //0
[]*4 //0
[1]*2 //2
[2,3]*2 //NaN
[]-2 //-2
({})%2 //NaN
({})%11 //NaN
({})-({}) //NaN
({})-([]) //NaN
({})+0 //"[object Object]0"
[]+0 //"0"
上面举得例子没有完全穷尽, 但**/
**、 -
、 *
、 %
规则是一样的
存在valueOf
/toString
/[symbol.toPrimitive]
时
如果仅存在 自定义 toString
, valueOf
情况,涉及到一个toPrimitive(input, number)
的情况,先调用 valueOf
, 再toString
let b = {
valueOf(){ console.log(' V '); return {} },
toString(){ console.log(' S '); return 1 }
}
b/[2] //V S 0.5
[2]*b // V S 2
如果 valueOf
返回就即使基本类型的就不会调用 toString
了,就是那张图里提到的规则
let b = {
valueOf(){ console.log(' V '); return 1 },
toString(){ console.log(' S '); return {} }
}
b/[2] // V 0.5
[2]*b // V 2
如果有 symbol.toPrimitive
那么直接执行的 判断 if(hint === ‘number’) 的判断,,并不会执行其它方法。
记住一点: 执行 /
、 -
、 *
、 %
操作时,是先将两边都转为数字, 也是preferedType
为 Number
;
关于 +
进行加号运算时规则如下
- 两边类型均为数字时,直接运算。
- 一方存在引用类型时, 两边都转为字符串做拼接操作
true
当做数字1
处理,false
/null
当数字0
处理
2+2 //4
0x12+3 //21
true+1 //2
false+2 //2
[]+1 //"1"
({})+3 //"[object Object]3"
(function(){})+3 //"function(){}3"
undefined+1 //NaN
null+2 //2
当然 bigInt
不能和 Number
做运算。
存在 [symbol.toPrimitive]
方法时,怎么处理 +
呢
- 单个
+
使用hint==='number'
- 两边存在表达式的时候,
hint==='default'
String(x)
时,hint==='string'
;Number(x)
,hint==='number'
var O = {
[Symbol.toPrimitive] (hint) {
if (hint === 'default') {
console.log('default')
return '0'
}
if (hint === 'number') {
console.log('number')
return 1
}
if (hint === 'string') {
console.log('string')
return '2'
}
}
}
+O //number 1
O+2 //default "02"
String(O) //string "2"
Number(O) //number 1
关于toString()
与String()
toString()
可以将数据都转为字符串,但是null和undefined不可以转换
toString
被很多类型都重写了,都有不一样的转换规则,关于细节自行查看吧
【MDN】URL.toString
【MDN】String.prototype.toString
【MDN】Function.prototype.toString
【MDN】Date.prototype.toString
【MDN】Number.prototype.toString
- 数字
toString()
方法可以接受一个参数,代表进制
二进制:.toString(2)
;
八进制:.toString(8)
;
十进制:.toString(10)
;
十六进制:.toString(16)
;
【MDN】Symbol.prototype.toString
String
方法时通用的,可以将任何类型都转为字符串!
常见题型分析
[]==[] // ?
[]==![] // ?
【分析】
false, 都为引用类型,引用地址不同,不相等
true, 先转换带运算符的 ![] => false
. 这时候都转为数字做判断, false => 0
, [] => 0
console.log({} + "" * 1) // ?
console.log({} - []) // ?
console.log({} + []) // ?
console.log([2] - [] + function () {}) // ?
分析
“[object Object]0”, 相当于String({})+String(Number("")*1)
,最后字符串拼接;
NaN, 相当于Number({}) => NaN
, Number([]) => 0
, NaN - 0 => NaN
;
“[object Object]”, 相当于String({}) + String([])
;控制台输出的时候,前面{}
会被忽略,结果时0
,因为+[]
“2function(){}”, 相当于String(Number([2]) - Number([])) + String(function(){})
实现
i==1&&i==2&&i==3 //结果为true
1.重写 valueOf
方法就好了
let i = {
value: 0,
valueOf(){ console.log('加1 ', this.value); return ++this.value++; }
}
i==1&&i==2&&i==3
//加1 0 加1 1 加1 2 //true
再提示一下本文参考(抄袭, 我不要脸了,哈哈)链接
【掘金-呆呆大佬】【精】从206个console.log()完全弄懂数据类型转换的前世今生(下)
其它思路也差不多
- 把
valueOf
返回值设为非原始类型,就会调用toString
,重写toString
方法就好了 - 增加
[symbol.toPrimitive]
, 重写if(hint === 'number')
判断就好了 - 利用数组转字符串会调用join(隐式),那么一个个取出来抽像比较(==)
第三种:
let a = [1, 2, 3]
a['join'] = function () {
return a.shift() // this代替a也可
}
a==1&&a==2&&a==3 //true
当然还有方法,是第三种的扩展(利用继承数组对象原型上的 join
方法),大家自行去看。
以上。
参考
站在别人的肩膀上才能看的更远(文章大部分例子来自 前世今生 系列的两篇文章)
【考拉个人博客】面试常问javascript数据类型
【掘金】JavaScript深入之头疼的类型转换(上)
【掘金】 【精】从206个console.log()完全弄懂数据类型转换的前世今生(上)
【掘金】【精】从206个console.log()完全弄懂数据类型转换的前世今生(下)
【思否】 揭秘‘==’下隐藏的类型转换