有段代码如下:

1 if (![] == []) {
2     //Code
3 }

![] == [],true or false?

我们都知道,ECMAScript中有两种类型的相等操作符:

  • 全等与不全等——直接比较而不转换类型
  • 相等与不相等——先转换类型再比较

全等与不全等的逻辑比较简单,而今天我们要关注的是我们平时用得比较多的第二种操作符:相等与不相等。当我们对两个操作数用 == 进行比较的时候,我们分两种情况:== 两边操作数的类型相同与不相同。我们都知道类型不相同时需要先转换类型,但是其中的转换规则是什么样的呢?也许许多人都不曾仔细研究过,今天我们就通过分析![] == []来深入研究一下 == 的比较机制。

两边类型相同

当两边的类型相同时,比较的逻辑就跟 === 一样:

  • 如果类型都是基本类型,则直接比较其值
  • 如果都是引用类型,则比较其引用地址(是否指向同一个对象)

如以下代码:

1 console.log(5 == 5); //true
2 console.log('abc' == 'abc'); //true
3 console.log([] == []); //false,两个不同的引用地址
4 var a = [], b = a;
5 console.log(a == b); //true

也许有人会说,既然[] == []为false,那么前面那个![] == []的结果就是true咯?!没错,但是其中的判断逻辑不是这么简单的,因为![]等于false,所以这个比较就相当于false == [],这时两边的类型不一样的,一个是Boolean,一个是Array,所以我们不能简单通过[] == []为false来判断![] == []为true,当我们将[]换成{},这时结果就不一样了。

1 console.log([] == []); //false
2 console.log(![] == []); //true
3 console.log({} == {}); //false
4 console.log(!{} == {}); //false

也许你会感到糊涂,[]与{}同样是引用类型,为什么![] == []与!{} == {}的结果不一样呢?那是因为Array有其特殊的地方,这时后话,我们先来看下当 == 两边类型不相等时的转换规则。

两边类型不相同

当 == 两边操作数的类型不相同时,会将操作数的类型进行转换相同的类型,通常也叫强制转型,然后再比较其相等性,比如:

1 console.log(5 == '5'); //true,将字符'5'转换成数字5再比较
2 console.log(false == 0); //true,先将布尔值false转换成数字0

在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:

  • 如果有一个操作数是布尔值,则在比较相等性之前现将其转换成数值——false转换成0,而true转换成1
  • 如果一个操作数是字符串,另外一个操作数是数值,在比较相等性之前现将其转换成数值(用Number()方法)
  • 如果一个操作数是对象(引用类型),另外一个操作数不是,则调用对象的valueOf()方法,用得到的原始值按照前面的规则进行比较(如果得到的原始值还是对象,则调用原始值的toString()方法再进行比较)

另外有几个需要注意的地方:

  • null和undefined是相等的(undefined派生于null)
  • 要比较相等性之前,不能将null和undefined转换成其他值(即除null和undefined之外的值都不和null与undefined相等)
  • 一般来说,如果a == b为true的话,那么a != b即为false,反之也可行,但是有一个例外,那就是NaN,NaN不等于任何操作数,包括它自己,即NaN == NaN和NaN != NaN都为false

根据以上规则,我们可以自己写一个比较相等的函数:

01 function Equal(a, b) { 
02     var typeA = typeof a, typeB = typeof b;
03    
04     //如果有一个操作数是NaN,则总返回false
05     //isNaN(undefined)返回true
06     if ((isNaN(a) && typeA === 'number') || (isNaN(b) && typeB === 'number')) { return false; }
07    
08     //如果操作数类型相等,则比较它们的值,否则转换类型
09     if (typeA === typeB) {
10         return a === b;
11     } else {   
12         //将undefined转成null(实际上是不转的,此处只是方便后面的比较)
13         if (typeA === 'undefined') { a = null; }
14         if (typeB === 'undefined') { b = null; }
15        
16         if (a === null || b === null) {
17             return a === b;
18         } else if (typeA === 'object' || typeB === 'object') {
19             //如果有一个是对象
20             //先调用其valueOf方法,如果返回还是object,则调用其toString方法
21             var o = (typeA === 'object' ? a : b).valueOf(), other = o === a ? b : a;
22             if (typeof o === 'object') {
23                 o = o.toString();
24             }
25             return Equal(o, other);
26         } else {           
27             //如果有Boolean,将其转成Number
28             if (typeA === 'boolean') { a = a ? 1 : 0; }
29             if (typeB === 'boolean') { b = b ? 1 : 0; }
30             //如果其中一个类型是Array,另外一个是Number,Number()返回number或NaN
31             if (typeA === 'string' && typeB === 'number') { a = Number(a); }
32             if (typeB === 'string' && typeA === 'number') { b = Number(b); }           
33             return Equal(a, b);
34         }
35     }
36 }
37 console.log(Equal([], [])); //false
38 console.log(Equal(![], [])); //true
39 console.log(Equal({}, {})); //false
40 console.log(Equal(!{}, {})); //false
41 console.log(Equal(NaN, NaN)); //false
42 console.log(Equal(undefined, null)); //true
43 console.log(Equal(false, null)); //false
44 console.log(Equal(false, 0)); //true
45 console.log(Equal(true, 2)); //false

