JS基础回顾-运算符2

###比较运算符(<,>,<=,>=)

######执行关系比较运算符的结果总是Boolean类型。

#####本以为比较运算符怎么也比等值运算符简单的多,没想到比隐式类型转换还要恶心。 其实恶心到人的还并不是比较运算符的应用层,而是原理层。 ###一、应用层 对于日常开发,只看这两条就足够了,第二部分原理层可以不看

(x<y 的比较)
1.如果 Type(px) 和 Type(py) 得到的结果不都是 String 类型,那么
    a.让 nx 为调用 ToNumber(px) 的结果。因为 px 和 py 都已经是基本数据类型(primitive values 也作原
始值),其执行顺序并不重要。
    b.让 ny 为调用 ToNumber(py) 的结果。
    c.如果 nx 是 NaN,返回 undefined
    d.如果 ny 是 NaN,返回 undefined
    e.如果 nx 和 ny 的数字值相同,返回 false
    f.如果 nx 是 +0 且 ny 是 -0,返回 false
    j.如果 nx 是 -0 且 ny 是 +0,返回 false
    h.如果 nx 是 +∞,返回 false
    i.如果 ny 是 +∞,返回 true
    g.如果 ny 是 -∞,返回 false
    k.如果 nx 是 -∞,返回 true
    l.如果 nx 数学上的值小于 ny 数学上的值(注意这些数学值都不能是无限的且不能都为 0),返回 ture。
否则返回 false。
2.否则,px 和 py 都是 Strings 类型
    a.如果 py 是 px 的一个前缀,返回 false。(当字符串 q 的值可以是字符串 p 和一个其他的字符串 r 拼接而
成时,字符串 p 就是 q 的前缀。注意:任何字符串都是自己的前缀,因为 r 可能是空字符串。)
    b.如果 px 是 py 的前缀,返回 true。
    c.让 k 成为最小的非负整数,能使得在 px 字符串中位置 k 的字符与字符串 py 字符串中位置 k 的字符不
相同。(这里必须有一个 k,使得互相都不是对方的前缀)
    d.让 m 成为字符串 px 中位置 k 的字符的编码单元值。
    e.让 n 成为字符串 py 中位置 k 的字符的编码单元值。
    f.如果 n<m,返回 true。否则,返回 false。

翻译如下:(我们以比较运算符 < 为例)

  if(比较运算符左边是String类型的值 && 比较运算符右边是String类型的值){
      将会依次比较《比较运算符》两边索引相同的字符在Unicode字符集中的编码值;举个例子:'abcf' < 'abde' ; 
      解释器会先比较'abcf'的'a'与'abde'的'a'在Unicode字符集中的编码值,看看到底谁大(获取一个字符的在
      Unicode字符集中的编码值的方法是:charCodeAt(索引)比如要获取'abcf'的'a'的编码=>'abcf'.charCodeAt(0),
      'abce'.charCodeAt(0)得到的都是97),相等就去比较'b'和'b',发现也是相等,再去比较'c'和'd','c'对应的
      Unicode编码值为99,'d'对应的Unicode编码值为100, 'c' < 'd',解释器执行到此结束,直接返回true,
      不会再对'f' 和 'e'进行比较!
  }else{
      1.左右两边全部转换成Number类型(对象转换成数字的规则是否还记得);
      2.进行数字的比较
      注意:
      c.如果 nx 是 NaN,返回 undefined
      d.如果 ny 是 NaN,返回 undefined
      这两条规则可能会给你带来误导,因为开篇规范已经定义:比较运算符的结果总是Boolean类型,
      为什么还会出现undefined?
      你现在可以先这么理解:比较运算符的运算结果会有三个可能值[true,false,undefined],但是,JS解释器在最终返回结果时,
      会帮我们把这些值转换成Boolean类型;

      h.如果 nx 是 +∞,返回 false
      i.如果 ny 是 +∞,返回 true
      g.如果 ny 是 -∞,返回 false
      k.如果 nx 是 -∞,返回 true
      h,i,g,k条件成立的前提是:两边的值不相等,Infinity < Infinity 为false
      
  }

这里还是要提示一下大家容易忽视的点: 你是否还记得 == 的 隐式类型转换中,null和undefined是不进行隐式类型转换的,但是null和undefined是相等的。比如, ######null == 0 ,返回false。但是null <= 0 ,返回true。 ######null == undefined ,返回true。但是null <= undefined ,返回false。 一定要格外注意这两个特殊的值。

自我检测:

