An old dog learns code

相等操作符

September 15, 2018



引子

在网上看到了几个关于相等操作符的面试题,除了 1,其他都一头雾水。 再次证明基础不牢固,有必要深入学习一下。

1. []==[]     //false
2. []==![]    //true
3. {}==!{}    //false
4. {}==![]    //Uncaught SyntaxError: Unexpected token ==
5. ![]=={}    //false
6. []==!{}    //true
7. undefined==null //true

操作符运算规则

相等操作符: == 全等操作符: ===

根据 JS 高程里的定义,操作符如上所示,但高程里全等操作符的计算规则有点语焉不详,查询 ECMA 后列举如下:

全等操作符判定规则 - Strict Equality Comparison(SEC)
  1. 如果x, y类型不同,返回false
  2. 如果x, yundefined,返回true
  3. 如果x, ynull,返回true
  4. 如果x, ynumber类型,那么:

    1. 如果x, y其中之一,或二者均为NaN,返回false
    2. 如果xy的值相同,返回true
    3. 如果x+0y-0,返回true
    4. 如果x-0y+0,返回true
    5. 其他情况返回false
  5. 如果x, ystring类型,那么当二者长度相同且字符顺序也一致时,返回true;否则返回false
  6. 如果x, yBoolean类型,那么当x, y均为true或均为false时,返回 true;否则返回false
  7. x, y为对象,二者指向同一对象时返回true;否则返回false

例 1

1 === []; //false
//1为number,[]为对象,类型不同,根据SEC-1返回false

undefined === undefined; //true
//类型相同,根据SEC-2返回true

null === null; //true
//类型相同,根据SEC-3返回true

null === undefined; // false
//null为object,undefinedw为undefined,类型不同,根据SEC-1返回false

NaN === 1; //false
//类型相同,根据SEC-4.1返回false

NaN === NaN; //false
//类型相同,根据SEC-4.1返回false
//NaN是唯一与自身不相等的数

1 === true; //false
//1为number,true为Boolean,类型不同,返回false

'' === false; //false
//''为string,false为Boolean,类型不同,返回false
相等操作符判定规则 - Abstract Equality Comparison(AEC)
  1. 如果 x, y 类型相同,按全等操作符判定规则进行比较
  2. 如果 x, y 一方为undefined,另一方为null,返回true
  3. 如果 x, y 一方为number,另一方为string,将string转换为number后再做比较
  4. 如果 x, y 一方为Boolean,将其转换为number后再做比较
  5. 如果 x, y 一方为对象,另一方为string, number, symbol,将对象转换为基本类型后再做比较
  6. 其他情况返回false

AEC-5中涉及对象与基本类型的转换,ECMA中由内置方法ToPrimitive(x)实现,基本规则如下:

对象转换基本类型规则 - Object To Primitive(OTP)
  1. 如果x为日期Date类型,调用x.toString()返回日期字符串
  2. x为其他类型对象,首先调用x.valueOf(),如结果为基本类型,返回该值
  3. 如果2不满足,继续调用x.toString(),如结果为基本类型,返回该值
  4. 3不满足,抛出TypeError异常

    注:

    1. 对象的valueOf()方法通常返回自身,因此绝大多数情况,转换为基本类型调用的是toString()方法
    2. 对象的默认toString()方法为Object.prototype.toString(),其输出值为字符串'[object objType]',其中objType为对象的类型,所以空对象{}.toString()的输出为'[object Object]'(注意一个是小写o,一个是大写O)
    3. 数组对象Array重写了toString()方法,其输出为Array.join('');如果不重写,其输出应为'[object Array]'

例 2

undefined == null; //true
//根据AEC-2,返回true

1 == true; //true
//根据AEC-4,将Boolean转换为number,Number(true)为1,变为1 == 1
//根据AEC-1及SEC-4.2,返回true

'' == false; //true
//根据AEC-4,将Boolean一方转换为number,Number(false)为0,变为'' == 0
//根据AEC-3,将''转换为number,Number('')为0, 变为0 == 0
//根据AEC-1及SEC-4.2,返回true

'' == []; //true
//根据AEC-3, OTP-注-2, OTP-注-3,空数组对象[]调用toString()方法
//[].toString()输出为'',变为'' == ''
//根据AEC-1及SEC-5,返回true

'' == {}; //false
//根据AEC-3, OTP-注-2,空对象{}调用toString()方法,
//{}.toString()输出为'[object Object]',变为'' == '[object Object]'
//根据AEC-1及SEC-5,返回false

解题及分析

规则介绍完了,下面根据规则解题

题 1

[] == []; //false

分析:

  • 二者均为数组对象,类型相同
  • 二个空数组分别指向2个数组,根据SEC-7,返回false

题 2

[] == ![]; //true

分析:

  • !逻辑非运算符先计算,![]值为false,表达式变为[] == false
  • 类型不同,一方为Boolean类型,应用AEC-4,表达式变为[] == 0
  • 一方为对象,应用AEC-5OTP-3OTP-注-3,表达式变为'' == 0
  • 继续应用AEC-3,表达式变为0 == 0,返回true

题 3

{}==!{}    //false

分析:

  • !逻辑非运算符先计算,表达式变为{} == false
  • 类型不同,且一方为Boolean类型,根据AEC-4,表达式变为{} == 0
  • 一方为对象,应用AEC-5OTP-3OTP-注-2,表达式变为'[object Object]' == 0
  • 一方为number,应用AEC-3,表达式变为NaN == 0
  • 二者同为number类型,应用SEC-4.1,返回false

题 4

{}==![]    //Uncaught SyntaxError: Unexpected token ==

分析:

  • 浏览器会将{}部分解析为代码块,表达式等效为==![],因此报错
  • 如果将表达式修改为({}) == ![],浏览器将能正确解析,分析过程及结果同题3

题 5

![] == {}; //false

分析:

  • !逻辑非运算符先计算,表达式变为false == {}
  • 剩余分析步骤同题3,表达式返回false

题 6

[] == !{}; //true

分析:

  • !逻辑非运算符先计算,表达式变为[] == false
  • 剩余分析步骤同题2,返回true

题 7

undefined == null; //true

分析:

  • 类型不同,应用AEC-2,返回true