翻译自:http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3

在JS中的关系比较(Relational Comparison)运算,指的是像x < y这种大小值的关系比较。

而相等比较,可区分为标准相等(standard equality)比较x == y与严格相等(strict equality)比较x === y两大种类。严格相等比较会比较左边与右边运算元的数据类型,值相等比较则只看值,简单的来说是这样解释没错。

ToPrimitive运算的详细说明可参考: JS中的{} + {}与{} + []的结果是什么?

不过,这两种比较实际上依内部设计来说,并不是那么简单。当然,在一般的使用情况是不需要考量那么多,本文的说明会涉及许多JS内部设计的部份,对于这两种比较来作比较彻底的理解,主要的参考数据是ECMAScript的标准文件。

严格相等比较(严格相等比较演算)

严格相等比较的演算规则先理解,主要是因为在标准相等比较(只比较值不比较数据类型)时,它在演算时的某些情况下会跳到严格相等比较的规则来。

严格相等比较的演算规则很容易理解,按照以下的步骤进行比较,出自ecma-262 11.9.6:

以下假设为比较 x === y的情况,Type(x)指的是x的数据类型,Type(y)指的是y的类型,最终返回值只有true或false,会按照下面的步骤进行比较,如果有返回时就停止之后的步骤:

注: Type(x)在ECMAScript的标准中指的并不是用typeof返回出来的结果,而是标准内部给定的各种数据类型,共有Undefined, Null, Boolean, String, Number 与 Object。例如typeof null的结果是"object",但ECMAScript会认为Null是个独立的数据类型。

  1. Type(x)与Type(y)不同,返回false

  2. Type(x)是Undefined,返回true(当然此时Type(y)也是Undefined)

  3. Type(x)是Null,返回true(当然此时Type(y)也是Null)

  4. Type(x)是Number时

    • (a.) x是NaN,返回false

    • (b.) y是NaN,返回false

    • (c.) x与y是同样的数字,返回true

    • (d.) x是+0,y是-0,返回true

    • (e.) x是-0,y是+0,返回true

    • (f.) 其他情况,返回false

  5. Type(x)是String时,只有当x中的字符顺序与y中完全相同时(长度相同,字符所在位置也相同),返回true。其他情况就返回false。

  6. Type(x)是Boolean时,只有当x与y是同时为true或同时为false时,返回true。其它情况返回false。

  7. 只有当x与y同时参照到同一对象时,返回true。其它情况返回false。

备注: 这个演算与the SameValue Algorithm (9.12)不同之处在于,对于有号的0与NaN处理方式不同。

注: 同值演算(the SameValue Algorithm)是标准中的另一个内部演算法,只会用在很特别的地方,可以先略过不看。

从上述的严格相等比较中,可以很清楚的看到数字、字符串、布尔与null、undefined或对象是如何比较的。

标准相等比较(抽象相等比较演算)

标准相等比较的演算规则按照以下的步骤进行比较,出自ecma-262 11.9.3:

以下假设为比较 x == y的情况,Type(x)指的是x的数据类型,Type(y)指的是y的类型,最终返回值只有true或false,会按照下面的步骤进行比较,如果有返回时就停止之后的步骤:

  1. Type(x)与Type(y)相同时,进行严格相等比较

  2. x是undefined,而y是null时,返回true

  3. x是null,而y是undefined时,返回true

  4. Type(x)是Number而Type(y)是String时,进行x == ToNumber(y)比较

  5. Type(x)是String而Type(y)是Number时,进行ToNumber(x) == y比较

  6. Type(x)是Boolean时,进行ToNumber(x) == y

  7. Type(y)是Boolean时,进行x == ToNumber(y)

  8. Type(x)是Number或String其中一种,而Type(y)是个Object时,进行x == ToPrimitive(y)比较

  9. Type(x)是个Object,而Type(y)是Number或String其中一种时,进行ToPrimitive(x) == y比较

  10. 其他情况,返回false

备注1: 以下的是三种强制转换的标准比较情况:

  • 字符串比较: "" + a == "" + b.

  • 数字比较: +a == +b.

  • 布尔比较: !a == !b

备注2: 标准相等比较有以下的不变式(invariants):

  • A != B 相当于 !(A == B)

  • A == B 相当于 B == A

