重学JS|好有趣的类型转换(下)

接着上篇 【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。

简要总结一下:当使用==进行比较的时候,会有以下转换规则(判断规则):

  1. 比较双方类型如果相同,值相等则相等,如 2 == 3肯定是为 false 的了, NaN 不等于 NaNNaN与任何值都不相等)

  2. 比较的双方都为基本数据类型:

    • 若是一方为nullundefined,则另一方必须为 null 或者 undefined 才为true,也就是null == undefinedtrue 或者null == null为true,因为 undefined 派生于 null
    • 其中一方为 String,是的话则把 String 转为 Number 再来比较
    • 其中一方为 Boolean ,是的话则将 Boolean 转为 Number 再来比较
  3. 比较的一方有引用类型:

    • 将引用类型遵循ToNumber 的转换形式来进行比较(实际上它的hintdefault,也就是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

分析 !{} == {}

  1. !{} => false,
  2. false =={}时, 参考 Boolean==Other(基本类型) 情况,false => 0
  3. 0=={}, 一方有对象/数组,先将对象转为字符串。{} => “[object Object]”
  4. 0=="[object Object]", 参考 Number == String,将字符串转数字, String => NaN
  5. 0==NaN ,结果为 false.

分析 !{} == []

  1. !{} => false,
  2. false ==[], 参考 Boolean==Other(基本类型) 情况,false => 0
  3. 0==[], 一方有对象/数组,先将对象转为字符串。{} => ""
  4. 0=="", 参考 Number == String,将字符串转数字, "" => 0
  5. 0==0 ,结果为 true.

!{} == [0]!{} == []相同

当函数(function A(){})存在 valueOf,取函数时(A==1), 调用的就是 A.valueOf()(如果 valueOf 方法存在)。

总结一下
双方为基本类型时

  1. 一方为Boolean / String, 转 Number 比较,null, undefined , NaN 比较特殊
  2. 一方存在引用类型时, 调用 ToPrimitive => valueof => toString, 两边都为基本类型时,再进行第一种情况
  3. 怪不得现在提倡使用 === 严格相等,不然 == 水那么深,你能把握住?🐶

+、-、*、/、%的类型转换

基本类型,会把运算符两边都转为数字再计算,不做说明

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’) 的判断,,并不会执行其它方法。

记住一点: 执行 /-*% 操作时,是先将两边都转为数字, 也是preferedTypeNumber;

关于 +

进行加号运算时规则如下

  • 两边类型均为数字时,直接运算。
  • 一方存在引用类型时, 两边都转为字符串做拼接操作
  • 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()完全弄懂数据类型转换的前世今生(下)
【思否】 揭秘‘==’下隐藏的类型转换

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值