微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

你可能不知道的 NaN 以及 underscore 1.8.3 _.isNaN 的一个 BUG

2016-07-04 22:37 伯乐专栏\/韩子迟

(点击上方公众号,可快速关注)

作者:伯乐在线专栏作者 - 韩子迟

链接:http://web.jobbole.com/86552/

点击 → 了解如何加入专栏作者


这篇文章并不在我的 underscore 源码解读计划中,直到 @pod4g 同学回复了我的 issue(详见 https://github.com/hanzichi/underscore-analysis/issues/2#issuecomment-227361035)。其实之前也有同学提出 isNaN 有 native 的 function,正好借此文辨析下几个常见的概念、方法,她们是 NaN,Number.NaN,isNaN,Number.isNaN,以及 underscore 中的 _.isNaN,顺便揪出了一个 BUG。


顺便安利,完整的 underscore 源码解读系列文章请戳 https://github.com/hanzichi/underscore-analysis


NaN & Number.NaN


ok,首先来了解下 NaN 和 Number.NaN 两个属性。


全局属性 NaN 表示 Not-A-Number 的值,顾名思义,就是表示 不是一个数字。


在编码中很少直接使用到 NaN。通常都是在计算失败时,作为 Math 的某个方法的返回值出现的(例如:Math.sqrt(-1))或者尝试将一个字符串解析成数字但失败了的时候(例如:parseInt(“blabla”))。这样做的好处是,不会抛出错误,只需要在下一步的运算中判断上个步骤的运算结果是否是 NaN 即可。


接着来看 Number.NaN,这货和 NaN 完全一样。其实,归根结底这俩货都是属于 Number 类型:


Object.prototype.toString.call(NaN)

// "[object Number]"

Object.prototype.toString.call(Number.NaN)

// "[object Number]"


isNaN & Number.isNaN


接着来聊 isNaN 和 Number.isNaN 俩方法。


我们都知道,虽然 NaN 作为 Number 类型,但是她不等于她自己, NaN == NaN 或者 NaN === NaN 都会返回 false,那么怎么检测一个 NaN 值呢?答案大家都知道了,isNaN 方法。


isNaN(NaN)

// true

isNaN(undefined)

// true

isNaN({})

// true

isNaN("abc")

// true


好多东西传入 isNaN 的结果都是 true,并不只是 NaN,为什么?因为参数会先被强制转换成 Number 类型,然后再进行判断。


Number(NaN)

// NaN

Number(undefined)

// NaN

Number({})

// NaN

Number("abc")

// NaN


ok,强制转换后其实都变成了 NaN。


那么 Number.isNaN 和 isNaN 有何区别呢?和全局函数 isNaN() 相比,该方法不会强制将参数转换成数字,只有在参数是真正的数字类型,且值为 NaN 的时候才会返回 true。


isNaN = function(value) {

    Number.isNaN(Number(value));

}

 

Number.isNaN = Number.isNaN || function(value) {

    return typeof value === "number" && isNaN(value);

}


值得注意的是,Number.isNaN 是 ES6 引入的,可以用上面的 Polyfill。


_.isNaN


最后来看看 underscore 对于 _.isNaN 的实现。


写代码首先得看需求,我们先看看 _.isNaN 的作用,查阅 API 文档 http://underscorejs.org/#isNaN:


this is not the same as the native isNaN function, which will also return true for many other not-number values, such as undefined.


文档指出,_.isNaN 和 native 的 isNaN 并不一样,必须是个 Number 类型(才可能返回 true),等等,似乎和 Number.isNaN 一样?且慢下结论。


我们来看看 edge 版本对其的实现(https://github.com/jashkenas/underscore/blob/master/underscore.js):


// Is the given value `NaN`?

_.isNaN = function(obj) {

  return _.isNumber(obj) && isNaN(obj);

};


obj 得是个 Number 类型,并且能通过 isNaN 函数的判断,才能返回 true。其实能通过这个函数的,只有两个值,NaN 和 new Number(NaN)(当然还有 Number.NaN,前面说了,NaN 和 Number.NaN 是一样的东西,下同)。


而能通过 Number.isNaN 函数的只有 NaN。(Number.isNaN(new Number(NaN) 会返回 false)


但是我看的 1.8.3 其实是这样实现的:


_.isNaN = function(obj) {

  return _.isNumber(obj) && obj !== +obj;

};


其实这是有 BUG 的,很显然 new Number(0) 并不应该是 Not-A-Number。


_.isNaN(new Number(0));

// true


为什么会这样写?这引发了我的好奇,找了下历史记录,是为了修复这个 issue https://github.com/jashkenas/underscore/issues/749。该 issue 认为,_.isNaN(new Number(NaN)) 应该返回 true。


我们可以看下再之前的版本对于 _.isNaN 的实现(https://github.com/jashkenas/underscore/commit/6ebb43f9b3ba88cc0cca712383534619b82f7e9b):


_.isNaN = function(obj) {      

   return obj !== obj;  

};


我又翻了下当时的测试数据(https://github.com/jashkenas/underscore/blob/6ebb43f9b3ba88cc0cca712383534619b82f7e9b/test/objects.js),发现当时没有类似 new Number(0) 的测试数据(现在已经有了)。


总结


对于 NaN 的判断,如果只针对 Number 类型,用 underscore 最新版的 _.isNaN 判断完全没有问题,或者用 ES6 的 Number.isNaN,两者的区别就在于一个 new Number(NaN),不过话又说回来,没人会这么蛋疼去这样 new 一个 NaN 吧?


专栏作者简介 ( 点击 → 加入专栏作者 


韩子迟:a JavaScript beginner

打赏支持作者写出更多好文章,谢谢!



【今日微信公号推荐↓】

更多推荐请看值得关注的技术和设计公众号


其中推荐了包括技术设计极客 和 IT相亲相关的热门公众号。技术涵盖:Python、Web前端、Java、安卓、iOS、PHP、C/C++、.NET、Linux、数据库、运维、大数据、算法、IT职场等。点击《值得关注的技术和设计公众号》,发现精彩!


 
前端大全 更多文章 来来,一起设计一个简单的活动发布系统 童年的马里奥,其实住在个超级现实的世界 JavaScript 错误处理的最佳实践 又被事件冒泡坑了一把,这次要彻底弄懂浏览器的事件流 我理想中的前端工作流
猜您喜欢 比尔·盖茨炮轰科技大佬:关注微软最新动态,做个对社会负责的人 大数据架构师必读的NoSQL建模技术 Apache\/Nginx通过UserAgent屏蔽蜘蛛和采集 这两天遇见的奇葩电脑! Bash 中的特殊字符大全