经过一张简单的图,让你完全地、永久地搞懂JS的==运算

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

在仔细阅读了ECMAScript规范的基础上,我画了一张图,我想经过它你会完全地搞清楚关于==的一切。同时,我也试图经过此文向你们证实==并非那么糟糕的东西,它很容易掌握,甚至看起来很合理。java

先上图:git

==运算符的运算规则
图1 ==运算规则的图形化表示github

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

确定不行,规范毕竟是给JavaScript运行环境的开发人员看的(好比V8引擎的开发人员们),而不是给语言的使用者看的。而上图正是将规范中复杂的描述翻译成了更容易看懂的形式。数组

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

  1. JS中的值有两种类型:原始类型(Primitive)、对象类型(Object)。es5

  2. 基本类型包括:Undefined、Null、Boolean、Number和String等五种。spa

  3. Undefined类型和Null类型的都只有一个值,即undefinednull;Boolean类型有两个值:truefalse;Number类型的值有不少不少;String类型的值理论上有无数个。翻译

  4. 全部对象都有valueOf()toString()方法,它们继承自Object,固然也可能被子类重写。

如今考虑表达式:

x == y

其中xy是上述六种类型中某一种类型的值。

xy的类型相同时,x == y能够转化为x === y,然后者是很简单的(惟一须要注意的多是NaN),因此下面咱们只考虑xy的类型不一样的状况。

一. 有和无

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

  • String、Number、Boolean和Object (对应左侧的大矩形框)

  • Undefined和Null (对应右侧的矩形框)

分组的依据是什么?咱们来看一下,右侧的Undefined和Null是用来表示不肯定或者的,而右侧的四种类型都是肯定的非空。咱们能够这样说:

左侧是一个存在的世界,右侧是一个的世界。

因此,左右两个世界中的任意值作==比较的结果都是false是很合理的。(见图1中链接两个矩形的水平线上标的false)

二. 空和空

JavaScript中的undefinednull是另外一个常常让咱们崩溃的地方。一般它被认为是一个设计缺陷,这一点咱们不去深究。不过我曾据说,JavaScript的做者最初是这样想的:

假如你打算把一个变量赋予对象类型的值,可是如今尚未赋值,那么你能够用null表示此时的状态(证据之一就是typeof null 的结果是'object');相反,假如你打算把一个变量赋予原始类型的值,可是如今尚未赋值,那么你能够用undefined表示此时的状态。

无论这个传闻是否可信,它们二者作==比较的结果是true是很合理的。(见图1中右侧垂直线上标的true)

在进行下一步以前,咱们先来讲一下图1中的两个符号:大写字母N和P。这两个符号并非PN结中正和负的意思。而是:

  • N表示ToNumber操做,即将操做数转为数字。它是规范中的抽象操做,但咱们能够用JS中的Number()函数来等价替代。

  • P表示ToPrimitive操做,即将操做数转为原始类型的值。它也是规范中的抽象操做,一样也能够翻译成等价的JS代码。不过稍微复杂一些,简单说来,对于一个对象obj

ToPrimitive(obj)等价于:先计算obj.valueOf(),若是结果为原始值,则返回此结果;不然,计算obj.toString(),若是结果是原始值,则返回此结果;不然,抛出异常。

注:此处有个例外,即Date类型的对象,它会先调用toString()方法,后调用valueOf()方法。

在图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
Number('\r\n\t123\v\f') // 结果123

固然也有例外,好比空白字符串转化为数字的结果是0。即

Number('') // 结果0
Number('\r\n\t \v\f') // 结果0

五. 单纯与复杂

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

可是,有时候事情复杂了也不是好事。好比一篇冗长的论文,并非每一个人都有时间、有耐心或有必要从头至尾读一遍,一般只了解其中心思想就够了。因而论文就有了关键字、概述。JavaScript中的对象也同样,咱们须要有一种手段了解它的主要特征,因而对象就有了toString()valueOf()方法。

toString()方法用来获得对象的一段文字描述;而valueOf()方法用来获得对象的特征值。

固然,这只是我本身的理解。顾名思义,toString()方法倾向于返回一个字符串。那么valueOf()方法呢?根据规范中的描述,它倾向于返回一个数字——尽管内置类型中,valueOf()方法返回数字的只有NumberDate

根据图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等,幸亏这没有什么难度。

八. 再次变形

其实,图一还不够完美。为何呢?由于对象与字符串/数字比较时都由对象来转型,可是与一样是原始类型的布尔类型比较时却须要布尔类型转型。实际上,只要稍稍分析一下,所有让对象来转为原始类型也是等价的。因此咱们获得了最终的更加完美的图形:

图片描述
图3 更完美的==运算规则的图形化表示

有一个地方可能让你疑惑:为何Boolean与String之间标了两个N?虽然按照规则应该是由Boolean转为数字,可是下一步String就要转为数字了,因此干脆不如两边同时转成数字。

九. 总结一下

前面说得很乱,根据咱们获得的最终的图3,咱们总结一下==运算的规则:

  • undefined == null,结果是true。且它俩与全部其余值比较的结果都是false

  • String == Boolean,须要两个操做数同时转为Number。

  • String/Boolean == Number,须要String/Boolean转为Number。

  • Object == Primitive,须要Object转为Primitive(具体经过valueOf和toString方法)。

瞧见没有,一共只有4条规则!是否是很清晰、很简单。

PS:最后,把图改了一下,仅供娱乐 : )

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

相关文章
相关标签/搜索