NaN你都未必懂,花五分钟让你懂得不能再懂

简介: NaN全称是Not-A-Number,不是一个数字。 在 JavaScript 中,整数和浮点数都统称为 Number 类型。

1.JPG


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,不可以被改写, 也不可以改写配置。


2.JPG

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。

取原值,也会根据条件执行不同的方法。


  1. 最优先调用 Symbol.toPrimitive, 如果存在
  2. 根据条件决定是先调用 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
复制代码


属性描述信息:

3.JPG


Number.isNaN


判断一个值是否是数字,并且值等于NaN.


ES标准的描述:


  1. If Type(number) is not Number, return false.
  2. If number is NaN, return true.
  3. 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 and y is NaN, return true.

2. If x is +0𝔽 and y is -0𝔽, return true.

3. If x is -0𝔽 and y is +0𝔽, return true.

4. If x is the same Number value as y, 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. If y is NaN, return false. 3. If x is the same Number value as y, return true. 4. If x is +0𝔽 and y is -0𝔽, return true. 5. If x is -0𝔽 and y is +0𝔽, return true.

6. Return false.


可以看到,任何一个为NaN,就直接返回false,必然不能严格的检查NaN.


Number::sameValueZeroNumber::sameValue


区别


在上个章节我们提到了,Array.prototype.includes值的比较实用的是 Number::sameValueZero , 突出了Zero, Zero是什么,是0啊,也就是说对0进行了特殊的处理。


对应的还要一个 Number::sameValue 方法, 一起看看:


4.JPG


可以看出Number::sameValueZero 不区分+0 -0Number::sameValue 则区分。


Object.is(+0, -0) // false, 区分+0,-0
[-0].includes(+0) // true,不区分+0,-0
复制代码


BigInt::sameValue和 BigInt:samgeValueZero


5.JPG


可以看出,除了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 )

1. If (x) = (y), return true; otherwise return false.


而R(x)是啥玩意

从 Number 或 BigInt x 到数学值的转换表示为“ x 的数学值”或 R(x)。+ 0F 和-0F的数学值为0


简单的结论:

  1. Number区分+0,- 0
  2. BitInt不区分


BigInt::sameValue和 BigInt:samgeValueZero有什么区别呢?


用一张图,更好解释:

6.JPG


没有区别,更合理的解释是什么呢?? 也许是继承自相同的父类???


小结



indexOf是ES5甚至更早的产物,includes是ES6的产物。 高级产物向着更合理化的方向发展,合情合理。


至于为什么不升级indexOf呢,历史包袱吧,以前的代码总不能让其产生意外效果吧。


写在最后



技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,带带我,一起学习。

相关文章
|
机器学习/深度学习 人工智能
WY15 幸运的袋子
WY15 幸运的袋子
WY15 幸运的袋子
|
前端开发 Java 测试技术
你肯定需要!软件测试面试题及答案这里面都有,关键不要钱
软件测人在进入岗位前,都要面对各种五花八门的面试问题。所以,不少人都会在面试前,提前做好相关的应试准备,如,面试话术,技能相关的面试真题背诵记录等等。 前者准备起容易,后者却比较费时费力,因为各个企业的面试题都会不一样,而想要提前做好应对准备,大量刷题就少不了,那哪里可以找大量的软件测试面试题及答案呢?
129 0
L1-062 幸运彩票 (15 分)
L1-062 幸运彩票 (15 分)
119 0
|
存储 缓存 负载均衡
10大高性能开发宝石,我要消灭一半程序员!
10大高性能开发宝石,我要消灭一半程序员!
148 0
10大高性能开发宝石,我要消灭一半程序员!
|
存储 算法 Java
面试官疯了吗,问我为什么浮点数不精确?
很多人都知道,Java 中的浮点数并不精确,需要用 BigDecimal进行精确计算,但是,很少有人知道为什么浮点数不精确呢?不精确为什么还要用呢?本文就来展开分析一波;
265 0
|
移动开发 JavaScript 前端开发
Day 5: GruntJS——重复乏味的工作总会有人做(反正我不做)
我们发现了比较有趣的系列文章《30天学习30种新技术》,准备翻译,一天一篇更新,年终礼包。下面是第五天的内容。
237 0
Day 5: GruntJS——重复乏味的工作总会有人做(反正我不做)
|
架构师 程序员
码农代码之外的生存指南,不要等到而立之年再后悔
  程序员不应该只会写代码。   我觉得在程序员群体中,很多人有个误区,就是觉得作为码农,每天安安静静的敲代码、默默的做好技术就可以了,其他的事情都不用去思考,以为一切都会水到渠成。但是还没等到水到渠成,却先等来了中年焦虑。慢慢的等到自己到了三十多岁的时候,发现公司里年轻且更能加班码农越来越多,带来了职业发展上的焦虑,同时上有老下有小且薪资也慢慢遇到职业瓶颈,又加重了生活财务的焦虑。   最近又翻了翻之前看过的一本书《软技能-代码之外的生存指南》,再次来读,又有一番新的理解。   作为一名程序员,我们的职业与生活中,不能仅仅只关注代码和技术。
207 0
|
程序员
程序员,你为什么值这么多钱?
听说一段时间不加薪,人就会开始思考起和工资有关的问题。消费水平又提升了,能力也进步了,经验也更多了,怎么还没涨工资呢? 近两年,有了点余钱就开始考虑起投资来,比如:投资股票首先需要判断的就是关于公司价值和价格的关系。
967 0
|
分布式计算 大数据 Java
关于学习的思考 - 不行就用笨办法【一千零一日】
做一个终身学习者,以不断提升认知为人生目标,向终身学习中致敬。
1392 0