NaN你都未必懂,花五分鐘讓你懂得不能再懂
NaN和Number.NaN
NaN全稱是Not-A-Number,不是一個數(shù)字。在 JavaScript 中,整數(shù)和浮點數(shù)都統(tǒng)稱為 Number 類型。
特點1 typeof是數(shù)字
口上說不是一個數(shù)字,typeof的值卻是number, 口是心非。
ES6之后,Number也多了一個靜態(tài)屬性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標(biāo)準(zhǔn)中, 不可配置,不可枚舉。也就是說不可以被刪除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,因為前者能準(zhǔn)確告訴你是否成功,而后者返回的被定義的對象。
- 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}
 
結(jié)果是無法改寫,所以不要打他的小心思。
常見的場景
計算, 類型轉(zhuǎn)換是典型的場景
- let print = console.log;
 - // parseInt
 - print(isNaN(parseInt("zz123"))) // true
 - // parseFloat
 - print(isNaN(parseFloat("zz123"))) // true
 - // 直接Number初始化
 - print(isNaN(Number("zz123"))) // true
 - // 數(shù)字運算
 - print(isNaN(0 / 0 )) // true
 - print(isNaN( 1 * "zz123" )) // true
 - print(Math.sqrt(-1)) // true
 
isNaN
isNaN() 是一個全局方法。
其本質(zhì)是檢查 toNumber 返回值, 如果是NaN,就返回 true,反之返回 false 。
可以簡化為語義:
- Number.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. | 
關(guān)于對象的轉(zhuǎn)換是:
1. Let primValue be ? ToPrimitive(argument, number).
2. Return ? ToNumber(primValue).
簡單翻譯就是先獲取原始類型的值,再轉(zhuǎn)為Number。
取原值,也會根據(jù)條件執(zhí)行不同的方法。
最優(yōu)先調(diào)用 Symbol.toPrimitive, 如果存在
根據(jù)條件決定是先調(diào)用 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
 
常規(guī)例子:
- 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
判斷一個值是否是數(shù)字,并且值等于NaN.
ES標(biāo)準(zhǔn)的描述:
- 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)) // true
 - print(Number.isNaN("123")) //false
 
isNaN和Number.isNaN的區(qū)別
Number.isNaN是嚴格判斷, 必須嚴格等于NaN。是不是NaN這個值
isNaN是通過內(nèi)部的 toNumber 轉(zhuǎn)換結(jié)果來判定的。Number轉(zhuǎn)換的返回值是不是NaN
Number.isNaN是ES6的語法,固然存在一定的兼容性問題。
Object.is
ES6標(biāo)準(zhǔn)新增方法,用于判斷兩個值是否屬于同一個值,其能準(zhǔn)確的判斷NaN。
- let print = console.log;
 - print(Object.is(NaN, NaN)); // true
 - print(Object.is("123", NaN)) // false
 
嚴格判斷NaN匯總
四種,2種ES6, 2種ES5。
- 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推薦的墊片,有些兼容低版本的庫就是這么實現(xiàn)的, 也是ES標(biāo)準(zhǔn)的精準(zhǔn)表達
- function isNaNVal(val){
 - return typeof val === 'number' && isNaN(val)
 - }
 
綜合的墊片
- if(!("isNaN" in Number)) {
 - Number.isNaN = function (val) {
 - return typeof val === 'number' && isNaN(val)
 - }
 - }
 
深究數(shù)組的indexOf與includes
三心醬在50個JS高級知識點有提到 includes能識別 NaN, 我們繼續(xù)來一看究竟。
- var arr=[NaN];
 - arr.indexOf(NaN) // -1
 - arr.includes(NaN) // true
 
includes
我們深入規(guī)范看一看:
ES標(biāo)準(zhǔn)的Array.prototype.includes 比較值相等調(diào)用的是內(nèi)部的 SameValueZero ( x, y )方法,其會檢查值第一值是不是數(shù)字,如果是數(shù)字,調(diào)用的是 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是相等的, 要區(qū)分+0和-0還得用Object.is
indexOf
ES標(biāo)準(zhǔn)中 Array.prototype.indexOf 值比較調(diào)用的是IsStrictlyEqual(searchElement, elementK), 其如果檢查到第一個值為數(shù)字,調(diào)用的 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::sameValueZero 和 Number::sameValue
區(qū)別
在上個章節(jié)我們提到了,Array.prototype.includes值的比較實用的是 Number::sameValueZero , 突出了Zero, Zero是什么,是0啊,也就是說對0進行了特殊的處理。
對應(yīng)的還要一個 Number::sameValue 方法, 一起看看:
可以看出Number::sameValueZero 不區(qū)分+0 -0, Number::sameValue 則區(qū)分。
- Object.is(+0, -0) // false, 區(qū)分+0,-0
 - [-0].includes(+0) // true,不區(qū)分+0,-0
 
BigInt::sameValue和 BigInt:samgeValueZero
圖片可以看出,除了Number有, BigInt也有相似的比較。
這兩個方法的主要應(yīng)用場景也就是 Object.is 和 Array.prototype.includes。
再猜猜下面的結(jié)果:
- Object.is(BigInt(+0),BigInt(-0))
 - Object.is(-0n,0n)
 - Object.is(-0,0)
 - [BigInt(+0)].includes(BigInt(-0))
 
3
2
1
結(jié)果,不如你所愿:
- Object.is(BigInt(+0),BigInt(-0)) // true
 - Object.is(-0n,0n) // true
 - Object.is(-0,0) // false
 - [BigInt(+0)].includes(BigInt(-0)) // false
 
哈哈,更多細節(jié) BigInt::equal ( x, y ):
核心解釋:
BigInt::sameValue ( x, y ) 調(diào)用 BigInt::equal ( x, y )
- BigInt::equal ( x, y )
 - 1. If ℝ(x) = ℝ(y), return true; otherwise return false.
 
而R(x)是啥玩意
- 從 Number 或 BigInt x 到數(shù)學(xué)值的轉(zhuǎn)換表示為“ x 的數(shù)學(xué)值”或 R(x)。+ 0F 和-0F的數(shù)學(xué)值為0
 
簡單的結(jié)論:
- Number區(qū)分+0,- 0
 - BitInt不區(qū)分
 
BigInt::sameValue和 BigInt:samgeValueZero有什么區(qū)別呢?
用一張圖,更好解釋:
沒有區(qū)別,更合理的解釋是什么呢??
小結(jié)
indexOf是ES5甚至更早的產(chǎn)物,includes是ES6的產(chǎn)物。高級產(chǎn)物向著更合理化的方向發(fā)展,合情合理。
至于為什么不升級indexOf呢,歷史包袱吧,以前的代碼總不能讓其產(chǎn)生意外效果吧。



















 
 
 










 
 
 
 