1.
    var a = [4,2],b = [0,4,3];
    a < b //结果?
2.
    var a = { b: 42 },b = { b: 43 };
    a < b //结果?
    a == b //结果?
    a <= b //结果?

###二、原理层 以下为ECMAScript5.0关于比较运算符的规范

11.8.1 The Less-than Operator ( < )

产生式 RelationalExpression : RelationalExpression < ShiftExpression 按照下面的过程执行 :

  1. 令 lref 为解释执行 RelationalExpression 的结果 .
  2. 令 lval 为 GetValue(lref).
  3. 令 rref 为解释执行 ShiftExpression 的结果 .
  4. 令 rval 为 GetValue(rref).
  5. 令 r 为抽象关系比较算法 lval < rval( 参见 11.8.5) 的结果
  6. 如果 r 为 undefined,返回 false. 否则 , 返回 r.

11.8.2 The Greater-than Operator ( > )

产生式 RelationalExpression : RelationalExpression > ShiftExpression 按照下面的过程执行 :

  1. 令 lref 为解释执行 RelationalExpression 的结果 .
  2. 令 lval 为 GetValue(lref).
  3. 令 rref 为解释执行 ShiftExpression 的结果 .
  4. 令 rval 为 GetValue(rref).
  5. 令 r 为为抽象关系比较算法 rval < lval( 参见 11.8.5) 的结果,参数 LeftFirst 设为 false
  6. 如果 r 为 undefined,返回 false. 否则 , 返回 r.

11.8.3 The Less-than-or-equal Operator ( <= )

产生式 RelationalExpression : RelationalExpression <= ShiftExpression 按照下面的过程执行 :

  1. 令 lref 为解释执行 RelationalExpression 的结果 .
  2. 令 lval 为 GetValue(lref).
  3. 令 rref 为解释执行 ShiftExpression 的结果 .
  4. 令 rval 为 GetValue(rref).
  5. 令 r 为为抽象关系比较算法 rval < lval( 参见 11.8.5) 的结果,参数 LeftFirst 设为 false
  6. 如果 r 为 true 或者 undefined ,返回 false. 否则 , 返回 true.

11.8.4 The Greater-than-or-equal Operator ( >= )

产生式 RelationalExpression : RelationalExpression >= ShiftExpression 按照下面的过程执行 :

  1. 令 lref 为解释执行 RelationalExpression 的结果 .
  2. 令 lval 为 GetValue(lref).
  3. 令 rref 为解释执行 ShiftExpression 的结果 .
  4. 令 rval 为 GetValue(rref).
  5. 令 r 为抽象关系比较算法 lval < rval( 参见 11.8.5) 的结果
  6. 如果 r 为 true 或者 undefined ,返回 false. 否则 , 返回 true.

11.8.5抽象关系比较算法

以 x 和 y 为值进行小于比较(x<y 的比较),会产生的结果可为 true,false或 undefined
(这说明 x、y 中最少有一个操作数是 NaN)。除了 x 和 y,这个算法另外需要一个名为 LeftFirst
 的布尔值标记作为参数。这个标记用于解析顺序的控制,因为操作数 x 和 y 在执行的时候会有潜
在可见的副作用。LeftFirst 标志是必须的,因为 ECMAScript 规定了表达式是从左到右顺序执行的。
LeftFirst 的默认值是 true,这表明在相关的表达式中,参数 x 出现在参数 y 之前。
如果 LeftFirst 值是 false,情况会相反,操作数的执行必须是先 y 后 x。这样的一个小于比较的执行步骤如下:

1.如果 LeftFirst 标志是 true,那么
    a.让 px 为调用 ToPrimitive(x, hint Number) 的结果。
    b.让 py 为调用 ToPrimitive(y, hint Number) 的结果。
2.否则解释执行的顺序需要反转,从而保证从左到右的执行顺序
    a.让 py 为调用 ToPrimitive(y, hint Number) 的结果。
    b.让 px 为调用 ToPrimitive(x, hint Number) 的结果。
3.如果 Type(px) 和 Type(py) 得到的结果不都是 String 类型,那么
    a.让 nx 为调用 ToNumber(px) 的结果。因为 px 和 py 都已经是基本数据类型(primitive values 也作原
始值),其执行顺序并不重要。
    b.让 ny 为调用 ToNumber(py) 的结果。
    c.如果 nx 是 NaN,返回 undefined
    d.如果 ny 是 NaN,返回 undefined
    e.如果 nx 和 ny 的数字值相同,返回 false
    f.如果 nx 是 +0 且 ny 是 -0,返回 false
    j.如果 nx 是 -0 且 ny 是 +0,返回 false
    h.如果 nx 是 +∞,返回 false
    i.如果 ny 是 +∞,返回 true
    g.如果 ny 是 -∞,返回 false
    k.如果 nx 是 -∞,返回 true
    l.如果 nx 数学上的值小于 ny 数学上的值(注意这些数学值都不能是无限的且不能都为 0),返回 ture。
