JavaScript高阶副本:“==”隐藏下的类型转换

7次阅读

共计 6545 个字符,预计需要花费 17 分钟才能阅读完成。

抛砖引玉
按照正常的逻辑来说,我们判断两个值是否相等会遵循以下规则:但是我看下面一组值:
[]==0 //true
[]==false //true
[]==!{} //true
[10]==10 //true
‘0’==false //true
”==0 //true
undefined==null //true
!null==true //true
居然没有按照我们的剧本走,那它比较规则又是什么?下面我就来分析一波。
“==”的比较规则
首先我们先去 ECMAScript5.1 中文版(http://lzw.me/pages/ecmascrip…)找一下“==”的比较规则,如下:
1. 若 Type(x) 与 Type(y) 相同,则
a. 若 Type(x) 为 Undefined,返回 true。
b. 若 Type(x) 为 Null,返回 true。
c. 若 Type(x) 为 Number,则
i. 若 x 为 NaN,返回 false。
ii. 若 y 为 NaN,返回 false。
iii. 若 x 与 y 为相等数值,返回 true。
iv. 若 x 为 +0 且 y 为−0,返回 true。
v. 若 x 为 −0 且 y 为 +0,返回 true。
vi 返回 false。
d. 若 Type(x) 为 String, 则当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。否则,返回 false。
e. 若 Type(x) 为 Boolean, 当 x 和 y 为同为 true 或者同为 false 时返回 true。否则,返回 false。
f. 当 x 和 y 为引用同一对象时返回 true。否则,返回 false。
2. 若 x 为 null 且 y 为 undefined,返回 true。
3. 若 x 为 undefined 且 y 为 null,返回 true。
4. 若 Type(x) 为 Number 且 Type(y) 为 String,返回 comparison x == ToNumber(y) 的结果。
5. 若 Type(x) 为 String 且 Type(y) 为 Number,返回比较 ToNumber(x) == y 的结果。
6. 若 Type(x) 为 Boolean,返回比较 ToNumber(x) == y 的结果。
7. 若 Type(y) 为 Boolean,返回比较 x == ToNumber(y) 的结果。
8. 若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果。
9. 若 Type(x) 为 Object 且 Type(y) 为 String 或 Number,返回比较 ToPrimitive(x) == y 的结果。
10. 返回 false
看完 ECMAScript5.1 中文版的介绍之后,相信很多小伙伴的心情应该是这样的:别看上面说的有点花里胡哨的,其实我们可以用很简单的话来总结出来。由于本篇文章核心是“==”是如何进行类型转换,我就总结一下类型不同的情况下“==”是如何比较的。

当数据类型为 Boolean 类型或者 String 类型时,比较时需要转换成 Number 类型。
当数据类型为引用类型时,需要转换成原始数据类型。当转换后的原始数据类型为 Boolean 类型或者 String 类型,则继续转换成 Number 类型。
undefined 和 null 跟任何值在“==”下都返回 false,但二者在“==”下返回 true。

具体流程图如下:

备注:
Javascript 的数据类型可以分为以下两种:

原始数据类型(null、undefined、Number、String、Boolean、Symbol(Es6 才引入的))
引用类型 (Object)

Boolean 类型、String 类型转换成 Number 类型的规则(ToNumber)
Boolean 类型

Boolean
Number

true
1

false
0

String 类型
标准的数字格式
如果是标准的数字格式,转换成 Number 类型相比不用多说,比如下面这几个栗子????:
“123” => 123
“12.34” => 12.34
“0.12” => 0.12
“-12.34” => -12.34
非标准的数字格式
但是如果是非标准的数据格式,要分两种情况来考虑:

第一种:只包含数字并且最多只有 1 个点。
第二种:包含非数字以及含有多个 1 个点。

只包含数字并且最多只有 1 个点
这种情况下会首先进行补 0 和去 0 的操作,下面看几个栗子????:
“01234” => 1234
“.1” => 0.1
“12.” => 12
“1.000” => 1
“-02.30” => -2.3
包含非数字以及含有多个 1 个点
这种情况下统统返回 NaN,意为“Not a Number”, 这里我们要注意一下,NaN 还是 Number 类型,下面看几个栗子????:
“123aa4” => NaN
“ 哈喽,DD” => NaN
typeof NaN => “numer”
引用类型转换成原始数据类型的规则(ToPrimitive)
在介绍转换规则之前,首先我们我们介绍一下引用类型都带有的两个方法:

Object.prototype.toString
Object.prototype.valueOf

这二者都可以将引用类型转换成原始数据类型,接下来我们对二者做一个详细的介绍:
Object.prototype.toString
MDN 是这样解释的:
The toString() method returns a string representing the object.(toString() 这个方法返回一个代表这个对象的字符串)
举个栗子????:
const obj = {}
console.log(String(obj)) //”[object Object]”
obj.toString = function(){
return ‘Hello,Teacher Cang!’
}
console.log(String(obj)) //”Hello,Teacher Cang!”
Object.prototype.valueOf
MDN 是这样解释的:
The valueOf() method returns the primitive value of the specified object.(valueOf() 这个方法返回这个对象的原始数据值)
举个栗子????:
const obj = {}
console.log(Number(obj)) //NaN
obj.valueOf = function(){
return 12345;
}
console.log(Number(obj)) //12345
valueOf 和 toString 的优先级
关于这二者的优先级,在不同的情况下有着不同的优先级,下面我们根据不同情况介绍一下。
ToNumber
看个栗子????:
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return ‘Hello,Teacher Cang!’;
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return 12345;
}
}
console.log(Number(obj));

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 valueOf
12345
通过上面的代码的运行结果,我们得出这么一个结论:
在 ToNumber 情况下,valueOf 的优先级大于 toString。
接下里我们看这么一种情况,如果 valueOf 返回的并不是原始数据类型会怎么样。
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return 12345;
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return {};
}
}
console.log(Number(obj));

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 valueOf
调用了 toString
12345
从上面的运行结果中,我们可以得出这么一个结论:
在 ToNumber 情况下,如果 valueOf 返回的不是原始数据类型,则会自动调用 toString。
打破砂锅问到底,再来一个非常极端的情况,如果说 valueOf 和 toString 返回的都不是原始数据类型,这时又该怎么样呢?同样,我们继续看一下运行结果:
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return {};
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return {};
}
}
console.log(Number(obj));

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 valueOf
调用了 toString
Uncaught TypeError: Cannot convert object to primitive value
从上面的运行结果中,我们再次可以得出这么一个结论:
在 ToNumber 情况下,如果 valueOf 和 toString 返回的都不是原始数据类型,那么 js 会抛出异常,提示无法将引用类型转换原始数据类型。
根据三组代码的运行的结果,我们最后总结一下:
在 ToNumber 情况下,js 会优先调用 valueOf,如果 valueOf 返回的不是原始数据类型,则会接着调用 toString,如果 toString 返回的也不是原始数据类型,js 会抛出一个异常,提示无法将引用类型转换原始数据类型。
具体流程图如下:

