JS中的关系比较与相等比较运算

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

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

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

不过,这两种比较实际上依内部设计来讲,并非那么简单。固然,在通常的使用状况是不须要考量那么多,本文的说明会涉及许多JS内部设计的部份,对于这两种比较来做比较完全的理解,主要的参考数据是ECMAScript的标准文件。函数

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

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

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

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

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

  1. Type(x)与Type(y)不一样,返回falsecode

  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中的相等比较(包含标准的与严格的)与关系比较的部份。至于没提到的,不相等(==)与严格不相等(!==),或是大于等于(>=)或小于等于(<=)只是这些演算规划的再组合结果而已。

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

相关文章
相关标签/搜索