JavaScript高阶教程之“==”隐藏下的类型转换


Posted in Javascript onApril 11, 2019

抛砖引玉

按照正常的逻辑来说,我们判断两个值是否相等会遵循以下规则:

JavaScript高阶教程之“==”隐藏下的类型转换

但是我看下面一组值:

[]==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中文版的介绍之后,相信很多小伙伴的心情应该是这样的:

JavaScript高阶教程之“==”隐藏下的类型转换

别看上面说的有点花里胡哨的,其实我们可以用很简单的话来总结出来。由于本篇文章核心是“==”是如何进行类型转换,我就总结一下类型不同的情况下“==”是如何比较的。

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

具体流程图如下:

JavaScript高阶教程之“==”隐藏下的类型转换

备注:

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会抛出一个异常,提示无法将引用类型转换原始数据类型。

具体流程图如下:

JavaScript高阶教程之“==”隐藏下的类型转换

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会抛出一个异常,提示无法将引用类型转换原始数据类型。

具体流程图如下:

JavaScript高阶教程之“==”隐藏下的类型转换

“==”下的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的类型转换,在文章的后面部分我也加上去了,希望可以给小伙伴们一个更加全面的类型转换的认识。最后附上完整的“==”类型转换的流程图:

JavaScript高阶教程之“==”隐藏下的类型转换

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
js判断运行jsp页面的浏览器类型以及版本示例
Oct 30 Javascript
js判断一个字符串是否包含一个子串的方法
Jan 26 Javascript
JAVA四种基本排序方法实例总结
Jul 24 Javascript
jquery用ajax方式从后台获取json数据后如何将内容填充到下拉列表
Aug 26 Javascript
JavaScript实现动态添加Form表单元素的方法示例
Aug 14 Javascript
Vue实现typeahead组件功能(非常靠谱)
Aug 26 Javascript
Mac下安装vue
Apr 11 Javascript
node.js部署之启动后台运行forever的方法
May 23 Javascript
微信小程序实现的picker多级联动功能示例
May 23 Javascript
vue element upload实现图片本地预览
Aug 20 Javascript
js脚本中执行java后台代码方法解析
Oct 11 Javascript
node.js使用fs读取文件出错的解决方案
Oct 23 Javascript
使用Vue父子组件通信实现todolist的功能示例代码
Apr 11 #Javascript
详解jQuery设置内容和属性
Apr 11 #jQuery
js作用域和作用域链及预解析
Apr 11 #Javascript
关于js陀螺仪的理解分析
Apr 11 #Javascript
angular 表单验证器验证的同时限制输入的实现
Apr 11 #Javascript
angular 实现同步验证器跨字段验证的方法
Apr 11 #Javascript
vue实现新闻展示页的步骤详解
Apr 11 #Javascript
You might like
php daddslashes()和 saddslashes()有哪些区别分析
2012/10/26 PHP
使用PHP编写发红包程序
2015/07/22 PHP
取得一定长度的内容,处理中文
2006/12/20 Javascript
jQuery DOM操作小结与实例
2010/01/07 Javascript
Jquery CheckBox全选方法代码附js checkbox全选反选代码
2010/06/09 Javascript
JS判断元素为数字的奇异写法分享
2012/08/01 Javascript
javascript当中的代码嗅探扩展原生对象和原型(prototype)
2013/01/11 Javascript
浅析Cookie中的Path与domain
2013/12/18 Javascript
JavaScript原型链示例分享
2014/01/26 Javascript
node.js中的fs.unlink方法使用说明
2014/12/15 Javascript
node.js中的forEach()是同步还是异步呢
2015/01/29 Javascript
js实现同一页面可多次调用的图片幻灯切换效果
2015/02/28 Javascript
Bootstrap轮播插件中图片变形的终极解决方案 使用jqthumb.js
2016/07/10 Javascript
bootstrap输入框组使用方法
2017/02/07 Javascript
element-ui表格合并span-method的实现方法
2019/05/21 Javascript
深入了解JS之作用域和闭包
2020/06/16 Javascript
[01:24:09]Ti4 冒泡赛第二轮DK vs C9 1
2014/07/14 DOTA
[36:22]VP vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
[01:07:46]完美世界DOTA2联赛循环赛 Magma vs IO BO2第二场 11.01
2020/11/02 DOTA
[57:12]完美世界DOTA2联赛循环赛 Inki vs Matador BO2第一场 10.31
2020/11/02 DOTA
python中关于日期时间处理的问答集锦
2013/03/08 Python
Python:Scrapy框架中Item Pipeline组件使用详解
2017/12/27 Python
python基础教程项目二之画幅好画
2018/04/02 Python
《与孩子一起学编程》python自测题
2018/05/27 Python
详解安装mitmproxy以及遇到的坑和简单用法
2019/01/21 Python
在Qt5和PyQt5中设置支持高分辨率屏幕自适应的方法
2019/06/18 Python
django-csrf使用和禁用方式
2020/03/13 Python
VSCode基础使用与VSCode调试python程序入门的图文教程
2020/03/30 Python
Python 实现一行输入多个数字(用空格隔开)
2020/04/29 Python
解决pyinstaller 打包exe文件太大,用pipenv 缩小exe的问题
2020/07/13 Python
《九色鹿》教学反思
2014/02/27 职场文书
保险公司增员口号
2015/12/25 职场文书
《西游记》读后感(3篇)
2019/09/20 职场文书
自制短波长线天线频率预选器 - 成功消除B2K之流的镜像
2021/04/22 无线电
前端监听websocket消息并实时弹出(实例代码)
2021/11/27 Javascript
Java的Object类的九种方法
2022/04/13 Java/Android