Skip to content

面试官:说说你对相等操作符的理解 #15

Open

Description

@RED523

背景

前几天,同事间在闲谈的时候,讨论了一些面试题目:

Q: null == undefined 等于什么?
A: false

很好,那么下面呢?

Q: [] == false 等于什么?
A: fasle  ❎(正确的结果是 true)

Q: undefined == 0 呢?
Q: undefined == 1 呢?
......

是的,上面的这些问题,都是一些老掉牙的面试题目了,都被玩烂了,但是,有多少人真正理解呢?比如上面的 [] == false 等于什么?会不会有人这么理解: == 操作符左右两边的操作数,值的类型是可以忽略的,只用比较操作数转化为它的原始值就可以,所以 [] 为 true, true === false 结果为 fasle。但是呢?又感觉比较牵强,有点问题,总觉得好像是有一套完整的比较规则。

我相信,即便是工作 5,6 年的开发,类似的问题也不一定全部答出(懂的读懂)。好,下面我们就详细的讲解下相等操作符相关的知识,以及面试官真正想考察的点。

我们都知道,判断两个变量是否相等是编程中最重要的操作之一。像字符串、布尔值、数值的比较都比较直观,但是对象的比较就有点抽象了。ECMAScript 中提供了两组比较的模式,第一组是等于、不等于,这种模式比较特殊,比较之前操作数会先进行类型转换,正因如此,就有人提出这种转换是否真的有必要?所以后面的标准中,出现了第二组比较模式,全等、不全等。这种模式也是现在主流的变量的比较方式。

等于、不等于

ECMAScript 中 ==表示等于,用!=表示不等于。如果==操作符左右两边的操作数相等,就返回true,如果!= 操作符左右两边的操作数不相等,就返回 true。相信每个人的知道。但是具体是怎么比较的呢?

比较之前,这两个操作符都会先进行强制类型转换,再确定操作数是否相等。下面是比较的规则(很重要):

  1. 如果任一操作数是布尔值,会将其转换为数值再比较是否相等。false 转换为 0,true 转换为 1;

  2. 如果一个操作数是字符串,另外一个操作数是数值,会将字符串其转换为数值,再比较是否相等;

    当操作数都是 字符串 时,JS 会进行 按字典序(lexicographical)比较,也叫 逐字符 UTF-16 码点比较。

    例如:"10" < "9",结果是 true,为什么呢?比较步骤是这样的:

    1. 第一步:比较第一个字符,'1' 和 '9'

    2. 第二步:将字符转为 UTF-16 码点,'1'对应49,'9'对应 57。可以使用charCodeAt方法转换,'1'.charCodeAt(0) < '9'.charCodeAt(0) 得到 49 < 57

    3. 第三步:比较字符, 49 < 57 结果为 true。因为 '1' 的码点比 '9' 小,整个字符串就不需要继续比较!

    由于字典序比较是逐字符的,一旦某个字符已经决定大小,就不用继续了,这和字典排序是一样的:

    1. "apple" < "banana"
    2. 因为 'a' < 'b'
    3. 不需要看后面的 "pple"、"anana"
  3. 如果一个操作数是对象,另外一个操作数不是,则会使用对象的valueOf() 方法将其转换为原始类型,再根据前面的规则进行比较;

    如果使用对象的valueOf() 方法转换后还是对象,则会尝试使用 toString()方法转换为字符串,再根据前面的规则进行比较。

  4. 如果两个操作数都是对象,则要比较两个操作数是否都指向同一个地址,是的话就返回 true,反之返回 false。

当然在比较的时候,也要遵循下面的特殊规则:

  • null 和 undefined 相等
  • null 和 undefined 不能转换为其它类型的值再进行比较
  • 如果任一操作数是 NaN,则相等操作符都会返回 fasle,不相等操作符返回 true。记住:即便两个操作数都是 NaN,相等操作符都返回 false,即:NaN 不等于 NaN

下面是一些常见的特殊情况和比较的结果:

null == undefined // true
undefined == 0 // false
null == 0 // false

'NaN' == NaN // false
6 ==  NaN // false
NaN == NaN // false
NaN != NaN // true

true == 1 // true
true == 2 // false
false == 0 // true

'5' == 5 // true
'5' == true // false
[] == 0 // true
[] == [] // false
[] == ![] // true

我们挑选上面几个例子来实操一下,并且描述下比较的过程:

例子一:

"5" == 5 // true
   ↓ Number("5") == 5
 5 == 5
   ↓
true

例子二:

"5" == true // false
   ↓ "5" == Number(true)
"5" == 1
   ↓ Number("5") == 1
 5 == 1
   ↓ 
false

例子三:

[] == 0 // true
   ↓ [].valueOf() == 0
[] == 0
   ↓ [].toString() == 0
"" == 0
   ↓ Number("") == 0
0 == 0
   ↓   
true

例子四:

[] == [] // false
   ↓ 引用地址不相等
true

例子五:有点特殊,遇到!操作符,会先将操作数转化为 Boolean 类型,然后再取反

[] == ![] // true
   ↓ [] == !true
[] == false
   ↓ [] == Number(false)
[] == 0
   ↓ [].valueOf() == 0
[] == 0
   ↓ [].toString() == 0
"" == 0
   ↓ Number("") == 0 
0 == 0
   ↓  
true

小结:用等于、不等于操作符比较两个变量的时候,在遵循特殊情况(上面的 3 点)下,会按照上面 1-4 的顺序进行比较,最终都会转换为数值进行比较。

全等、不全等

ECMAScript 中 ===表示全等,用!==表示不全等。全等、不全等与等于、不等于类似,但是在比较时不进行类型的转换,两个操作数只有在不转换类型的前提下相等,才会返回 true。

下面是一些常见的特殊情况和比较的结果:

null === undefined // false
undefined === 0 // false
null === 0 // false

'NaN' === NaN // false
6 ===  NaN // false
NaN === NaN // false
NaN !== NaN // true

true === 1 // false
true === 2 // false
false === 0 // false

'5' === 5 // false
'5' === true // false
[] === 0 // false
[] === [] // false
[] === ![] // false

简单对比两种模式,例如下面的代码:

55 == '55' // true 转换后相等
55 === '55' // false,不相等,因为数据类型不同

55 != '55' // fasle,转换后相等
55 !== '55' // true,不相等,因为数据类型不同

55 !== '55'也可以这么问:”数值 55 和字符串 55 有区别吗?“答案是:“有”(true)

总结

上面详细介绍了相等操作符的两种模式,希望能够帮助到大家。这里提一下,由于等于、不等于操作符存在类型转换问题,因此推荐使用全等、不全等操作符。这样有助于在代码中保持数据类型的完整性,并且也更加清晰易懂。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions