通过一张简单的图,让你彻底地搞懂JS的==运算
大家知道,JavaScript中的==
是一种比较复杂运算,它的运算规则很奇怪,很容易让人犯错,从而成为JavaScript中“最糟糕的特性”之一。
在仔细阅读ECMAScript规范的基础上,我画了一张图,我想等你理解了这张图后,会彻底地弄懂关于==运算的一切。同时,我试图通过此文向大家证明==
并不是那么糟糕的东西,它很容易掌握,甚至看起来很合理,并没那么糟糕。
先上图:
图1 == 运算规则的图形化表示
==
运算规则的精确描述在此: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):
图2 == 运算过程中类型转化的趋势
发现什么了吗?对,在运算过程中,所有类型的值都有一种向数字类型转化的趋势。毕竟曾经有名人说过:
万物皆数。
七. 勉强举个栗子
前面废话太多了,这里还是举个例子,来证明图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
。字符串 == 数字
时,字符串转为数字。布尔值 == 其他类型
时,布尔值转为数字。对象 == 数字/字符串
时,对象转为基本类型。
最后,把图改了一下,仅供娱乐 : )
OK,结束了。如果你觉得这篇文章对你有用,请点赞,让更多的人看到。
另外,文章中的谬误,请不吝指出。
通过一张简单的图,让你彻底地搞懂JS的==运算的更多相关文章
- 通过一张简单的图,让你搞懂JS的==运算
== 运算的规则: undefined == null,结果是true.且它俩与所有其他值比较的结果都是false. String == Boolean,需要两个操作数同时转为Number. Stri ...
- 一张图彻底搞懂JavaScript的==运算
一张图彻底搞懂JavaScript的==运算 来源 https://zhuanlan.zhihu.com/p/21650547 PS:最后,把图改了一下,仅供娱乐 : ) 大家知道,==是JavaSc ...
- 三张图搞懂JavaScript的原型对象与原型链
对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__proto__混淆,二来它们之间的各种指向实在有些复杂,其实市面上已经有非常多的文章在尝试说清楚,有一张 ...
- 三张图搞懂JavaScript的原型对象与原型链 / js继承,各种继承的优缺点(原型链继承,组合继承,寄生组合继承)
摘自:https://www.cnblogs.com/shuiyi/p/5305435.html 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__pro ...
- 一篇文章一张思维导图看懂Android学习最佳路线
一篇文章一张思维导图看懂Android学习最佳路线 先上一张android开发知识点学习路线图思维导图 Android学习路线从4个阶段来对Android的学习过程做一个全面的分析:Android初级 ...
- 【转载】跟着9张思维导图学习JavaScript
原文:跟着9张思维导图学习JavaScript 学习的道路就是要不断的总结归纳,好记性不如烂笔头,so,下面将 po 出我收集的 9 张 JavaScript相关的思维导图(非原创). 思维导图小ti ...
- 跟着9张思维导图学习Javascript js 关键字和保留字 css3中的BFC,IFC,GFC和FFC
跟着9张思维导图学习Javascript 学习的道路就是要不断的总结归纳,好记性不如烂笔头,so,下面将 po 出我收集的 9 张 javascript 相关的思维导图(非原创). 思维导图小ti ...
- Unity 渲染教程(三):使用多张纹理贴图
对多个纹理进行采样 应用一张细节贴图 在线性空间中处理颜色 使用一张splat纹理 这是关于渲染的教程系列的第三部分. 前面的部分介绍了着色器和纹理. 我们已经看到如何使用单个纹理来使平坦表面看起来更 ...
- pygame简单动态图 & 动态图片的移动
之前在学pygame 时看了一些博客(来自http://eyehere.net/2011/python-pygame-novice-professional-plant-zombie-1/),觉得写得 ...
随机推荐
- WPF从入门到放弃系列第一章 初识WPF
什么是WPF WPF(Windows Presentation Foundation)是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分.它提供 ...
- C#微信登录-电脑版扫描二维码登录
像京东,一号店等网站都实现了用微信来登录的功能,就是用手机上的微信扫一扫网站上的二维码,微信上确认后,即可自动用微信的帐号登录网站. 一.创建网站应用 在微信开放平台创建一个网站应用 https:// ...
- 仿今日头条最强顶部导航指示器,支持6种模式-b
项目中经常会用到类似今日头条中顶部的导航指示器,我也经常用一个类似的库PagerSlidingTabStrip,但是有时并不能小伙伴们的所有需求,所以我在这个类的基础上就所有能用到的情况做了一个简单的 ...
- asp.net中对象的序列化,方便网络传输
对象序列化 是将对象状态转换为可保持或传输的格式的过程.反序列化 是将流转换为对象序列化和反序列化相结合 可以使对象数据轻松的存储和传递 在 .NET 中,如果是对象可序列化,需要在 声明对象的开始部 ...
- C# 发送邮件实例代码
1.构造附件 static List<Attachment> BuildAttachments(List<EmailFile> files) { ) { return null ...
- TSS 任务状态段 详解
http://blog.163.com/di_yang@yeah/blog/static/86118492201222210725146/1 什么是TSS TSS 全称task state segme ...
- Vue.js 基础示例
为 Vue.js 初学者写了一些简单的示例,在线示例 示例源码 了解更多请查看 Vue.js 官网文档:http://vuejs.org.cn/guide/
- Android MediaStore与Media.EXTERNAL_CONTENT_URI
MediaStore这个类是Android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取.这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,andro ...
- Android开发:Translucent System Bar 的最佳实践
Translucent System Bar 的最佳实践 近几天准备抽空总结Android一些系统UI的实践使用,于是开始动手建了一个库AndroidSystemUiTraining ,边撸代码边写总 ...
- Android进阶篇-内存管理
很多时候我们需要考虑Android平台上的内存管理问题,Dalvik VM给每个进程都分配了一定量的可用堆内存,当我们处理一些耗费资源的操作时可能会产生OOM错误(OutOfMemoryError)这 ...