从入门到入土JS知识(一):关系操作符和相等操作符

时间:2020-09-08 11:56:00 来源:互联网 热度: 作者: 佚名 字体:

摘要

在浏览技术网时,无意中发现一个同行问的问题,问题如下:‘ {} == {} 或 {} > {} 输出false,为什么{} >= {}输出true ? ’。很有趣的问题,对啊为什么会这样呢,百思不得其解。为了理清这问题中的解题关键,我翻阅了JS红宝书以及温习了MDN网站中对关系运算符和相等运算符的知识点,终于是解开了问题的迷雾,看到了庐山真相。

JS操作符知识回顾

关系运算符

比较两个操作数的值,关系操作符会先将操作数转为原始值,使它们类型相同,再进行比较运算。

关系操作符的比较规则

小于、大于、小于等于和大于等于等关系操作符进行比较操作时,与ECMAScript的其他操作符一样,当关系操作符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作。其中这些奇怪的操作需要遵守以下对应的规则:

  • 如果两个操作数是数值,则执行数值比较。

  • 如果两个操作数是字符串,则比较两个字符串对应的字符串编码值。

  • 如果一个操作数是数值,则将另一个操作数转换为数值,然后执行数值比较。

  • 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。

  • 如果一个操作数是对象,则调用这个对象的valueOf()方法,用得到的结果按照前面的规则执行比较。如果对象没有valueOf()方法,则调用toString()方法,并用得到的结果根据前面的规则执行比较。

  • 任何操作数与NaN进行关系比较,结构都是false。

关系比较实例分析

为了探究对象操作数的关系比较,这里就省去了普通操作数的比较实例分析。感兴趣的小伙伴可以查阅mdn中的定义与实例分析,以下将由单一性到多元性的实例去剖析关系操作符在对象中的规则机制。

单一对象—{} || [] || function(){}

普通对象{}、数组对象[]、函数对象function(){}都是对象,单一性对象的关系操作符运行机制是一致的(单一性对象测试,需要测试数组对象、函数对象时,可以把普通对象置换成对应测试对象即可),因此此处仅展示普通对象的关系比较例子。

console.log("{} > {}", {} > {}); // 输出:false
console.log("{} < {}", {} < {}); // 输出:false
console.log("{} >= {}", {} >= {}); // 输出:true
console.log("{} <= {}", {} <= {}); // 输出:true

单一性测试结果

单一性测试分析

遵循关系操作符的比较规则,当操作数是对象时,调用对象的valueOf()方法获取原始值进行比较,或者调用toString()方法获取对象字符串再进行比较。我们分别打印对象的valueOf()、toString()获取到的值,如下图所示:

因为{}的valueOf()方法返回值是对象本身(默认情况下,如果对象没有原始值,则valueOf将返回对象本身),因此我们可以比较{}对象的toString()方法返回的字符串大小,而单一性对象关系比较时,返回字符串一致,所以就产生了以上的测试结果。

多元对象—{} && [] && function(){}

普通对象{}、数组对象[]、函数对象function(){}都是对象,多元性对象的关系操作符运行机制是一致的(多元性对象测试,需要测试其余关系操作符时,可以把大于操作符置换成对应的关系比较符),因此此处仅展示多元性对象大于操作符的比较例子。

console.log('{} > []', {} > []); // 输出:true
console.log('{} > function(){}', {} > function(){}); // 输出:false
console.log('[] > function(){}', [] > function(){}); // 输出:false
console.log('[] > {}', [] > {}); // 输出:false
console.log('function(){} > {}', function(){} > {}); // 输出:true

多元性测试结果

多元性测试分析

任然遵循关系操作符的比较规则,分别比较普通对象{}、数组对象[]、函数对象function(){}的valueOf()方法或者toString()方法,我们分别打印三个对象的方法值如下:

由打印的方法值可以了解到,三类对象的valueOf()方法都是返回自身对象原始值,因此我们可以根据toString()方法获取到的字符串进行字符串关系比较,而字符串比较是使用基于标准字典的Unicode值来进行比较的,我们分别对普通对象、数组对象、函数对象的toString()方法返回的字符串进行Unicode转码,Unicode分别如下图所示:

根据Unicode的大小可知,函数对象>普通对象>数组对象,所以产生了以上的测试结果。

相等运算符

判断两个操作数是否相等,相对于相等比较,ECMScript的解决方案提供了两组操作符(解决早期对象相等比较将对象转换成相似类型的合理性问题): 相等和不相等————先转换再比较,全等和不全等————仅比较而不转换。

