JavaScript的==运算详解

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

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

先上图:

==运算规则的精确描述在此: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):

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

万物皆数。

七. 勉强举个栗子

前面废话太多了,这里还是举个例子,来证明图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,结束了。如果你觉得这篇文章对你有用,请点赞,让更多的人看到。
另外,文章中的谬误,请不吝指出。

(0)

相关推荐

  • 浅析Javascript中“==”与“===”的区别

    之前使用JavaScript的时候,时不时的会遇到使用 == 和 === 这两个符号来判断两个变量是否相等.但是对于这两个符号的差别一直没有去调查.今天又遇到了 === 这个符号,所以决定去查一下,究竟这两者有什么区别. 在Javascript中有"=="和"==="两种比较运行符,那么他们有什么区别呢? 一.对于string,number等基础类型,==和===是有区别的 1)不同类型间比较,==之比较"转化成同一类型后的值"看"值&

  • javascript中等于(==)与全等(===)的区别说明

    var y = 5; if(y == "5"){ document.write("== '5' True "); } else{ document.write("== '5' False "); } if(y == 5){ document.write("== 5 数字 is True "); } else{ document.write("== 5 数字 False "); } if( y === 5){

  • JS中==与===操作符的比较

    ===操作符: 要是两个值类型不同,返回false 要是两个值都是number类型,并且数值相同,返回true 要是两个值都是stirng,并且两个值的String内容相同,返回true 要是两个值都是true或者都是false,返回true 要是两个值都是指向相同的Object,Arraya或者function,返回true 要是两个值都是null或者都是undefined,返回true ==操作符: 如果两个值具有相同类型,会进行===比较,返回===的比较值 如果两个值不具有相同类型,也有

  • JavaScript中为什么null==0为false而null大于=0为true(个人研究)

    生活中我们在不停的编写代码,写着JavaScript,很少有时间进行概念上的研究.我呢,今天闲来没啥事,研究了一下有关"null"和"0"的关系.希望大家看完了能有所收获. 复制代码 代码如下: alert(null>=0) 复制代码 代码如下: 上面的代码会弹出什么来呢?False?True?其实是true.那么是为什么呢?为什么"null>=0"为true呢?null>=0的时候,强转为数字类型.在进行null>=0比

  • javascript中if和switch,==和===详解

    今天改插件BoxScroll的时候,因为if里面的条件判断多于两个,于是立马想着改写switch.改到一半,忽然记起来JSHint等代码质量检测工具中的一个要求,用===替换==,不用不可靠的强制转型.然后忽然猜想,改成switch是不是会降低效率啊?switch里面的实际判断是==还是===? 有了想法,赶紧举个栗子,好一口吃掉: var a = '5'; switch (a) { case 5: console.log('=='); break; case "5": console

  • JavaScript的==运算详解

    大家知道,JavaScript中的==是一种比较复杂运算,它的运算规则很奇怪,很容易让人犯错,从而成为JavaScript中"最糟糕的特性"之一. 在仔细阅读ECMAScript规范的基础上,我画了一张图,我想等你理解了这张图后,会彻底地弄懂关于==运算的一切.同时,我试图通过此文向大家证明==并不是那么糟糕的东西,它很容易掌握,甚至看起来很合理,并没那么糟糕. 先上图: ==运算规则的精确描述在此:The Abstract Equality Comparison Algorithm.

  • javascript 面向对象function详解及实例代码

    javascript 面向对象function详解     js中的函数有三种表示方式: //函数的第一种表示方式:函数关键字的方式 function f1() { alert("f1"); } //函数的第二种表示方式:函数字面量的方式 var f2 = function() { alert("f2"); } //函数的第三种表示方式:构造函数的方式 var f3 = new Function('var a = 100; b = 200; return a+b;'

  • Kotlin 语言中调用 JavaScript 方法实例详解

    Kotlin 语言中调用 JavaScript 方法实例详解 Kotlin 已被设计为能够与 Java 平台轻松互操作.它将 Java 类视为 Kotlin 类,并且 Java 也将 Kotlin 类视为 Java 类.但是,JavaScript 是一种动态类型语言,这意味着它不会在编译期检查类型.你可以通过动态类型在 Kotlin 中自由地与 JavaScript 交流,但是如果你想要 Kotlin 类型系统的全部威力 ,你可以为 JavaScript 库创建 Kotlin 头文件. 内联 J

  • Javascript字符串常用方法详解

    字符串 字符串就是一个或多个排列在一起的字符,放在单引号或双引号之中. 'abc' "abc" length属性 js里的字符串类似于数组,都是一个一个字符拼凑在一起组成的,因此可以用length属性取得字符串的长度 var str = "hello" str.length; // 5 字符串常用的一些方法 1. charAt() str.charAt(n) => 返回字符串的第 n 个字符,如果不在 0~str.length-1之间,则返回一个空字符串. v

  • JavaScript作用域示例详解

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域示例详解的介绍,希望能帮助大家更好的学习JavaScript. 任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在JavaScript中,变量的作用域有全局作用域和局部作用域两种. 一.JavaScript中无块级作用域 在Java或C#中存在块级作用域

  • JavaScript 函数节流详解及方法总结

    JavaScript 函数节流详解 浏览器一个网页的UI线程只有一个,他同时会处理界面的渲染和页面JavaScript代码的执行(简单扩展一下,浏览器或者JavaScript运行大环境并不是单线程,诸如ajax异步回调.hybrid框架内与native通信.事件队列.CSS运行线程等等都属于多线程环境,不过ES6引入了Promise类来减少了部分异步情况).因此当JavaScript代码运行计算量很大的方法时,就有可能阻塞UI线程,小则导致用户响应卡顿,严重的情况下浏览器会提示页面无响应是否强制

  • Javascript复制实例详解

    在做项目时有一个需求,是需要复制内容到剪切板,因为有众多浏览器,所以要兼容性很重要. 1.最简单的copy,只能在IE下使用 使用clipboardData方法 <script type="text/javascript"> function copy(){ window.clipboardData.setData("text",document.getElementById("name").value); alert("T

  • javascript 操作cookies详解及实例

    javascript 操作cookies详解 这段操作cookies的方法我使用很久了,但是一直一来没遇到什么问题,今天在做一个在第一个页面保存了cookies,第二个页面获取或者第三个页面获取的功能中,发现了方法的局限性,比如,第一个页面路径为 http://xxxxx/cyb-car2016/h5OfficeWorker/index,第二个页面路径为 http://xxxxx/cyb-car2016/h5AlertController/index,其中除了域名是一样之外,还有一个命名空间不一

  • 最通俗易懂的javascript变量提升详解

    如下所示: a = 'ghostwu'; var a; console.log( a ); 在我没有讲什么是变量提升,以及变量提升的规则之前, 或者你没有学习过变量提升,如果按照现有的javascript理解, 对于上述的例子,你可能会认为第3行代码的输出结果应该是undefined, 因为第二行是var a; 声明变量,但是没有赋值,所以a的值是undefined, 但是正确的结果是ghostwu. 至于为什么,请继续往下看! console.log( a ); var a = 'ghostw

  • JavaScript实现AOP详解(面向切面编程,装饰者模式)

    什么是AOP? AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计.安全控制.异常处理等.把这些功能抽离出来之后, 再通过"动态织入"的方式掺入业务逻辑模块中. AOP能给我们带来什么好处? AOP的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块. JavaScript实现AOP的思路? 通常,在 JavaScript 中实现 AOP,都是指把一个函数"动态织入&qu

随机推荐