备注3: 相等比较运算不一定总是可以转变(transitive),例如:

  • new String("a") == "a" 与 "a" == new String("a") 的结果都是true

  • new String("a") == new String("a") 结果是false.

备注4: 字符串比较使用的是简单的字符测试。并非使用复杂的、语义导向的字符定义或是Unicode所定义的字符串相等或校对顺序。

注: 上述的ToNumber与ToPrimitive都是标准内部运算时使用的方法,并不是让开发者使用的。

由标准相等比较的演算得知,它的运算是以"数字为最优先",任何其它的类型如果与数字作相等比较,必定要先强制转为数字再比较。但这是一个相当具有隐藏作用的运算,在一般实作时,会很容易造成误解,例如以下的例子:

> 0 == []
true > '' == []
true

上面这是因为空数组[],进行ToPrimitive运算后,得到的是空字符串,所以作值相等比较,相当于空字符串在进行比较。

> '[object Object]' == {}
true > NaN == {}
false

上面的空对象字面量,进行ToPrimitive运算后,得到的是'[object Object]'字符串,这个值会如果与数字类型的NaN比较,会跳到同类型相等的严格相等比较中,NaN不论与任何数字作相等比较,一定是返回false。

> 1 == new Number(1)
true > 1 === new Number(1)
false > 1 === Number(1)
true

上面说明了,包装对象在JS中的内部设计中,标准的值相等比较是相同的,但严格相等比较是不同的值,包装对象仍然是个对象,只是里面的valueOf方法是返回这个对象里面带的原始数据类型值,经过ToPrimitive方法运算后,会返回原始数据的值。Number()函数调用只是转数字类型用的函数,这个用法经常会与包装对象的用法混在一起。

这个小节的结论是,在JS中没有必要的情况下,使用严格的相等比较为最佳的值相等比较方式,标准的相等容易产生不经意的副作用,有的时候你可能会得到不预期的结果。

关系比较(抽象关系比较演算)

关系比较的演算规则主要是按照以下的步骤进行比较,出自ecma-262 11.8.5:

以下假设为比较 x < y的情况,因为在标准中的抽象关系比较演算的说明比较复杂,有涉及布尔标记的以左方优先或右方优先,而且最终返回值有true、false与undefined,实际上最终不会有undefined值出现,即是得到false而已,以下为只考虑左方优先(LeftFirst)的简化过的步骤。会按照下面的步骤进行比较,如果有返回时就停止之后的步骤:

  • (1. & 2.) x经过ToPrimitive(x, hint Number)运算为px值,y经过ToPrimitive(y, hint Number)运算为py值

  • (3.) 如果Type(px)与Type(py)不同时为String时

    • (a.b.) px作ToNumber(px)运算,得到nx值,与py作ToNumber(py)值,得到ny值

    • (c.d.) nx或ny中有其一为NaN时,返回undefined

    • (e.) nx与ny是同样的Number值,返回false

    • (f.) nx是+0,而且ny是−0,返回false

    • (g.) nx是−0,而且ny是+0,返回false.

    • (h.) nx是+∞,返回false

    • (i.) ny是+∞,返回true

    • (j.) ny是−∞,返回false

    • (k.) nx是−∞,返回true

    • (l.) 如果在数学上的值,nx小于ny,而且nx与ny是有限值(finite),而且不同时为0时,返回true。否则返回false。

  • (4.) 如果Type(px)与Type(py)同时为String时

    • (a.) 如果py是px的前缀(prefix)时,返回false (前缀代表px字符串中是由py字符串组成的,py只是px的子字符串的情况)

    • (b.) 如果px是py的前缀(prefix)时,返回true

    • (c.d.e.f) 以字符串中的按顺序的字符,用字符的编码整数的大小来比较。k是可得到的一个最小非负整数,在px与py中的k位置有不同的字符(从左边算过来)。在px中某个位置k的字符编码整数为m,在py某个位置k的字符编辑为n,如果m < n,则返回true,否则返回false

备注2: 字符串比较使用的是简单的词典顺序测试。并非使用复杂的、语义导向的字符定义或是Unicode所定义的字符串相等或校对顺序。

注: +∞相当于全局属性InfinityNumber.POSITIVE_INFINITY−∞相当于全局属性-InfinityNumber.NEGATIVE_INFINITY

