【踩坑】关于JS中的==运算符,以及由于强制类型转换出现的==运算问题

本文详细解析了JavaScript中`=`和`==`运算符的区别,以及它们在不同类型值比较时的行为,强调了使用`===`的必要性,特别是在处理布尔值、null、undefined、NaN和特殊值时的正确用法。
摘要由CSDN通过智能技术生成

1.关于 == 运算符和===运算符

        可能你也听说过“==检查值是否相等,===检查值和类型是否相等”的说法,这种说法其实不够准确,正确的解释是:”== 允许在相等比较中进行强制类型转换,而 === 不允许”

         两种说法看似一致,但在这第一种解释中,=== 似乎比 == 做的事情更多,因为它还要检查值的类型。第二种解释中 == 的工作量更大一些,因为如果值的类型不同还需要进行强制类型转换。

        实际上== 和 === 都会检查操作数的类型。区别在于操作数类型不同时它们的处理方式不同。虽然强制类型转换确实要多花点时间,但仅仅是微秒级而已,如果两个相同类型的值进行比较时,除了js引擎实现上存在细微差别,其他没有什么不同。

2.不同类型值之间的相等比较   

 (1)字符串和数字之间的相等比较

         我们已经知道了 == 运算符会进行强制类型转换操作,那字符串和数字之间的相等比较时,具体怎么转换?是数字转换为字符串,还是字符串转换为数字?

        这个问题的答案是在ES5 规范中定义的:“如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。”

     所以在字符串和数字之间的相等比较中,js总是将字符串类型转换为数字类型进行比较。

(2).其他类型和布尔类型之间的相等比较

        这是==运算符最容易出错的一个地方,我们在初学时可能会这样写 if(x == true),来判断一个值是否为真值,可总是得到错误的结果。

        在规范中:“如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果; 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。”

        所以在与布尔类型相等比较时,会将布尔值转换为数字类型,所以你可能会出现以下问题:

var x = "42";
var y = true;
var z = false;

x == y; // false
x == z; // false

       通过上边两个相等比较,“42”既不等于true,也不等于false。是这样吗?

       其实在这个相等比较中,js先将布尔值y转换成数字 1 ,等式变为 1 == “42” ,此时类型依旧不同,字符串和数字比较,字符串将被强制转换为数字类型 ,变为 1 == 42 ,结果自然是false。对z的处理也是类似操作。

        在这个过程中,从没有进行ToBoolean的操作,所以“42”是真值还是假值与 == 本身没有关系。所以建议大家写代码时无论什么情况下都不要使用 == true 和 == false。

// 不要这样用,条件判断不成立:
if (a == true) {}
// 条件判断不成立:
if (a === true) {}
// 这样的显式用法没问题:
if (a) {}
// 这样的显式用法更好:
if (!!a) {}
// 这样的显式用法也可以:
if (Boolean( a )) {}

    (3)null 和 undefined 之间的相等比较

        规范:”如果 x 为 null,y 为 undefined,则结果为 true。 如果 x 为 undefined,y 为 null,则结果为 true。“  在== 中 null 和 undefined 相等(它们也与其自身相等)。null 和 undefined 之间的强制类型转换是安全可靠的。所以通过这种方式将 null 和 undefined 作为等价值来处理比较好。

var a = yumian();
//条件判断 a == null 仅在 yumian() 返回非 null 和 undefined 时才成立,除此之外其
//他值都不成立,包括 0、false 和 "" 这样的假值。
if (a == null) {}
//这种情况使用显式的做法要繁琐些,效率也会更低
if (a === undefined || a === null) {}

   (4)对象和非对象之间的相等比较

        规范:如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。

var a = 42;
var b = [ 42 ];
a == b; // true
        [ 42 ] 首先调用 ToPromitive 抽象操作,返回 "42",变成 "42" == 42,然后又变成 42 == 42,最后二者相等。ToPromitive 抽象操作的所有特性(如 toString()、valueOf())在这里都适用。
var a = "abc";
var b = Object( a ); // 和new String( a )一样
a === b; // false
a == b; // true

         a == b 结果为 true,因为 b 通过 ToPromitive 进行强制类型转换(也称为“拆封”,英文 为 unboxed 或者 unwrapped),并返回标量基本类型值 "abc",与 a 相等。但有一些值不这样,原因是 == 算法中其他优先级更高的规则。如:

