JavaScript的==运算详解


Posted in Javascript onJuly 20, 2016

大家知道,JavaScript中的==是一种比较复杂运算,它的运算规则很奇怪,很容易让人犯错,从而成为JavaScript中“最糟糕的特性”之一。

在仔细阅读ECMAScript规范的基础上,我画了一张图,我想等你理解了这张图后,会彻底地弄懂关于==运算的一切。同时,我试图通过此文向大家证明==并不是那么糟糕的东西,它很容易掌握,甚至看起来很合理,并没那么糟糕。

先上图:

JavaScript的==运算详解

==运算规则的精确描述在此:The Abstract Equality Comparison Algorithm。但是,这么复杂的描述,你确定看完后脑子不晕?确定立马就能拿它指导实践?

肯定不行,规范毕竟是给JavaScript运行环境的开发人员看的(比较V8引擎的开发人员们),而不是给语言的使用者看的。而上图正是将规范翻译成了方便大家看的形式。

在详细介绍图1中的每个部分前,我们来复习一下JS中关于类型的知识:

JS中的值有两种类型:基本类型、对象类型。
基本类型包括:Undefined、Null、Boolean、Number和String等五种类型。
Undefined类型和Null类型的都只有一个值,即undefined和null;Boolean类型有两个值:true和false;Number类型的值有很多很多;String类型的值有无数个值(理论上)。
所有对象都有valueOf()和toString()方法,它们继承自Object,当然也可能被子类重写。
现在考虑表达式:

x == y
其中x和y是六种类型中某一种类型的值。

当x和y的类型相同时,x == y可以转化为x === y,而后者是很简单的(唯一需要注意的可能是NaN),所以下面我们只考虑x和y的类型不同的情况。

一. 有和无

在图1中,JavaScript值的六种类型用蓝底色的矩形表示。首先它们被分成了两组:

String、Number、Boolean和Object (对应左侧的大矩形框)
Undefined和Null (对应右侧的矩形框)
分组的依据是什么?我们来看一下,右侧的Undefined和Null是用来表示不确定、无或者空的,而右侧的四种类型都是确定的、有和非空。我们可以这样说:

左侧是一个存在的世界,右侧是一个空的世界。
所以,左右两个世界中的任意值做==比较的结果都是false是很合理的。(即图1中连接两个矩形的水平线上标的false)

二. 空和空

JavaScript中的undefined和null是另一个经常让我们崩溃的地方。通常它被认为是一个设计缺陷,这一点我们不去深究。不过我曾听说,JavaScript的作者最初是这样想的:

假如你打算把一个变量赋予对象类型的值,但是现在还没有赋值,那么你可以用null表示此时的状态(证据之一就是typeof null 的结果是'object');相反,假如你打算把一个变量赋予原始类型的值,但是现在还没有赋值,那么你可以用undefined表示此时的状态。
不管这个传闻是否可信,它们两者做==比较的结果是true也是很合理的。(即图1中右侧垂直线上标的true)

在进行下一步之前,我们先来说一下图1中的两个符号:大写字母N和P。这两个符号不是PN节中正和负的意思。而是:

N表示ToNumber操作,即将操作数转为数字。它是ES规范中的抽象操作,但我们可以用JS中的Number()函数来等价替代。
P表示ToPrimitive操作,即将操作数转为原始类型的值。它也是ES规范中的抽象操作,它也可以翻译成等价的JS代码。不过稍微复杂一些,简单说来,对于一个对象obj:
ToPrimitive(obj)等价于:先计算obj.valueOf(),如果结果为原始值,则返回此结果;否则,计算obj.toString(),如果结果是原始值,则返回此结果;否则,抛出异常。
注:此处有个例外,即Date类型的对象,它会先调用toString()方法.

在图1中,标有N或P的线表示,当它连接的两种类型的数据做==运算时,标有N或P的那一边的操作数要先执行ToNumber或ToPrimitive变换。

三. 真与假

从图1可以看出,当布尔值与其他类型的值作比较时,布尔值会转化为数字,具体来说

true -> 1
false -> 0
这一点也不需浪费过多口舌。想一下在C语言中,根本没有布尔类型,通常用来表示逻辑真假的正是整数1和0。

四. 字符的序列