否则返回 false。
4.否则,px 和 py 都是 Strings 类型
    a.如果 py 是 px 的一个前缀,返回 false。(当字符串 q 的值可以是字符串 p 和一个其他的字符串 r 拼接而
成时,字符串 p 就是 q 的前缀。注意:任何字符串都是自己的前缀,因为 r 可能是空字符串。)
    b.如果 px 是 py 的前缀,返回 true。
    c.让 k 成为最小的非负整数,能使得在 px 字符串中位置 k 的字符与字符串 py 字符串中位置 k 的字符不
相同。(这里必须有一个 k,使得互相都不是对方的前缀)
    d.让 m 成为字符串 px 中位置 k 的字符的编码单元值。
    e.让 n 成为字符串 py 中位置 k 的字符的编码单元值。
    f.如果 n<m,返回 true。否则,返回 false。

注:使用或代替的时候要注意,这里的步骤 3 和加号操作符 + 算法 (11.6.1) 的步骤 7 的区别。

注:String 类型的比较使用了其编码单元值的作为一个简单的词法表序列去比较。这里不打算使用更复杂的、
语义化的字符或字符串序列,和 Unicode 规范的整理序列进行比较。因此,字符串的值和其对应的 Unicode 
标准的值是不相同的。实际上,这个算法假定了所有字符串已经是正常化的格式。同时要注意,对于字符串
拼接追加的字符的时候,UTF-16 编码单元值的词法表序列是不同于代码点值的序列的。

规范果然还是规范,严谨和晦涩并存。 11.8.1-11.8.4 中,你只需要看标出加粗的部分,其他可以略过,我们可以发现一些猫腻。

< > <= >=
实际运算 left < right right < left right < left left < right
leftFirst true false false true
运算结果
(r)
######1.实际运算
我们在写JS代码时,确实是在用<,>,<=,>=4个比较运算符,但是,在编译器生成代码时都是使用的同一个运算符(<)进行比较运算,编译器也想偷懒,请你原谅她。
######2.leftFirst
leftFirst是个啥?当js引擎执行编译器生成的代码时,如果只有(<)显然是不行的,比如,我们写的代码是 x > y, 如果编译器生成的可执行代码是 x < y 那肯定是达不到开发者想要的结果的,所以,为了使程序正常运行,编译器还需要把 x 和 y互换,推理出编译器生成的真实的可执行代码其实是 y < x,但是!还不够,为什么呢?因为根据ECMAScript规范,js的执行顺序必须是从左到右的,这里,我们要清晰地认识执行顺序的本质是,表达式取值的顺序(在这里可以理解为 x 和 y 获取原始值的顺序,因为当涉及到对象与对象的比较时,这条规则是非常有用的),因此,为了符合规范,编译器为每个比较运算设置了隐藏属性,(我感觉应该是每条表达式都有这个隐藏属性,但我无从验证)这个属性就是leftFirst,

#####leftFirst的值是Boolean类型 true 代表 编译器生成的带有比较运算符语句的代码后,js引擎执代码行时会先把(<)左边转化成原始值,再把右边转换成原始值 false 代表 编译器生成的带有比较运算符语句的代码后,js引擎执代码行时会先把(<)右边转化成原始值,再把左边转换成原始值

这样就算是遵循了ECMAScript规范了。

######3.运算结果 上面说到,比较运算符的运算结果会有三个可能值[true,false,undefined],如果返回了undefined,那么就代表比较运算符两边至少有一个是NaN,根据NaN的特性(NaN不等于任何值,包括本身)这个也很好说得通,因为NaN参与的比较无意义。如果反回了undefined,一律按false处理(上表可以体现)。 < , > 的运算结果应该很好理解了,对<= ,>= 结果的理解我们需要借助数学中的集合思想,准确了说应该是补集思想,<= 是 >的一个补集,>= 是 < 的一个补集,看图我们也能得知,除去undefined的情况,<= 的运算结果就是 > 的运算结果的取反,>= 的运算结果就是 < 的运算结果的取反。希望对你有帮助。

收工。