相等和不想等

相等和不相等操作符都会先转换操作数(强制转换操作数),然后再比较两个操作数转换后的相等性。在比较两个操作数的大小时,相等和不相等需要遵守以下对应的规则:

  • 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值(false转换为0,true转换为1)。

  • 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值。

  • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,再进行相等性比较。

  • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都是指向同一个对象,则返回true,否则返回false。

  • null和undefined是相等的,且不能将null和undefined转换为其他值。

  • 如果有一个操作数是NaN,相等操作符返回false,不相等操作符返回true(NaN不等于NaN)。

相等比较实例分析

相等比较和不相等比较,实例分析中依旧是分为单一性对象比较和多元性对象比较,由于单一和多元的运行机制完全参照相等比较的规则,所以下面将统一分析两类对象比较。

console.log('================单一性对象比较==============');

console.log("{} == {}", {} == {}); // 输出:false
console.log('{} != {}', {} != {}); // 输出:true
console.log('function(){} == function(){}', function(){} == function(){} ); // 输出:false
console.log('function(){} != function(){}', function(){} != function(){} ); // 输出:true
console.log('[] == []', [] == [] ); // 输出:false
console.log('[] != []', [] != [] ); // 输出:true

console.log('================多元性对象比较==============');

console.log('{} == []', {} == []); // 输出:false
console.log('{} != []', {} != []); // 输出:true
console.log('{} == function(){}', {} == function(){}); // 输出:false
console.log('{} != function(){}', {} != function(){}); // 输出:true
console.log('[] == function(){}', [] == function(){}); // 输出:false
console.log('[] != function(){}', [] != function(){}); // 输出:true

相等测试结果

相等测试分析

数组和函数也是对象,遵循这相等比较的规则,两个操作数都是对象时,比较两个对象是不是同一个对象?对啊,难道{}和{}不是同一个对象吗?所以这里的是不是究竟是个什么含义呢?如果深知javascript内存管理的,就会知晓当创建一个变量对象时,javascript内存会为这个变量对象创建一个堆内存地址来储存该变量对象,同时也会创建一个指向堆内存地址的栈内存指针(类比了下C中的指针)。javascript的变量对象的内存管理机制如下图所示:

根据javascript的内存管理机制,如果两操作数为对象类型,JavaScript会比较其内部引用地址,仅当他们在内存中引用不同对象时不相等;这样就会产生以上的测试结果。

全等和不全等

只有在两个操作数未经转换就相等的情况下就返回true。需要注意的是,在相等操作中null == undefined返回true,那是因为它们是类似的值,但是在全等操作中null === undefined返回false,那是因为它们是不同类型的值。

全等比较实例分析

全等比较和不全等比较基本上和相等比较差不多,区别在于全等比较不转换类型,直接比较且是严格的比较。比较依旧是分为单一性对象和多元性对象实例,全等比较实例如下。

console.log('================单一性对象比较==============');

console.log("{} === {}", {} === {}); // 输出:false
console.log('{} !== {}', {} !== {}); // 输出:true
console.log('function(){} === function(){}', function(){} === function(){} );// 输出:false
console.log('function(){} !== function(){}', function(){} !== function(){} ); // 输出:true
console.log('[] === []', [] === [] ); // 输出:false
console.log('[] !== []', [] !== [] ); // 输出:true

console.log('================多元性对象比较==============');

console.log('{} === []', {} === []); // 输出:false
console.log('{} !== []', {} !== []); // 输出:true
console.log('{} === function(){}', {} === function(){}); // 输出:false
console.log('{} !== function(){}', {} !== function(){}); // 输出:true
console.log('[] === function(){}', [] === function(){}); // 输出:false
console.log('[] !== function(){}', [] !== function(){}); // 输出:true

全等测试结果

全等测试分析

全等比较不转换而直接比较,那么{}、[]、function(){}三者对象是不同类型的值,所以它们不全等;但是为什么{}和{}也不全等呢?对啊,Why?那是因为上一部分说到的javascript的内存管理机制的原因所在,当两个操作数都是对象,javascript会比较其内部引用地址(栈内存指针),当且仅当它们的引用指向内存(堆内存地址)中的相同对象(区域)时才相等,即它们在栈内存中的引用地址相同。因此就会产生以上的测试结果。

参考文献

  1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Comparison_Operators

  2. javascript高级程序设计(第3版)