在图1中,我们把String和Number分成了一组。为什么呢?在六种类型中,String和Number都是字符的序列(至少在字面上如此)。字符串是所有合法的字符的序列,而数字可以看成是符合特定条件的字符的序列。所以,数字可以看成字符串的一个子集。

根据图1,在字符串和数字做==运算时,需要使用ToNumber操作,把字符串转化为数字。假设x是字符串,y是数字,那么:

x == y -> Number(x) == y
那么字符串转化为数字的规则是怎样的呢?规范中描述得很复杂,但是大体来说,就是把字符串两边的引号去掉,然后看看它能否组成一个合法的数字。如果是,转化结果就是这个数字;否则,结果是NaN。例如:

Number('123') // 结果123
Number('1.2e3') // 结果1200
Number('123abc') // 结果NaN
当然也有例外,比如空字符串转化为数字的结果是0。即

Number('') // 结果0

五. 单纯与复杂

原始类型是一种单纯的类型,它们直接了当、容易理解。然而缺点是表达能力有限,难以扩展,所以就有了对象。对象是属性的集合,而属性本身又可以是对象。所以对象可以被构造得任意复杂,足以表示各种各样的事物。

但是,有时候事情复杂了也不是好事。比如一篇长长的论文,并不是每个人都有时间、有耐心或有必要从头到尾读一遍,通常只了解其中心思想就够了。于是论文就有了关键字、概述。JavaScript中的对象也一样,我们需要有一种手段了解它的主要特征,于是对象就有了toString()和valueOf()方法。

toString()方法用来得到对象的一段文字描述;而valueOf()方法用来得到对象的特征值。
当然,这只是我自己的理解。另外,顾名思义,toString()方法倾向于返回一个字符串。valueOf()方法呢?根据规范中的描述,它倾向于返回一个数字——尽管内置类型中,valueOf()方法返回数字的只有Number和Date。

根据图1,当一个对象与一个非对象比较时,需要将对象转化为原始类型(虽然与布尔类型比较时,需要先将布尔类型变成数字类型,但是接下来还是要将对象类型变成原始类型)。这也是合理的,毕竟==是不严格的相等比较,我们只需要取出对象的主要特征来参与运算,次要特征放在一边就行了。

六. 万物皆数

我们回过头来看一下图1。里面标有N或P的那几条连线是没有方向的。假如我们在这些线上标上箭头,是连线从标有N或P的那一端指向另一端,那么会得到(不考虑undefined和null):

JavaScript的==运算详解

发现什么了吗?对,在运算过程中,所有类型的值都有一种向数字类型转化的趋势。毕竟曾经有名人说过:

万物皆数。

七. 勉强举个栗子

前面废话太多了,这里还是举个例子,来证明图1确实是方便有效可以指导实践的。

例,计算下面:

[''] == false
首先,两个操作数分别是对象类型和布尔类型。根据图1,需要将布尔类型转为数字类型,而false转为数字的结果是0,所以表达式变为:

[''] == 0
两个操作数变成了对象类型和数字类型。根据图1,需要将对象类型转为原始类型:

首先调用[].valueOf(),由于数组的valueOf()方法返回自身,所以结果不是原始类型,继续调用[].toString()。
对于数组来说,toString()方法的算法,是将每个元素都转为字符串类型,然后用','依次连接起来,所以最终结果是空字符串'',它是一个原始类型的值。
此时,表达式变为:

'' == 0
两个操作数变成了字符串类型和数字类型,根据图1,需要将字符串类型转为数字类型,前面说了空字符串变成数字是0。于是表达式变为:

0 == 0
到此为止,两个操作数的类型终于相同了,结果明显是true。

从这个例子可以看出,要想掌握==运算的规则,除了牢记图1外,还需要记住那些内置对象的toString()和valueOf()方法的规则。包括Object、Array、Date、Number、String、Boolean等。

八. 总结一下

前面说得很乱,在这里再总结一下图1中表达的==运算的规则:

undefined == null的结果是true。它俩与其他所有值比较的结果都是false。
字符串 == 数字时,字符串转为数字。
布尔值 == 其他类型时,布尔值转为数字。
对象 == 数字/字符串时,对象转为基本类型。
最后,把图改了一下,仅供娱乐 : )

JavaScript的==运算详解