关系比较基本上要区分为数字类型与字符串类型,但依然是以"数字"为最优先的比较,只要有其他类型与数字相比较,一定会先被强制转换为数字。但在这之前,需要先用ToPrimitive而且是hint为数字来转换为原始数据类型。

以下为一些与对象、数组、Date对象的关系比较例子:

> 1 < (new Date())
true > 1 > (new Date())
false > [] < 1
true > [] > 1
false > ({}) < 1
false > ({}) > 1
false

虽然在标准中的抽象关系比较演算中,有存在一种返回值undefined,但在真实的情况并没有这种返回值,相当不论怎么比较都是得到false的值。上面的例子中,空对象({})的ToPrimitive运算得出的是'[object Object]'字符串值,经过ToNumber运算会得到NaN数字类型的值,这个值不论与数字1作大于小于的关系运算,都是false。

Date()对象因为ToPrimitive运算的hint为数字,所以也是会作转换为数字类型的值为优先(也就是调用valueOf为优先),所以并不是正常情况的以输出字符串为优先(也就是调用toString方法为优先)的预设情况。

以下为一些字符串关系比较的例子:

> 'a' > ''
true > 'a' < ''
false > 'a' > []
true > 'a' < []
false > 'a' > ({})
true > 'a' < ({})
false

字符串与空字符串相比,都是套用前缀(prefix)的规则步骤,因为空字符串算是所有字符串的前缀(组成的子字符串之一),所以必然地所有有值的字符串值一定是大于空字符串。

空数组经过ToPrimitive运算出来的是空字符串,所以与空字符串相比较的结果相同。

空对象经过ToPrimitive运算出来的是'[object Object]'字符串值,以'a'.charCodeAt(0)计算出的值是字符编码是97数字,而'['.charCodeAt(0)则是91数字,所以'a' > ({})会是得到true。

如果开始混用数字与字符串比较,可能是有陷阱的比较例子:

> '11' > '3'
false > '11' > 3
true > 'one' < 3
false > 'one' > 3
false

'11'与'3'相比较,其实都是字符串比较,要依照可比较的字符位置来比较,也就是'1'与'3'字符的比较,它们的字符编码数字分别是49与51,所以'1' < '3',这里的运算的结果必然是返回false。

'11'与3数字比较,是会强制都转为数字来比较,'11'会转为11数字值,所以大于3。

'one'这个字符串转为数字后,是NaN这个数字值,NaN与任何数字比较,既不大于也不小于,不论作大于或小于,都是返回false。(实际上在标准中它这种返回值叫undefined)

字符串与数字之外其他的原始数据类型的比较,只要记得原则就是强制转为数字来比较就是了,以下为例子:

> true > null
true > false > undefined
false

简单地说明在ToNumber运算时,这些其他的原始数据类型值的转换结果如下:

  • Undefined -> NaN

  • Null -> +0

  • Boolean -> (true -> 1, false -> 0)

注: JS认为+0与-0是完全相同的值,在严格相等比较中是相等的。

注: 字符串比较实际上是拆为字符在词典表中的编辑整数值来比较,对于非英语系的语言,JS另外有提供String.prototype.localeCompare的方法来进行局部语言的比较工作。

总结

本章延伸了之前的加法运算文章中的ToPrimitive运算解说的部份,较为仔细的来研究JS中的相等比较(包含标准的与严格的)与关系比较的部份。至于没提到的,不相等(==)与严格不相等(!==),或是大于等于(>=)或小于等于(<=)只是这些演算规划的再组合结果而已。

标准的值相等比较(==),是一种有不经意的副作用的运算,不管如何,开发者必定要尽量避免,比较前可以自行转换类型的方式,再作严格的相等比较,本章也有说明为何要避免使用它的理由。

原:https://segmentfault.com/a/1190000000650129#articleHeader0

原:https://segmentfault.com/a/1190000008038751