ToString
看个栗子????:
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return ‘Hello,Teacher Cang!’;
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return 12345;
}
}
console.log(String(obj));

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 toString
Hello,Teacher Cang!
通过上面的代码的运行结果,我们得出这么一个结论:
在 ToString 情况下,toString 的优先级大于 valueOf。
同样我们看一下,toString 返回的值不是原始数据类型时会怎样:
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return {};
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return ‘Hello,Teacher Cang!’;
}
}
console.log(String(obj));

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 toString
调用了 valueOf
Hello,Teacher Cang!
根据运行结果我们可以得出:
在 ToString 情况下,如果 toString 返回的不是原始数据类型,则会自动调用 valueOf。
最后我们看一下极端情况,二者返回的都不是原始数据类型:
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return {};
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return {};
}
}
console.log(String(obj));

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 toString
调用了 valueOf
Uncaught TypeError: Cannot convert object to primitive value
我们又发现:
在 ToString 情况下,如果 toString 和 valueOf 返回的都不是原始数据类型,那么 js 会抛出异常,提示无法将引用类型转换原始数据类型。
我们将三个结论综合一下:
在 ToString 情况下,js 会优先调用 toString,如果 toString 返回的不是原始数据类型,则会接着调用 valueOf,如果 valueOf 返回的也不是原始数据类型,js 会抛出一个异常,提示无法将引用类型转换原始数据类型。
具体流程图如下:
“==”下的 valueOf 和 toString 的优先级
从文章前面我们总结出来“==”的比较流程来看,引用类型转换成原始数据类型之后,如果是 Sting 类型的话,还要再次转成 Number 类型,因此“==”下的引用类型转换原始数据类型过程中,遵循 ToNumber 的优先级规则。
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return ‘Hello,Teacher Cang!’;
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return 12345;
}
}
console.log(obj==12345);

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 valueOf
true
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return 12345;
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return {};
}
}
console.log(obj==12345);

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 valueOf
调用了 toString
true
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return {};
},
valueOf(){
console.log(‘ 调用了 valueOf’);
return {};
}
}
console.log(obj==12345);

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 toString
调用了 valueOf
Uncaught TypeError: Cannot convert object to primitive value
“==”之外的类型转换
“==”号只涉及到了 ToPrimitive 和 ToNumber 这两种转换,ToBoolean 和 ToString 这两个没有涉及到的我们也介绍一下。
ToBoolean
对于 ToBoolean,我们只需要记住几个特例是转成 false 的,其余的皆为 true。
Boolean(”) => false
Boolean(undefined) => false
Boolean(null) => false
Boolean(0) => false
ToString
Number to String
Number 转 String 之前,首先会做一个去 0 和补 0 的操作,然后再去转成 String 类型。
String(1.234) => “1.234”
String(NaN) => “NaN”
String(.1234) => “0.1234”
String(1.23400) => “1.234”
Boolean to String
String(true) => “true”
String(false) => “false”
null 和 undefined to String
String(null) => “null”
String(undefined) => “undefined”
引用类型 to String
引用类型先 ToPrimitive 转换成原始数据类型,若转换后的原始数据类型不是 String 类型,再做 String 类型的转换。
const obj = {
toString(){
console.log(‘ 调用了 toString’);
return true;
}
}
console.log(String(obj))

控制台输出结果:
>>>>>>>>>>>>>>>>>>
调用了 toString
“true”
总结
“==”下的类型转换,要分为两种情况来考虑。第一种,原始数据类型;第二种,引用类型。原始数据类型中 String 类型和 Boolean 类型是需要转换成 Number 类型再去比较的,而引用类型则需要先转换成原始数据类型再进行后续的转换。搞清整个流程之后,我们再去理解“==”涉及到的 ToNumber 和 ToPrimitive 是如何进行转换的。按照这样子的理解步骤走,我们对“==”隐藏下的类型转换会有比较清晰的认识。另外,“==”不涉及到 ToString 和 ToBoolean 的类型转换,在文章的后面部分我也加上去了,希望可以给小伙伴们一个更加全面的类型转换的认识。最后附上完整的“==”类型转换的流程图:

正文完
 0