美文网首页HTML的自我修养程序员
JavaScript中比较运算的详细总结

JavaScript中比较运算的详细总结

作者: leisure_gcidea | 来源:发表于2017-02-19 15:00 被阅读7次

    前言

    在javascript开发中,比较操作是十分常见的。由于显式/隐式强制类型转换机制的存在,我们在使用比较运算时显得过于随意,也许表面上看并没有什么错误(比如在if()语句中判断两值相等时顺手就写成 == ),但是这可能会埋下很多不易发现的隐患。对于比较操作(相等关系和不等关系),在javascript中其实是有一套完善的机制的。本文依据ES5规范《ECMAScript Language Specification ECMA-262
    5.1 Edition / June 2011》
    的11.8节和11.9节:

    对javascript中比较操作相关内容进行系统总结。


    相等比较

    严格相等

    严格相等指“===”,它不允许比较双方进行强制类型转换。因此,问题考虑变得简单,对于x === y,javascript引擎在进行判断时所遵循的算法如下:

    1. 如果x和y的数据类型不同,返回false。
    2. 如果x是undefined,返回true。
    3. 如果x是null,返回true。
    4. 如果x是number类型:
       4.1 如果x是NaN,返回false。
       4.2 如果y是NaN,返回false。
       4.3 如果x和y的值相等,返回true。
       4.4 如果x是+0,y是-0,返回true。
       4.5 如果x是-0,y是+0,返回true。
       4.6 否则,返回false。
    5. 如果x是string类型,如果x和y是长度相等且对应位置上字符相同的序列,返回true;否则返回false。
    6. 如果x是Boolean类型,如果x和y均为true或者x和y均为false,返回true;否则,返回false。
    7. 如果x是对象(普通对象,函数,数组等),那么如果x和y指向同一个对象(是内存中同一个对象的引用),返回true;否则,返回false。

    宽松相等

    宽松相等指“==”,它会对比较双方进行隐式强制类型转换。下面先根据ES5规范进行系统说明:

    1. 如果x和y的数据类型相同:
       1.1 如果x的类型是undefined,返回true。
       1.2 如果x的类型是null,返回true。
       1.3 如果x的类型是number:
        1.3.1 如果x是NaN,返回false。
        1.3.2 如果y是NaN,返回false。
        1.3.3 如果x和y的值相同,返回true。
        1.3.4 如果x是+0,y是-0,返回true。
        1.3.5 如果x是-0,y是+0,返回true。
        1.3.6 否则,返回false。
       1.4 如果x是string类型,如果x和y是长度相等且对应位置上字符相同的序列,返回true;否则返回false。
       1.5 如果x是Boolean类型,如果x和y均为true或者x和y均为false,返回true;否则,返回false。
       1.6 如果x是对象(普通对象,函数,数组等),那么如果x和y指向同一个对象(是内存中同一个对象的引用),返回true;否则,返回false。
    2. 如果x是null,y是undefined,返回true。
    3. 如果x是undefined,y是null,返回true。
    4. 如果x是number类型,y是string类型,则对y进行类型转换,转换为number类型,返回 x == ToNumber(y) 的结果(参见上述1.3)。
    5. 如果x是string类型,y是number类型,则对x进行类型转换,转换为number类型,返回 ToNumber(x) == y 的结果(参见上述1.3)。
    6. 如果x是Boolean类型,则对x进行类型转换,转换为number类型,返回 ToNumber(x) == y 的结果(此时y的类型还是不确定的,应将其转换为number类型后进行比较)。
    7. 如果y是Boolean类型,则对y进行类型转换,转换为number类型,返回 x == ToNumber(y) 的结果(此时x的类型还是不确定的,应将其转换为number类型后进行比较)。
    8. 如果x是string类型或者number类型,y是一个对象(普通对象,函数,数组等),则对y进行类型转换---使用内置的[[ToPrimitive]]方法转换(该方法简单来说,就是先调用该对象上的valueOf()方法,如果有该方法且返回基本类型值,就使用该值进行强制类型转换;如果不存在,就调用该对象上的toString()方法,如果有该方法,就使用其返回值来进行强制类型转换;如果这两个方法都不存在,就产生TypeError错误。),返回 x == ToPrimitive(y) 的结果。
    9. 如果x是一个对象(普通对象,函数,数组等),y是string类型或者number类型,则对x进行类型转换---使用内置的[[ToPrimitive]],返回 ToPrimitive(x) == y 的结果。
    10. 否则(非上述所有情况),返回false。

    注:针对以上10条有几点注意事项:
    1.对a,b强制按字符串string类型进行比较,可采用如下方法:
    "" + a == "" + b

    2.对a,b强制按数字number类型进行比较,可采用如下方法:
    +a == +b

    3.对a,b强制按布尔值boolean类型进行比较,可采用如下方法:
    !!a == !!b

    4.相等比较操作有以下恒等性:

    • A != B 等价于 !(A == B)
    • A == B 等价于 B == A (除非A B有顺序上的互相计算关系)

    5.相等操作并不是总具有传递性。
    new String("a") == "a" 和 "a" == new String("a") 结果都返回true;
    new String("a") == new String("a") 却返回false。
    因为new String("a")是一个对象,按照上述规则会被转换为"a",因此相等;而new String("a") == new String("a"),==左右两边是两个不同的对象,在内存中位于不同地址,因此结果返回false。


    由于强制类型转换的存在,宽松相等的情况变得复杂,比较容易出现问题的有以下几种:

    更改内置原生原型后的相等比较

    Number.prototype.valueOf = function(){
        return 3;
    }
    
    new Number(2) == 3; // true
    

    这只是为了说明这种情况的存在,应该不会有人这么去改原型上的方法。

    假值的相等比较

    这部分应该是比较复杂的:

    "0" == null;            
    //false
    "0" == undefined;           
    //false
    
    "0" == false;           
    //true
    "0" == NaN;         
    //false
    "0" == 0;           
    //true
    "0" == "";          
    //false
    
    false == null;      
    //false
    false == undefined; 
    //false
    false == NaN;       
    //false
    false == 0;         
    //true
    false == "";        
    //true
    false == [];        
    //true
    false == {};        
    //false
    
    "" == null;         
    //false
    "" == undefined;    
    //false
    "" == NaN;          
    //false
    "" == 0;            
    //true
    "" == [];           
    //true
    "" == {};           
    //false
    
    0 == null;          
    //false
    0 == undefined;     
    //false
    0 == NaN;           
    //false
    0 == [];            
    //true
    0 == {};            
    //false
    

    解释如下:

    "0" == null;        
    //false:null转换为"null"
    "0" == undefined;   
    //false:undefined转换为"undefined"
    "0" == false;       
    //true:false转换为0;"0"转换为0
    "0" == NaN;         
    //false:NaN转换为"NaN"
    "0" == 0;           
    //true:"0"转换为0
    "0" == "";          
    //false:都是字符串,值不同
    
    false == null;      
    //false:false转换为0;null转换为"null",进而转换为数字,得到NaN
    false == undefined; 
    //false:false转换为0;undefined转换为"undefined",进而转换为数字,得到NaN
    false == NaN;       
    //false:false转换为0;与NaN不同
    false == 0;         
    //true:false转换为0
    false == "";        
    //true:false转换为0;""转换为0
    false == [];        
    //true:false转换为0;[]转换为0
    false == {};        
    //false:false转换为0;{}转换为NaN
    
    "" == null;         
    //false:""转换为0;null转换为"null",进而转换为数字,得到NaN
    "" == undefined;    
    //false:""转换为0;undefined转换为"undefined",进而转换为数字,得到NaN
    "" == NaN;          
    //false:""转换为0;与NaN不同
    "" == 0;            
    //true:""转换为0
    "" == [];           
    //true:""转换为0;[]转换为0
    "" == {};           
    //false:""转换为0;{}转换为NaN
    
    0 == null;          
    //false:null转换为"null",进而转换为数字,得到NaN
    0 == undefined;     
    //false:undefined转换为"undefined",进而转换为数字,得到NaN
    0 == NaN;           
    //false:0与NaN不同
    0 == [];            
    //true:[]转换为0
    0 == {};            
    //false:{}转换为NaN
    

    一些极端情况

    a. [] == ![]; //true
      []被转换为0,![]被转换为false,进而被转换为0。
    b. 2 == [2]; //true
      [2]调用数组的valueOf()方法,返回"2",进而被转换为2。
    c. "" == [null]; //true
      [null]调用数组的valueOf()方法,进而调用toString()方法,返回""。


    一些选用的原则

    1. 如果==两边有true或者false(指本身,不是经过类型转换以后的),绝对不使用==。
    2. 如果==两边有[],"",0,尽量不使用==。
    3. ==和===选取哪一个取决于是否允许比较双方进行强制类型转换。
    4. 不应该一味地使用===来避免考虑这些可能的问题,因为有时候隐式类型转换可以让代码更加简洁,只要用的对。‘
    5. typeof x == "function"typeof x != "undefined"这样的用法是完全正确且安全的,开发中也常用。

    经典的相等比较关系图

    GitHub上有一个经典的比较关系图,http://dorey.github.io/JavaScript-Equality-Table/,以表格的形式系统总结了宽松相等==,严格相等===,if()条件语句中使用不同数据类型对应的结果,很有意义:


    不等比较

    != 和 !==

    只要搞清楚上面详细描述的==和===,对应取反即可。

    > < >= <=

    这些比较的基础是 x < y:

    x < y会返回true或者false或者undefined。如果返回undefined,说明x,y两者至少有一个是NaN。比较算法中需要使用一个布尔值的标记LeftFirst作为参数。这个参数的作用是控制可能具有副作用的操作作用于x和y的顺序。这个标志是有必要的,因为在ECMAScript指定了从左到右的运算顺序,LeftFirst的默认值是true,表示x的表达式是在y的表达式左边的。如果LeftFirst值为false,情况相反,表明关于y的操作必须先于x进行。据此,比较操作规则如下:

    1. 如果LeftFirst值为true:
       1.1 ToPrimitive(x, hint Number)的结果记为px
       1.2 ToPrimitive(y, hint Number)的结果记为py
    2. 否则,运算顺序改为从右向左:
       2.1 ToPrimitive(y, hint Number)的结果记为py
       2.2 ToPrimitive(x, hint Number)的结果记为px
    3. 经过如上转换,如果px和py至少有一个的类型不为string:
       3.1 ToNumber(px)的结果记为nx
       3.2 ToNumber(py)的结果记为ny
       3.3 如果nx是NaN,返回undefined
       3.4 如果ny是NaN,返回undefined
       3.5 如果nx和ny的值相同,返回false
       3.6 如果nx是+0,ny是-0,返回false
       3.7 如果nx是-0,ny是+0,返回false
       3.8 如果nx是+Infinity,返回false
       3.9 如果ny是+Infinity,返回true
       3.10 如果ny是-Infinity,返回false
       3.11 如果nx是-Infinity,返回true
       3.12 如果nx的值小于ny,返回true;否则,返回false
    4. 如果px和py都是string:
       4.1 如果py是px的前缀,返回false
       4.2 如果px是py的前缀,返回true
       4.3 设置变量k,k表示px和py对应位上出现不同值时,位置的索引。
       4.4 记px的位置k上的字母对应的字符编码值为m
       4.5 记py的位置k上的字母对应的字符编码值为n
       4.6 如果m < n,返回true;否则返回false

    因此,> < >= <=就有如下的规则:

    RelationalExpression > ShiftExpression

    1. 记lref为RelationalExpression的运算结果值。
    2. 记lval为lref类型转换后获取的value值。
    3. 记rref为ShiftExpression的运算结果值。
    4. 记rval为rref类型转换后获取的value值。
    5. 设LeftFirst为false,将rval < lval按照上述规则进行运算,结果为r。
    6. 如果r是undefined返回false,否则返回r。

    RelationalExpression < ShiftExpression

    1. 记lref为RelationalExpression的运算结果值。
    2. 记lval为lref类型转换后获取的value值。
    3. 记rref为ShiftExpression的运算结果值。
    4. 记rval为rref类型转换后获取的value值。
    5. 将lval < rval按照上述规则进行运算,结果为r。
    6. 如果r是undefined返回false,否则返回r。

    RelationalExpression >= ShiftExpression

    1. 记lref为RelationalExpression的运算结果值。
    2. 记lval为lref类型转换后获取的value值。
    3. 记rref为ShiftExpression的运算结果值。
    4. 记rval为rref类型转换后获取的value值。
    5. 设LeftFirst为false,将rval < lval按照上述规则进行运算,结果为r。
    6. 如果r是true或者undefined返回false,否则返回true。

    RelationalExpression <= ShiftExpression

    1. 记lref为RelationalExpression的运算结果值。
    2. 记lval为lref类型转换后获取的value值。
    3. 记rref为ShiftExpression的运算结果值。
    4. 记rval为rref类型转换后获取的value值。
    5. 将lval < rval按照上述规则进行运算,结果为r。
    6. 如果r是true或者undefined返回false,否则返回true。

    相关文章

      网友评论

        本文标题:JavaScript中比较运算的详细总结

        本文链接:https://www.haomeiwen.com/subject/iidjwttx.html