var a = null;
var b = Object( a ); // 和Object()一样
a == b; // false
var c = undefined; 
var d = Object( c ); // 和Object()一样
c == d; // false
var e = NaN; 
var f = Object( e ); // 和new Number( e )一样
e == f; //
        因为没有对应的封装对象,所以 null 和 undefined 不能够被封装(boxed),Object(null)
和 Object() 均返回一个常规对象。
        NaN 能够被封装为数字封装对象,但拆封之后 NaN == NaN 返回 false,因为 NaN 不等于 NaN

3.非常规值的情况

    (1).NaN 不等于 NaN。 NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。可以使用Number.isNaN() 来判断一个值是否是 NaN。

let a = 5 / "yumian";
a == NaN; // false
a === NaN; // false
Number.isNaN( a ); // true
//NaN 不等于自身,也可通过这个方法判断
if (!Number.isNaN) {
 Number.isNaN = function(n) {
 return n !== n;
 };
}

        (2)0 == -0。JavaScript 有一个常规的 0(也叫作+0)和一个 -0。-0 除了可以用作常量以外,也可以是某些数学运算的返回值。

var a = 0 / -3; // -0
var b = 0 * -3; // -0
-0 == 0; // true

        通过==运算时,-0 == 0; // true,在有关-0操作时可以用isNegZero( ); 进行处理

isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false
同时,ES6 中新加入了一个工具方法 Object.is(..) 来判断两个值是否绝对相等,可以用来处理
上述所有的特殊情况,但尽量不要使用 Object.is(..),因为==和===效率更高、更为通用,Object.is(..) 主要用来处理那些特殊的相等比较。
var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false

4.特殊情况

     (1).返回其他数字

        valueOf() 函数能得到封装对象中的基本类型值,Number() 涉及 ToPrimitive 强制类型 转换,此时会调用 valueOf()。通过修改原型上的valueOf函数,返回了不同的值

Number.prototype.valueOf = function() {
 return 3;
};
new Number( 2 ) == 3; // true

        如果让 a.valueOf() 每次调用都产生副作用,比如第一次返回 2,第二次返回 3,就会出现

这样的情况。
var i = 2;
Number.prototype.valueOf = function() {
 return i++;
};
var a = new Number( 42 );
if (a == 2 && a == 3) {
 console.log( "Yep, this happened." );
}

       (2).[ ] == ![ ]

[] == ![] // true,

         ! 运算符根据 ToBoolean 规则,它会进行布尔值的显式强制类型转换(同时反转奇偶校验位)。所以 [] == ![] 变成了 [] == false。前面我们讲过 false == [],最后的结果就顺理成章了。

2 == [2]; // true
"" == [null]; // true

        == 右边的值 [2] 和 [null] 会进行 ToPrimitive 强制类型转换, 以便能够和左边的基本类型值(2 和 "")进行比较。因为数组的 valueOf() 返回数组本身, 所以强制类型转换过程中数组会进行字符串化。 第一行中的 [2] 会转换为 "2",然后通过 ToNumber 转换为 2。第二行中的 [null] 会直接转 换为 ""。 所以最后的结果就是 2 == 2 和 "" == ""。

        如果还是觉得困惑,那么你的困惑可能并非来自强制类型转换,而是 ToPrimitive 将数组 转换为字符串这一过程。也许你认为 [2].toString() 返回的不是 "2",[null].toString() 返回的也不是 ""。

(3)0 == "\n"; //true

        ""、"\n"(或者 " " 等其他空格组合)等空字符串被 ToNumber 强制类型转换 为 0。

(4).假值的相等比较

"0" == undefined; // false
"0" == false; // true 
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true 
false == ""; // true
false == []; // true 
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!
0 == {}; // false
function do(a) {
 if (a == "") {
 // .. 
 }
}

三种”晕“的情况,会在do(0) 和 do([])遇到,这些特殊情况会导致各种问题,尽量不要使用。

5.简单总结

        1.如果两边的值中有 true 或者 false,千万不要使用 ==。

        2.如果两边的值中有 []、"" 或者 0,尽量不要使用 ==。

        强制类型转换在很多地方非常有用,能够让相等比较更简洁(比如 null 和 undefined)。
隐式强制类型转换在部分情况下确实很危险,这时为了安全起见就要使用 ===。
本文主要根据《你不知道的JavaScript》中卷第四章了解,有兴趣可以去看下

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值