详解一下 javascript 中的比较的更多相关文章

  1. 【转】详解JavaScript中的this

    ref:http://blog.jobbole.com/39305/ 来源:foocoder 详解JavaScript中的this JavaScript中的this总是让人迷惑,应该是js众所周知的坑 ...

  2. 详解 javascript中offsetleft属性的用法(转)

    详解 javascript中offsetleft属性的用法 转载  2015-11-11   投稿:mrr    我要评论 本章节通过代码实例介绍一下offsetleft属性的用法,需要的朋友可以做一 ...

  3. 详解javascript中的this对象

    详解javascript中的this对象 前言 Javascript是一门基于对象的动态语言,也就是说,所有东西都是对象,一个很典型的例子就是函数也被视为普通的对象.Javascript可以通过一定的 ...

  4. (转载)详解Javascript中prototype属性(推荐)

    在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不 ...

  5. 【转】详解JavaScript中的异常处理方法

    有三种类型的编程错误:(1)语法错误和(2)运行时错误(3)逻辑错误:语法错误: 语法错误,也被称为解析错误,在编译时进行传统的编程语言,并出现在JavaScript解释时. 例如,下面一行将导致一个 ...

  6. 详解JavaScript中的原型

    前言 原型.原型链应该是被大多数前端er说烂的词,但是应该还有很多人不能完整的解释这两个内容,当然也包括我自己. 最早一篇原型链文章写于2019年07月,那个时候也是费了老大劲才理解到了七八成,到现在 ...

  7. 详解Javascript中正则表达式的使用

    正则表达式用来处理字符串特别好用,在JavaScript中能用到正则表达式的地方有很多,本文对正则表达式基础知识和Javascript中正则表达式的使用做一个总结. 第一部分简单列举了正则表达式在Ja ...

  8. 详解JavaScript中的this

    JavaScript中的this总是让人迷惑,应该是js众所周知的坑之一. 个人也觉得js中的this不是一个好的设计,由于this晚绑定的特性,它可以是全局对象,当前对象,或者…有人甚至因为坑大而不 ...

  9. 详解JavaScript中的Url编码/解码,表单提交中网址编码

    本文主要针对URI编解码的相关问题做了介绍,对Url编码中哪些字符需要编码.为什么需要编码做了详细的说明,并对比分析了Javascript 中和 编解码相关的几对函数escape / unescape ...

随机推荐

  1. lua源代码学习(一)lua的c api外围实现

    工作后,整个人已经比較松懈了.尽管一直在看lua的源代码.可是一直是比較零碎的时间,没有系统的整理,所以还是收获不多.由于近期工作也不是非常忙了,就想整理下lua的源代码学习的笔记.加深下印象,并分享 ...

  2. android 本地字符串存取

    存 // data 指定的文件名 SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVAT ...

  3. Linux中Kill掉进程的10种方法

    常规篇: 首先,用ps查看进程,方法如下: 复制代码 代码如下: $ ps -ef……smx 1822 1 0 11:38 ? 00:00:49 gnome-terminalsmx 1823 1822 ...

  4. 搭建基于HTTP协议内网yum仓库

    目录 1. 前言 2. 把rpm包下载到本地 3. 配置nginx对外提供服务 4. 配置本地repo文件 5. 生成repodata信息 6. 检查及使用 7. 对管理机器上的仓库进行更新 参考资料 ...

  5. ios 工作日志

    1.设计模式 1.1 想用一个controllerK控制多个页面的切换 但是每一个页面必须要引用这个controller,这样才能控制进度, 所以必须是弱引用.controller必须被某一个实例强引 ...

  6. sql2008评估板过期

    1.查看sql2008到期时间,打开数据库---帮助---关于,具体可查看试用期还有多长时间 2.重新激活 : ① 打开注册表后,找到并把 HKEY_LOCAL_MACHINE\SOFTWARE\Mi ...

  7. 软件包管理:rpm命令管理-安装升级与卸载

    严格区分大小写 卸载命令不许再包的目录下执行.

  8. 使用TreeView加载XML文件

    PS: 由于小弟初学编程,本文只写实现方式,代码写的不是很好请见谅! 1.需要读取的xml文档内容 2. 最终实现效果 3  貌似看起实现起来很复杂 但是想想还是挺简单 思路:  读取XML文档 →获 ...

  9. C#导出Excel总结

    一.asp.net中导出Execl的方法:在asp.net中导出Execl有两种方法,一种是将导出的文件存放在服务器某个文件夹下面,然后将文件地址输出在浏览器上:一种是将文件直接将文件输出流写给浏览器 ...

  10. UVM中的sequence使用(一)

    UVM中Driver,transaction,sequence,sequencer之间的关系. UVM将原来在Driver中的数据定义部分,单独拿出来成为Transaction,主要完成数据的rand ...