由-0与NaN的判断来看Array的元素识别

今天无聊在翻看MDN开发文档时,看到这么一个方法Array.prototype.includes(),之前在书上看到过,说是ES6修订文档(可以称为ES7)中新增的一个方法,用来判断一个数组中是否包含一个指定的值,返回一个布尔类型。当时也没有深究新增的这个方法与Array.prototype.indexOf()到底有什么不同,闲来无事,深究了一下,结果扯出来一些关于值判断的问题,自己都把自己问蒙了。下面就简单总结一下。

先说两个方法:

Array.prototype.includes(searchElement, fromIndex)

用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false,fromIndex 表示从该索引处开始查找,如果为负数,则按升序从arr.length+fromIndex的索引开始搜索。默认为0。

1
2
3
4
5
var arr = [1, 2, 3, NaN, 0, -0];
console.log(arr.includes(1)); //true
console.log(arr.includes(NaN)); //true
console.log(arr.includes(3, 3)); //false
console.log(arr.includes(2, -2)); //false

Object.is(value1, value2)

用来判断两个值是否相等,返回布尔值,下面情况则认为是相等的:

  • 两个值都是undefined
  • 两个值都是null
  • 两个值都是truefalse
  • 两个值是由相同个数的字符按照相同的顺序组成的字符串
  • 两个值指向同一个对象
  • 两个值都是数字并且都是正零+0、都是负-0、都是NaN、都是除零和NaN外的其他同一个数字
1
2
3
4
Object.is(undefined, undefined);   //true
Object.is(null, null); //true
Object.is(0, -0); //false
Object.is(NaN, NaN); //true

从上面可以看出,Object.is()与全等===不同的地方在于,===认为正零+0和负零-0相等,而且NaN不自等:

1
2
console.log(0 === -0);      // true
console.log(NaN === NaN); // false

includes VS indexOf

以往我们在判断某一项是否存在于一个数组内时,都是使用arr.indexOf(item)方法,根据返回的索引值是否为-1来进行判断:

1
2
3
4
5
6
7
8
9
10
var arr = [1, 2, 3, 4, 0, -0, NaN];
if (arr.indexOf(5) > -1) {
//不会执行
}
if (arr.indexOf(NaN) > -1) {
//不会执行
}
console.log(arr.indexOf(NaN)); // -1
console.log(arr.indexOf(-0)); // 4
console.log(arr.indexOf(0)); // 4

方法可行,但是这种方法一来无法判断NaN,另一方面既然我们在条件判断语句中使用,为什么不能有一个返回布尔类型的方法呢?includes()方法应运而生。

1
2
3
4
5
6
7
8
9
10
var arr = [1, 2, 3, 4, 0, NaN];
if (arr.includes(5)) {
//不会执行
}
if (arr.includes(NaN)) {
//执行
}
console.log(arr.includes(NaN)); //true
console.log(arr.includes(4)); //true
console.log(arr.includes(-0)); //true,与indexOf一样,都无法区分+0和-0

这样我们在进行元素存在判断的时候就不用再跟-1去比较了,直接返回布尔值,岂不美哉!

Object.is() VS ===

除了值‘在不在’的判断,我们日常还会跟值‘等不等’来打交道。现在随便问个Front-Ender都知道使用全等===来进行‘等不等’的判断,当然这里就涉及到我之前博客里讲的隐式类型转换的知识了,不再重复。如果我要判读的值是NaN或者-0呢?全等===还能胜任吗?

1
2
console.log(NaN === NaN);    // false, 都知道NaN‘不自等’
console.log(0 === -0); // true, 无法区分+0和-0

有个小误区,其实NaN应该跟NaN相等才是正常的,长久以来,一直被NaN不自等所误导,反而认为NaN不相等。现在有了Object.is(),可以愉快的判断NaN-0了:

1
2
3
console.log(Object.is(NaN, NaN));    // true
console.log(Object.is(0, -0)); // false
console.log(Object.is(-0, -0)); // true

还有一种粗暴的方式来判断-0

1
2
3
4
5
6
7
8
var x = -0;
if (x == 0 && 1 / x === -Infinity) {
// -0的情况
}
//或者
if (x == 0 && 1 / x < 0) {
// -0的情况
}

Number.isNaN() VS isNaN()

说起NaN的判断,怎么能不提isNaN()Number.isNaN()Number.isNaN()是一个更强大的方法,该方法不会强制将参数转换为数字,只有在参数是真正的数字类型,并且值为NaN的时候才会返回true

1
2
3
4
5
6
7
Number.isNaN(NaN);        // true
Number.isNaN(0 / 0); // true
Number.isNaN('NaN'); // true
Number.isNaN({}); // false
Number.isNaN(undefined); // false
Number.isNaN('test'); // false
Number.isNaN('37'); // fasle

isNaN()会首先尝试将这个参数转换为数值,然后才会对转换后的结果是否是NaN进行判断。 对于能被强制转换为有效的非NaN数值来说(空字符串和布尔值分别会被强制转换为数值0和1),返回false值也许会让人感觉莫名其妙。

1
2
3
4
5
6
7
isNaN('');                // false
isNaN(true); // false
isNaN('NaN'); // true
isNaN({}); // true
isNaN(undefined); // true
isNaN('test'); // true
isNaN('37'); // false

其实这里面又涉及到了类型转换的问题,可以看一下之前的博客。

总结

  1. 当需要判断数组内是否有NaN时,可以考虑使用includes()方法,当然要考虑兼容性问题;
  2. 当需要判断一个值是否是NaN时,可以使用Object.is()Number.isNaN(),优先使用Number.isNaN()
  3. 如果非要考虑-0的情况,那就可以使用Object.is()或者x == 0 && 1 / x < 0来进行判断;