NaN和Number.NaN
NaN全称是Not-A-Number,不是一个数字。 在 JavaScript 中,整数和浮点数都统称为 Number 类型。
特点1 typeof是数字
口上说不是一个数字,typeof的值却是number
, 口是心非。
ES6之后,Number也多了一个静态属性NaN
typeof NaN // number typeof Number.NaN // number 复制代码
特点2 我不等于我自己
我否定我自己,也就这家了。 硬要说,还有一个+0
和 -0
NaN == NaN // false Number.NaN == NaN // false NaN === NaN // false Number.NaN === NaN // false +0 == -0 // true Object.is(+0, -0) // fasle 复制代码
NaN的描述信息
其是一个值,新的ES标准中, 不可配置,不可枚举。 也就是说不可以被删除delete
,不可以被改写, 也不可以改写配置。
delete NaN // false NaN = 1 // 1 NaN == 1 // false delete Number.NaN // false Number.NaN = 1 // 1 Number.NaN == 1 // false 复制代码
我们尝试改写:
使用Reflect.defineProperty
而不使用Object.defineProperty
,因为前者能准确告诉你是否成功,而后者返回的被定义的对象。
const success =Reflect.getOwnPropertyDescriptor(window, 'NaN'), { writable: true, configurable: true, }) console.log(success) // false Reflect.getOwnPropertyDescriptor(window, 'NaN') // {value: NaN, writable: false, enumerable: false, configurable: false} 复制代码
结果是无法改写,所以不要打他的小心思。
常见的场景
计算, 类型转换是典型的场景
let print = console.log; // parseInt print(isNaN(parseInt("zz123"))) // true // parseFloat print(isNaN(parseFloat("zz123"))) // true // 直接Number初始化 print(isNaN(Number("zz123"))) // true // 数字运算 print(isNaN(0 / 0 )) // true print(isNaN( 1 * "zz123" )) // true print(Math.sqrt(-1)) // true 复制代码
isNaN
isNaN() 是一个全局方法。
其本质是检查 toNumber 返回值, 如果是NaN,就返回 true,反之返回 false 。
可以简化为语义:
const isNaN = function (val){ return Object.is(Number(val), NaN); } 复制代码
toNumber 方法, 大致的逻辑如下:
le 15: ToNumber Conversions
Argument Type | Result |
Undefined | Return NaN. |
Null | Return +0𝔽. |
Boolean | If argument is true, return 1𝔽. If argument is false, return +0𝔽. |
Number | Return argument (no conversion). |
String | Return ! StringToNumber(argument ). |
Symbol | Throw a TypeError exception. |
BigInt | Throw a TypeError exception. |
关于对象的转换是:
1. Let primValue
be ? ToPrimitive(argument
, number).
2. Return ? ToNumber(primValue
).
简单翻译就是先获取原始类型的值,再转为Number。
取原值,也会根据条件执行不同的方法。
- 最优先调用
Symbol.toPrimitive
, 如果存在 - 根据条件决定是先调用 valueOf 还是toString
对象这里就比较有意思了, 看下面的例子, valueOf的返回,可以直接影响isNaN的值。
let print = console.log; var person = { age: 10, name: "tom", valueOf(){ return this.name } } print(isNaN(person)) // true let print = console.log; var person = { age: 10, name: "tom", valueOf(){ return this.age } } print(isNaN(person)) // false 复制代码
常规例子:
let print = console.log; print(isNaN("123")) //false print(isNaN('zz123')) //true print(isNaN(NaN)) //true 复制代码
isNaN是可以被删除的,但是不可被枚举:
delete isNaN // true typeof // undefined isNaN = 1 // 1 isNaN == 1 //true 复制代码
属性描述信息:
Number.isNaN
判断一个值是否是数字,并且值等于NaN.
ES标准的描述:
- If Type(
number
) is not Number, return false.- If
number
is NaN, return true.- Otherwise, return false.
所有可以语义化为:
Number.isNaN = function(val){ if(typeof val !== "number"){ return false } return Object.is(val, NaN); } 复制代码
demo:
let print = console.log; print(Number.isNaN(NaN)) // false print(Number.isNaN("123")) //true 复制代码
isNaN和Number.isNaN的区别
Number.isNaN是严格判断, 必须严格等于NaN
。是不是NaN这个值
isNaN是通过内部的 toNumber 转换结果来判定的。Number转换的返回值是不是NaN
Number.isNaN是ES6的语法,固然存在一定的兼容性问题。
Object.is
ES6标准新增方法,用于判断两个值是否属于同一个值,其能准确的判断NaN
。
let print = console.log; print(Object.is(NaN, NaN)); // true print(Object.is("123", NaN)) // false 复制代码
严格判断NaN汇总
四种,2种ES6, 2种ES5。
Number.isNaN (ES6)
Number.isNaN(NaN) // true Number.isNaN(1) // false 复制代码
Object.is (ES6)
function isNaNVal(val){ return Object.is(val, NaN); } isNaNVal(NaN) // true isNaNVal(1) // false 复制代码
自身比较 (ES5)
最为简单的一种方式。
function isNaNVal(val){ return val !== val; } isNaNVal(NaN) // true isNaNVal(1) // false 复制代码
typeof + NaN (ES5)
这是MDN推荐的垫片,有些兼容低版本的库就是这么实现的, 也是ES标准的精准表达
function isNaNVal(val){ return typeof val === 'number' && isNaN(val) } 复制代码
综合的垫片
if(!("isNaN" in Number)) { Number.isNaN = function (val) { return typeof val === 'number' && isNaN(val) } } 复制代码
深究数组的indexOf与includes
三心酱在50个JS高级知识点有提到 includes能识别 NaN, 我们继续来一看究竟。
var arr=[NaN]; arr.indexOf(NaN) // -1 arr.includes(NaN) // true 复制代码
includes
我们深入规范看一看:
ES标准的Array.prototype.includes 比较值相等调用的是内部的 SameValueZero ( x
, y
)方法,其会检查值第一值是不是数字,如果是数字,调用的是
Number::sameValueZero(x
, y
), 其具体比较步骤:
1. If
x
is NaN andy
is NaN, return true.2. If
x
is +0𝔽 andy
is -0𝔽, return true.3. If
x
is -0𝔽 andy
is +0𝔽, return true.4. If
x
is the same Number value asy
, return true.5. Return false.
其先对NaN进行了比较,所以能检查NaN, 这里还有一个额外信息,比较的时候+0和-0是相等的, 要区分+0和-0还得用Object.is
indexOf
ES标准中 Array.prototype.indexOf 值比较调用的是IsStrictlyEqual(searchElement
, elementK
), 其如果检查到第一个值为数字,调用的 Number::equal(x
, y
).
其比对逻辑
1. If
x
is NaN, return false. 2. Ify
is NaN, return false. 3. Ifx
is the same Number value asy
, return true. 4. Ifx
is +0𝔽 andy
is -0𝔽, return true. 5. Ifx
is -0𝔽 andy
is +0𝔽, return true.6. Return false.
可以看到,任何一个为NaN,就直接返回false,必然不能严格的检查NaN.
Number::sameValueZero 和 Number::sameValue
区别
在上个章节我们提到了,Array.prototype.includes值的比较实用的是 Number::sameValueZero , 突出了Zero, Zero是什么,是0啊,也就是说对0进行了特殊的处理。
对应的还要一个 Number::sameValue 方法, 一起看看:
可以看出Number::sameValueZero 不区分+0 -0
, Number::sameValue 则区分。
Object.is(+0, -0) // false, 区分+0,-0 [-0].includes(+0) // true,不区分+0,-0 复制代码
BigInt::sameValue和 BigInt:samgeValueZero
可以看出,除了Number有, BigInt也有相似的比较。
这两个方法的主要应用场景也就是 Object.is 和 Array.prototype.includes。
再猜猜下面的结果:
Object.is(BigInt(+0),BigInt(-0)) Object.is(-0n,0n) Object.is(-0,0) [BigInt(+0)].includes(BigInt(-0)) 复制代码
3
2
1
结果,不如你所愿:
Object.is(BigInt(+0),BigInt(-0)) // true Object.is(-0n,0n) // true Object.is(-0,0) // false [BigInt(+0)].includes(BigInt(-0)) // false 复制代码
哈哈,更多细节 BigInt::equal ( x
, y
):
核心解释:
BigInt::sameValue ( x
, y
) 调用 BigInt::equal ( x
, y
)
BigInt::equal (
x
,y
)
而R(x)是啥玩意
从 Number 或 BigInt x 到数学值的转换表示为“ x 的数学值”或 R(x)。+ 0F 和-0F的数学值为0
简单的结论:
- Number区分+0,- 0
- BitInt不区分
BigInt::sameValue和 BigInt:samgeValueZero有什么区别呢?
用一张图,更好解释:
没有区别,更合理的解释是什么呢?? 也许是继承自相同的父类???
小结
indexOf是ES5甚至更早的产物,includes是ES6的产物。 高级产物向着更合理化的方向发展,合情合理。
至于为什么不升级indexOf呢,历史包袱吧,以前的代码总不能让其产生意外效果吧。
写在最后
技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,带带我,一起学习。