如果你是一个前端开发人员的话,那么难免会遇上这样的情况
console.log(0 == false); // true
console.log('' == false); // true
console.log([] == false); // true
console.log(0 === false); //false
console.log('' === false); //false
console.log([] === false); // false
不熟悉 JavaScript 的开发者也许会问:== 为啥既然有了
==
了,还要 ===
干啥?这两者有什么区别?====
顾名思义,是相等运算符。而 ===
是全等,也叫严格模式下的相等。上面的例子其实可以看出这两者有挺大的区别的。在 JavaScript 中,
==
运算符会在进行比较的时候进行一次隐式类型转换,这个转换是开发人员看不到的。比较的结果也会像前面的例子那样神奇。而 ===
运算符则是进行严格的对比,如果两个变量的类型不一致或者类型一致,但是值不同的话,均会返回 false
。==
运算符其实在很多地方会有令人意想不到的结果,如果你觉得上面的例子不是很难懂的话,那么来看看下面的这个例子:if ([0]) {
console.log([0] == true); // false
console.log([0] == false); // true}
if ('something') {
console.log('something' == true); // false
console.log('something' == false); // false}
嗯,你是不是也开始觉得神奇了?
为啥
if ([0])
是成立的,而 [0] == true
是 false
?同样的道理,为啥 if ('string')
是成立的,而 'string' == true
是 false
?最奇怪的是 'string' == false
居然也是 false
?是的,这个
==
运算符看似很奇怪,运算的结果经常会出乎人意料,所以,现在也有很多 JavaScript 大神都在呼吁不要使用 ==
运算符进行比较,而应该使用 ===
进行比较。因为相对于 ==
来说,===
运算符的运算结果则靠谱多了,或者说,其结果是可以清楚的被预知的。在我刚开始学 JavaScript 的时候,看到了不少关于这方面的说法。于是乎一开始我也是对这种说法深信不疑,而且对
==
敬而远之。总之,凡是进行比较的运算,我都是会使用 ===
而不是使用 ==
。甚至会跟身边的小伙伴们说,使用 ==
进行比较是一种不合格的写法。虽然说使用
===
运算符在日常开发中运作得很好,也没有什么值得注意的。但是,对于 ==
运算符来说,我是一点都不了解。只知道进行比较之前会进行隐式类型转换。我也曾经疑惑过,我也曾经想弄清楚这里面的奥秘,深入去掌握它,但是终究是太懒,后来也就没有去了解它的欲望了。直到后来看到了一篇文章,才发现,原来当初的想法错的多离谱,而且,原来 JavaScript 中的
==
运算符其实一点都不难理解好吧,扯了这么多,下面开始进入正题。
在上面的例子中,有两个地方值得研究。
第一个是求真假值:
// if 表达式中的条件
if (expression) {
// code goes here
}
// 或者三元表达式
var something = a ? 'a' : 'b';
第二个则是 ==
相等比较:
console.log(undefined == null);
console.log([0] == '');
好在,如今的浏览器在处理上面的这些操作时都已经达到了一个标准了。
求真假值
在求真假值的时候,你只需要记住:
- 如果需要判断的变量为对象时,全部返回
true
。 - 如果变量是字符串,而且不为空字符串时,返回
true
。 - 如果变量是数字,而且不为
+0
或者-0
或者NaN
时,返回true
。 - 如果变量是布尔值,则返回它本身的值。(不会进行转换)
- 如果变量是
undefined
或者null
时,则返回false
。
有了这些规则之后,那么再看回原来的例子就很好理解了。
// [0] 是一个数组,而数组本质上是一个对象,因此为 `true` 。
if ([0]) {
}
// 'something' 是一个非空的字符串,因此返回 `true`。
if ('something') {
}
真假值的判断很容易掌握和理解。不过尽管规则很容易理解,但是还是有些地方容易出错,不信来看看:
// copied from https://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/
var trutheyTester = function(expr) {
return expr ? "truthey" : "falsey";
}
trutheyTester({}); //truthey (an object is always true)
trutheyTester(false); //falseytrutheyTester(new Boolean(false)); //truthey (an object!)
trutheyTester(""); //falseytrutheyTester(new String("")); //truthey (an object!)
trutheyTester(NaN); //falseytrutheyTester(new Number(NaN)); //truthey (an object!)
需要注意的是,使用
new
运算符返回的总是一个对象。另外值得一提的是,在使用
new
创建两个值相同的变量时,对两者进行比较的话都是返回 false
:var a = new String('a');
var b = new String('a');
console.log(a == b); //false
console.log(a === b); //false ,这个更不用说。Number 类型也是同样道理。
神奇吧?有些人觉得这无法接受,结果实在是太难预测了,有些人则觉得,这是 JavaScript 特有的特性,加以好好利用的话,则会在一些地方得到灵活的运用。你是怎么想的呢?
好吧下面来看看
==
运算符的规则:==
运算符
==
运算符相对来说是比较自由的,容忍度比较高。它允许两个不同类型的变量进行比较。其实这里面的奥秘则是,**在进行比较之前,解析器会将两个变量转换成同一种类型(通常是数字),然后再进行比较。**而当两个变量是同一种类型时,那么结果则跟 ===
运算符一样。除了 undefined
和 null
两个在 ==
运算符中是相等的之外,其他大部分都是先转换成数字再进行比较。来看看详细的规则:(当两个变量均为相同类型值时,参考全等运算符的规则。)
除了上表所示的几种类型之外,其余的都是
false
。其中 toNumber()
和 toPrimitive()
是解析器内部的两个私有方法。它主要的功能是根据特定的规则转换传入的参数。其具体的规则为:toNumber()
:
toPrimitive()
:
以上则是整个转换的规则。要说难其实不难,因为就上面几条规则而已。要说不难的话,其实也很勉强。一开始接触这方面的知识,难免会有点摸不着头脑。接下来就来演示几个例子,消化消化一下。
[0] == true
[0] == true
// 首先将布尔值转换成数字
[0] == 1
// 接着将 [0] 转换成基本类型值
// 由于 [0].valueOf() 返回一个数组,不是一个基本类型值,则调用
// [0].toString() ,返回 "0"
"0" == 1
// 再将字符串转换成数字
0 == 1 // false,值不同。
[0] == false
[0] == false
// 首先将布尔值转换成数字
[0] == 0
// 接着将 [0] 转换成基本类型值。和上面的一样。于是
0 == 0 // true
'string' == true
'string' == true
// 将布尔值转换成数字
'string' == 1
// 将字符串转换成数字
NaN == 1 // false
'string' == false
'string' == false
// 将布尔值转换成数字,然后将字符串转换成数字。
NaN == 0 // false
Object 对象的 valueOf
方法
var obj = {
valueOf: function () {
return '1';
}
};
obj == 1
// 调用对象的 `valueOf` 方法,返回字符串
'1''1' == 1
// 将字符串转换成数字
1 == 1 // true
Object 对象的 toString
方法
var obj = {
toString: function () {
return '1';
}
};
obj == 1
// 调用对象的 `toString` 方法,返回字符串 '1' ,然后转换成数字
1 == 1 //true
再来演示一下转换对象时,究竟是优先调用
valueOf
还是 toString
:var obj = {
valueOf: function () {
return 0;
},
toString: function () {
return 1;
}
};
obj == 0 // true
obj == 1 // false
以上,可以非常直观的看出,转换对象时,会优先调用
valueOf
方法。现在再来看看全等运算符的情况:
===
运算符
===
运算符的情况则简单了许多。- 当两个变量不为同一种类型时,总是返回
false
。 - 当两个变量均为
undefined
或者null
时返回true
。 - 当两个变量均为数字时,当且仅当两者数值相同(但是不为 NaN)时返回
true
。(记住,NaN === NaN
返回false
。) - 当两个变量均为字符串时,当且仅当两者值相同时返回
true
。 - 当两个变量均为布尔值时,当且仅当两者值相同时返回
true
。 - 当两个变量均为对象时,当且仅当两者引用的对象一致时返回
true
。 - 其他情况,一律返回
false
。
那么,这时候我们应该就明白了为什么:
var a = new String('a');
var b = new String('a');
a == b // 两者都是同个类型的变量,转为全等运算。
a === b // 因为使用 new 操作符得到的是一个新的对象,而不是引用某个对象,因此,这里的变量 a 和 b 虽然其 toString() 的值相同,但终究不是引用同一个对象,因此这个运算结果为 false 。
看到这里,你是否有一种豁然开朗的感觉?没错,当初我就是这样的感受!
当遇到一件难以理解的问题时,如果抱着敬而远之的心态的话,那么你有可能会避开了一些“坑”。但是从某些角度看,因为不理解而不去使用,甚至不愿意花时间去深刻挖掘其根本原因,那是一种不科学的学习方式。有时候,迎难而上恰恰是技术提升的关键步骤,而这种提升恰恰能够将你和普通的开发者们区分开来。这就是研究的精华和价值所在。在此,我非常感激让我明白了解这个技术难点的博主!
现在再来看看什么时候应该使用 ===
,什么时候使用 ==
综上,我们可以知道使用
==
运算符其实大部分时候都是将变量转换成数字,那么当需要比较的变量一定为数字的时候,我们就可以光明正大的使用 ==
进行对比。换句话说,当我们明确知道需要比较的两个变量一定为同类型的值时,我们就可以毫不忌讳的使用 ==
运算符进行比较,而不用在乎因为隐式转换带来的问题:var arr = [1,2,3,4,5]
arr.length == 5 // length 属性返回的始终是一个数字。
arr.length === 5 // 没必要的做法。
if (typeof myVar == 'function') // typeof 操作符返回的始终是一个字符串,而且只有仅有的几种情况。
if (typeof myVar === 'function') // 没必要的做法
嗯,其他的不用说了吧...