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 相关文章推荐
Bookmarklet实现启动jQuery(模仿 云输入法)
Sep 15 Javascript
onclick与listeners的执行先后问题详细解剖
Jan 07 Javascript
js类定义函数时用prototype与不用的区别示例介绍
Jun 10 Javascript
javascript cookie的简单应用
Feb 24 Javascript
JS跨域解决方案之使用CORS实现跨域
Apr 14 Javascript
javascript实现table单元格点击展开隐藏效果(实例代码)
Apr 10 Javascript
详解vue中computed 和 watch的异同
Jun 30 Javascript
浅谈js的解析顺序 作用域 严格模式
Oct 23 Javascript
详解Vue源码学习之callHook钩子函数
Jul 25 Javascript
vuex根据不同的用户权限展示不同的路由列表功能
Sep 20 Javascript
vue设置一开始进入的页面教程
Oct 28 Javascript
JS常用跨域方法实现原理解析
Dec 09 Javascript
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
20个2014年最优秀的PHP框架回顾
2014/10/22 PHP
解决nginx不支持thinkphp中pathinfo的问题
2015/07/21 PHP
PHPExcel笔记, mpdf导出
2016/05/03 PHP
微信公众号之主动给用户发送消息功能
2019/06/22 PHP
javascript 动态添加事件代码
2008/11/30 Javascript
jQuery移动和复制dom节点实用DOM操作案例
2012/12/17 Javascript
asm.js使用示例代码
2013/11/28 Javascript
JS获得QQ号码的昵称,头像,生日的简单实例
2013/12/04 Javascript
js与运算符和或运算符的妙用
2014/02/14 Javascript
js生成动态表格并为每个单元格添加单击事件的方法
2014/04/14 Javascript
使用jQuery中的when实现多个AJAX请求对应单个回调的例子分享
2014/04/23 Javascript
js清空form表单中的内容示例
2014/05/20 Javascript
jquery 插件实现瀑布流图片展示实例
2015/04/03 Javascript
实现前后端数据交互方法汇总
2015/04/07 Javascript
javascript判断并获取注册表中可信任站点的方法
2015/06/01 Javascript
使用impress.js制作幻灯片
2015/09/09 Javascript
JavaScript运行过程中的“预编译阶段”和“执行阶段”
2015/12/16 Javascript
jQuery获取radio选中项的值实例
2016/06/18 Javascript
vue2.x 父组件监听子组件事件并传回信息的方法
2017/07/17 Javascript
Vue2.0子同级组件之间数据交互方法
2018/02/28 Javascript
vue elementui form表单验证的实现
2018/11/11 Javascript
c++生成dll使用python调用dll的方法
2014/01/20 Python
Python第三方库的安装方法总结
2016/06/06 Python
Python2.7读取PDF文件的方法示例
2017/07/13 Python
对Pytorch神经网络初始化kaiming分布详解
2019/08/18 Python
Python替换NumPy数组中大于某个值的所有元素实例
2020/06/08 Python
Python实现弹球小游戏
2020/08/01 Python
详解如何通过H5(浏览器/WebView/其他)唤起本地app
2017/12/11 HTML / CSS
财务副总经理工作职责
2013/11/25 职场文书
反腐倡廉警示教育活动心得体会
2014/09/04 职场文书
大专生自我鉴定怎么写
2014/09/16 职场文书
大学生军训自我鉴定范文
2014/09/18 职场文书
2014年房产销售工作总结
2014/12/08 职场文书
教师专业技术工作总结2015
2015/05/13 职场文书
证婚人婚礼致辞
2015/07/28 职场文书
Mysql 设置boolean类型的操作
2021/06/04 MySQL