Array.toString()

那么为什么![] == []为true呢?其比较步骤如下:

  • ![]为false,式子相当于false == [],两边类型不同且有一个对象[]
  • 右边的[]调用valueOf()方法,得到的还是对象[],因此再调用toString()的方法,Array的toString()方法相当于Array.join(','),例如[1, 2, 3].toString()等于'1,2,3',而[].toString()等于一个空字符串''({}.toString()等于'[object Object]'),则此时式子相当于false == ''
  • 因为存在布尔值为false,将其转成数值0,此时式子变成0 == ''
  • 最后将右边的空字符串''用Number()方法转成0,0 == 0为true,所以![] == []

因此:

1 console.log(![] == []); //true
2 console.log(0 == []); //true
3 console.log(0 == ['']); //true
4 console.log('0' == []); //false
5 console.log('0' == [0]); //true
6 console.log(true == [1]); //true

总结:由于相等和不相等操作符存在类型转换问题,而为了保持代码中数据类型的完整性,推荐使用全等和不全等操作符(已经强调很久了)。

JavaScript的相等(==)与全等(===)的更多相关文章

  1. JavaScript自动计算价格和全选

    JavaScript自动计算价格和全选,价格自增加减,复选框,反选,全选. 如图: 如图: CSS代码 @charset "gb2312"; /* CSS Document */ ...

  2. javascript小技巧(非常全)

    事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture();  event.srcE ...

  3. Javascript实现CheckBox的全选与取消全选的代码(转)

    js函数 复制代码 代码如下: <script type="text/javascript"> function checkAll(name) { var el = d ...

  4. JavaScript案例四:全选练习

    JavaScript实现全选,全不选等效果... <!DOCTYPE html> <html> <head> <title>JavaScript全选练习 ...

  5. (网页)javascript小技巧(非常全)

    事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture();  event.srcE ...

  6. JavaScript -- 操作input CheckBox 全选框

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. JavaScript:让浏览器全屏显示

    并不是所有人都会按F11让浏览器全屏显示~~~ 一.直接上代码 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xh ...

  8. Javascript与Flash通信全解析

    原文:https://www.imququ.com/post/39.html Flash已经提供了ExternalInterface接口与JavaScript通信,ExternalInterface有 ...

  9. JavaScript基础篇最全

    本章内容: 简介 定义 注释 引入文件 变量 运算符 算术运算符 比较运算符 逻辑运算符 数据类型 数字 字符串 布尔类型 数组 Math 语句 条件语句(if.switch) 循环语句(for.fo ...

  10. JavaScript数组所有API全解密

    全文共13k+字,系统讲解了JavaScript数组的各种特性和API. 数组是一种非常重要的数据类型,它语法简单.灵活.高效. 在多数编程语言中,数组都充当着至关重要的角色,以至于很难想象没有数组的 ...

随机推荐

  1. u-boot.bin生成过程分析

    ELF格式“u-boot”文件的生成规则如下,下面对应Makefile的执行过程分别分析各个依赖. $(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $( ...

  2. count_char

    import java.util.Scanner; public class count_char { public static void main(String args[]) { int cou ...

  3. 基于vue来开发一个仿饿了么的外卖商城(二)

    一.抽出头部作为一个组件,在底部导航的时候可以相应的显示不同的标题 技术点:使用slot进行组件间的通信:父组件给子组件传值(子组件里面通过props接收父组件传过来的数据) 查看链接:https:/ ...

  4. R语言学习笔记(十三):零碎知识点(36-40)

    36--diag() 如果它的参数是一个矩阵,它返回的是一个向量 如果它的参数是一个向量,它返回的是一个向量 如果它的参数是一个标量,它返回的是指定大小的单位矩阵 > diag(2) [,1] ...

  5. Android面试收集录 OpenGL ES

    1.如何用OpenGL ES绘制一个三角形? 编写一个类实现Renderer接口,实现onDrawFrame方法,onSurfaceChanged方法,onSurfaceCreated方法 编写一个类 ...

  6. JAVA中堆栈和内存分配详解(摘抄)

    在Java中,有六个不同的地方可以存储数据: 1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存 ...

  7. iOS开发中常见的一些异常

    iOS开发中常见的异常包括以下几种NSInvalidArgumentExceptionNSRangeExceptionNSGenericExceptionNSInternallnconsistency ...

  8. 「日常训练」「小专题·图论」 Cow Contest (1-3)

    题意 分析 问题是要看出来这是个floyd闭包问题.我没看出来- - 分析之后补充. 代码 // Origin: // Theme: Graph Theory (Basic) // Date: 080 ...

  9. 第十八篇 模块与包--time&random模块&模块导入import(os.path.dirname(os.path.abspath(__file__)))

    模块 在Python中, 一个.py文件就称为一个模块. 使用模块的好处: 1. 最大的好处就是大大提高了代码的可维护性 2. 编写代码不必从零开始.一个模块编写完毕,就可以被其他地方引用.在写其他程 ...

  10. 第三篇 Python执行方式和变量初始

    第一个Python程序 可以打开notepad或者其他文本编辑器,输入:print("Hello Python!"),将文件保存到任意盘符下,后缀名是  .py 两种python程 ...