OK,结束了。如果你觉得这篇文章对你有用,请点赞,让更多的人看到。
另外,文章中的谬误,请不吝指出。

Javascript 相关文章推荐
jQuery学习7 操作JavaScript对象和集合的函数
Feb 07 Javascript
在javascript中实现函数数组的方法
Dec 25 Javascript
学习AngularJs:Directive指令用法(完整版)
Apr 26 Javascript
微信小程序 实现tabs选项卡效果实例代码
Oct 31 Javascript
Vue响应式添加、修改数组和对象的值
Mar 20 Javascript
详解Node项目部署到云服务器上
Jul 12 Javascript
Vue封装一个简单轻量的上传文件组件的示例
Mar 21 Javascript
微信小程序时间控件picker view使用详解
Dec 28 Javascript
JS实现判断有效的数独算法示例
Feb 25 Javascript
微信小程序 高德地图路线规划实现过程详解
Aug 05 Javascript
vue+elementui 对话框取消 表单验证重置示例
Oct 29 Javascript
element-plus一个vue3.xUI框架(element-ui的3.x 版初体验)
Dec 02 Vue.js
javascript特效实现——当前时间和倒计时效果的简单实例
Jul 20 #Javascript
javascript中数组和字符串的方法对比
Jul 20 #Javascript
js简单实现图片延迟加载的方法
Jul 19 #Javascript
JS实现输入框提示文字点击时消失效果
Jul 19 #Javascript
JavaScript新增样式规则(推荐)
Jul 19 #Javascript
JavaScript动态添加css样式和script标签
Jul 19 #Javascript
javascript中使用未定义变量或值的情况分析
Jul 19 #Javascript
You might like
解析php5配置使用pdo
2013/07/03 PHP
PHP中常见的缓存技术实例分析
2015/09/23 PHP
学习thinkphp5.0验证类使用方法
2017/11/16 PHP
Laravel5.5 手动分页和自定义分页样式的简单实现
2019/10/15 PHP
Javascript代码混淆综合解决方案-Javascript在线混淆器
2006/12/18 Javascript
javascript脚本调试方法小结
2008/11/24 Javascript
Prototype 工具函数 学习
2009/07/23 Javascript
Javascript加载速度慢的解决方案
2014/03/11 Javascript
jquery ajax应用中iframe自适应高度问题解决方法
2014/04/12 Javascript
原生js实现日期联动
2015/01/12 Javascript
JavaScript编写连连看小游戏
2015/07/07 Javascript
zTree实现节点修改的实时刷新功能
2017/03/20 Javascript
Ionic项目中Native Camera的使用方法
2017/06/07 Javascript
使用JS编写的随机抽取号码的小程序
2017/08/11 Javascript
微信小程序实现时间预约功能
2018/11/27 Javascript
vue 实现 rem 布局或vw 布局的方法
2019/11/13 Javascript
uni-app如何实现增量更新功能
2020/01/03 Javascript
jQuery单页面文字搜索插件jquery.fullsearch.js的使用方法
2020/02/04 jQuery
Vue项目中使用flow做类型检测的方法
2020/03/18 Javascript
Python中的is和==比较两个对象的两种方法
2017/09/06 Python
PyQt5利用QPainter绘制各种图形的实例
2017/10/19 Python
python爬虫获取淘宝天猫商品详细参数
2020/06/23 Python
Python 从相对路径下import的方法
2018/12/04 Python
Python线上环境使用日志的及配置文件
2019/07/28 Python
浅谈多卡服务器下隐藏部分 GPU 和 TensorFlow 的显存使用设置
2020/06/30 Python
python如何求圆的面积
2020/07/01 Python
公务员个人自我评价分享
2013/11/06 职场文书
中国文明网签名寄语
2014/01/18 职场文书
甜点店创业计划书
2014/01/27 职场文书
有多年工作经验的自我评价
2014/03/02 职场文书
小学六年级学生评语
2014/04/22 职场文书
学生会主席演讲稿
2014/04/25 职场文书
警示教育活动总结
2014/05/05 职场文书
俞敏洪一分钟演讲稿
2014/08/26 职场文书
社团个人总结范文
2015/03/05 职场文书
解决MySQL Varchar 类型尾部空格的问题
2022/04/06 MySQL