细读 JS | 详谈一下 NaN

发布于 2023-05-12 18:21:29 字数 7804 浏览 80 评论 0

Freepik

一、NaN 的怪诞行为?

NaN 是 Not-A-Number 的简写,表示「不是一个数字」的意思,尽管如此,它却属于 Number 类型。

typeof NaN === 'number' // true

平常所写的 NaN 只是「全局变量」的一个「属性」而已,该属性的「初始值」为 NaN。为了便于区分,下文中高亮 NaN 表示全局变量的属性,NaN 表示属性值。

类似的还有 undefinedInfinityglobalThis,它们都是全局对象属性。

前面故意提到 NaN 的初始值为 NaN,原因是在 ES3 时代,该属性是可以被覆盖的,也就是 writable 的(这点与 undefined 表现是一致的),自 ES5 起就不可被重新赋值了。

The value of NaN is NaN.

需要注意的是,NaN 是 JavaScript 中唯一一个不等于其本身的值。这样的话,全等比较结果为 false 是不是看起来合乎情理了?

NaN === NaN // false

利用这一特性,可以快速地写出一个判断某个值是否为 NaN 的方法:

const myIsNaN = x => x !== x

myIsNaN(NaN) // true

小结:

  • 虽然 NaN 表示一个不为数字的值,但它类型却属于 Number 类型。
  • NaN 是 JavaScript 中唯一一个不等于其本身的值。
  • NaN === NaN 的比较结果为 false

二、isNaN()、Number.isNaN()、Number.NaN 傻傻分不清?

先上几个菜尝尝鲜:

NaN == NaN // false
NaN === NaN // false
NaN === Number.NaN // false
isNaN(NaN) // true
isNaN('NaN') // true
isNaN('string') // true
isNaN(undefined) // true
isNaN({}) // true
isNaN('11abc') // true
isNaN(new Date().toString()) // true

isNaN(null) // false
isNaN(10) // false
isNaN('10') // false
isNaN('10.2') // false
isNaN('') // false
isNaN(' ') // false
isNaN(new Date()) // false
Number.isNaN(NaN) // true
Number.isNaN(Number.NaN) // true
Number.isNaN(0 / 0) // true

Number.isNaN('NaN') // false
Number.isNaN('') // false
Number.isNaN(' ') // false
Number.isNaN(10) // false
Number.isNaN(undefined) // false
Number.isNaN(null) // false
Number.isNaN({}) // false
Number.isNaN(new Date()) // false
Number.isNaN(new Date().toString()) // false

希望你没有晕,其实掌握内在原理就很简单了,顶多是看起来「反直觉」而已...

2.1 Number.NaN

它是内置对象 Number 提供的一个「静态属性」,其值就是 NaN,且「只读」。

ECMAScript 1st Edition #15.7.3.4 可以看到:

The value of Number.NaN is NaN.
This property shall have the attributes { DontEnum, DontDelete, ReadOnly }.

这大概就是与全局对象属性 NaN 的唯一区别吧。

2.2 isNaN()

它是全局对象的一个方法,用于判断一个值是否为 NaN。

ECMAScript 1st Edition #15.1.2.6 可以看到:

Applies ToNumber to its argument, then returns true if the result is NaN, and otherwise returns false.

从规范描述可知,它内部做了「类型转换」。

isNaN(x) 为例,它先将 x 转换为 Number 类型(即规范中的 ToNumber 抽象操作),然后再判断转换后的值是否为 NaN,若为 NaN 返回 true,否则返回 false。比如:

const str = 'string'

isNaN(str) // true

// 相当于
const transformedStr = Number(str) // NaN
isNaN(transformedStr) // true

这样的特性有什么用呢,MDN 是这样介绍的,请看这里

基于此,我们可以快速写出其 Polyfill 方法:

globalThis.myIsNaN = function (value) {
  const transformedValue = Number(vulue)
  return transformedValue != transformedValue
}

若对数据类型转换不是太熟,可以阅读文章(隐式)数据类型转换详解

2.3 Number.isNaN()

从命名上看,globalThis.isNaN() 它是反直觉的,它偷偷给我们做了一次类型转换。

可能正是因为这个原因,所以 ES6 标准中提供了一个全新的方法 Number.isNaN(),其内部逻辑如下(可看):

  1. 若入参值不是 Number 类型,则返回 false
  2. 若入参值为 NaN 则返回 true,否则返回 false

isNaN() 不同的是,它不会对传入的值做类型转换。因此,可以快速写出其 Polyfill 方法:

Number.myIsNaN = function (value) {
  if (typeof value !== 'number') return false
  return value !== value
}

都 2022 年了,都用 Number.isNaN() 来判断吧,其余的就交给 Babel 了。

2.4 小结

  • Number.NaN 静态属性的返回值正是 NaN 本身,该属性只读。
  • 在 ES5 及以上,可以认为 globalThis.NaN 等价于 Number.NaN,它们的值均为 NaN。
  • isNaN()Number.isNaN() 多了一个 Number() 过程,实际项目中若要判断一个值是否为 NaN,建议使用后者。

三、indexOf() 和 includes() 对 NaN 是如何判断的?

其实在另一篇文章《相等比较详解》已经介绍过了。

const arr = [NaN]

arr.indexOf(NaN) // -1
arr.includes(NaN) // true

究其原因,是由于其内部使用了不同的比较算法。

这俩算法对 NaN 的处理如下(详见):

另外,全等比较也使用了 Number::equal (x, y) 算法:

两个 Number 类型的值的比较,其实都是围绕了这三个算法:Number::equal (x, y)Number:: sameValue (x, y)Number::sameValueZero (x, y),有兴趣可以看下。它们之中比较特别的值无非就是 +0-0NaN

四、其他

此前写过不少关于 JavaScript 数据类型的一些文章:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

勿忘心安

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

亽野灬性zι浪

文章 0 评论 0

少年亿悲伤

文章 0 评论 0

南七夏

文章 0 评论 0

qq_EJoXxu

文章 0 评论 0

17780639550

文章 0 评论 0

萌逼全场

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文