详解一下 javascript 中的比较
翻译自: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是个独立的数据类型。
Type(x)与Type(y)不同,返回false
Type(x)是Undefined,返回true(当然此时Type(y)也是Undefined)
Type(x)是Null,返回true(当然此时Type(y)也是Null)
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
Type(x)是String时,只有当x中的字符顺序与y中完全相同时(长度相同,字符所在位置也相同),返回true。其他情况就返回false。
Type(x)是Boolean时,只有当x与y是同时为true或同时为false时,返回true。其它情况返回false。
只有当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,会按照下面的步骤进行比较,如果有返回时就停止之后的步骤:
Type(x)与Type(y)相同时,进行严格相等比较
x是undefined,而y是null时,返回true
x是null,而y是undefined时,返回true
Type(x)是Number而Type(y)是String时,进行
x == ToNumber(y)比较Type(x)是String而Type(y)是Number时,进行
ToNumber(x) == y比较Type(x)是Boolean时,进行
ToNumber(x) == yType(y)是Boolean时,进行
x == ToNumber(y)Type(x)是Number或String其中一种,而Type(y)是个Object时,进行
x == ToPrimitive(y)比较Type(x)是个Object,而Type(y)是Number或String其中一种时,进行
ToPrimitive(x) == y比较其他情况,返回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所定义的字符串相等或校对顺序。
注:
+∞相当于全局属性Infinity或Number.POSITIVE_INFINITY,−∞相当于全局属性-Infinity或Number.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 中的比较的更多相关文章
- 【转】详解JavaScript中的this
ref:http://blog.jobbole.com/39305/ 来源:foocoder 详解JavaScript中的this JavaScript中的this总是让人迷惑,应该是js众所周知的坑 ...
- 详解 javascript中offsetleft属性的用法(转)
详解 javascript中offsetleft属性的用法 转载 2015-11-11 投稿:mrr 我要评论 本章节通过代码实例介绍一下offsetleft属性的用法,需要的朋友可以做一 ...
- 详解javascript中的this对象
详解javascript中的this对象 前言 Javascript是一门基于对象的动态语言,也就是说,所有东西都是对象,一个很典型的例子就是函数也被视为普通的对象.Javascript可以通过一定的 ...
- (转载)详解Javascript中prototype属性(推荐)
在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不 ...
- 【转】详解JavaScript中的异常处理方法
有三种类型的编程错误:(1)语法错误和(2)运行时错误(3)逻辑错误:语法错误: 语法错误,也被称为解析错误,在编译时进行传统的编程语言,并出现在JavaScript解释时. 例如,下面一行将导致一个 ...
- 详解JavaScript中的原型
前言 原型.原型链应该是被大多数前端er说烂的词,但是应该还有很多人不能完整的解释这两个内容,当然也包括我自己. 最早一篇原型链文章写于2019年07月,那个时候也是费了老大劲才理解到了七八成,到现在 ...
- 详解Javascript中正则表达式的使用
正则表达式用来处理字符串特别好用,在JavaScript中能用到正则表达式的地方有很多,本文对正则表达式基础知识和Javascript中正则表达式的使用做一个总结. 第一部分简单列举了正则表达式在Ja ...
- 详解JavaScript中的this
JavaScript中的this总是让人迷惑,应该是js众所周知的坑之一. 个人也觉得js中的this不是一个好的设计,由于this晚绑定的特性,它可以是全局对象,当前对象,或者…有人甚至因为坑大而不 ...
- 详解JavaScript中的Url编码/解码,表单提交中网址编码
本文主要针对URI编解码的相关问题做了介绍,对Url编码中哪些字符需要编码.为什么需要编码做了详细的说明,并对比分析了Javascript 中和 编解码相关的几对函数escape / unescape ...
随机推荐
- lua源代码学习(一)lua的c api外围实现
工作后,整个人已经比較松懈了.尽管一直在看lua的源代码.可是一直是比較零碎的时间,没有系统的整理,所以还是收获不多.由于近期工作也不是非常忙了,就想整理下lua的源代码学习的笔记.加深下印象,并分享 ...
- android 本地字符串存取
存 // data 指定的文件名 SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVAT ...
- Linux中Kill掉进程的10种方法
常规篇: 首先,用ps查看进程,方法如下: 复制代码 代码如下: $ ps -ef……smx 1822 1 0 11:38 ? 00:00:49 gnome-terminalsmx 1823 1822 ...
- 搭建基于HTTP协议内网yum仓库
目录 1. 前言 2. 把rpm包下载到本地 3. 配置nginx对外提供服务 4. 配置本地repo文件 5. 生成repodata信息 6. 检查及使用 7. 对管理机器上的仓库进行更新 参考资料 ...
- ios 工作日志
1.设计模式 1.1 想用一个controllerK控制多个页面的切换 但是每一个页面必须要引用这个controller,这样才能控制进度, 所以必须是弱引用.controller必须被某一个实例强引 ...
- sql2008评估板过期
1.查看sql2008到期时间,打开数据库---帮助---关于,具体可查看试用期还有多长时间 2.重新激活 : ① 打开注册表后,找到并把 HKEY_LOCAL_MACHINE\SOFTWARE\Mi ...
- 软件包管理:rpm命令管理-安装升级与卸载
严格区分大小写 卸载命令不许再包的目录下执行.
- 使用TreeView加载XML文件
PS: 由于小弟初学编程,本文只写实现方式,代码写的不是很好请见谅! 1.需要读取的xml文档内容 2. 最终实现效果 3 貌似看起实现起来很复杂 但是想想还是挺简单 思路: 读取XML文档 →获 ...
- C#导出Excel总结
一.asp.net中导出Execl的方法:在asp.net中导出Execl有两种方法,一种是将导出的文件存放在服务器某个文件夹下面,然后将文件地址输出在浏览器上:一种是将文件直接将文件输出流写给浏览器 ...
- UVM中的sequence使用(一)
UVM中Driver,transaction,sequence,sequencer之间的关系. UVM将原来在Driver中的数据定义部分,单独拿出来成为Transaction,主要完成数据的rand ...