米娜桑,哦哈喲~
本章講解關于 JavaScript 奇妙的 Bug,與其說是Bug,不如說是語言本身隱藏的奧秘,接下來就看看可能會影響到我們編程的那些Bug吧,
typeof null === "object"
官方自帶的Bug,typeof 運算子會回傳對應運算元型別的字串 表示,唯獨 null,回傳object,檔案解釋說:
在 JavaScript 最初的實作中,JavaScript 中的值是由一個表示型別的標簽和實際資料值表示的,物件的型別標簽是 0,由于
null代表的是空指標(大多數平臺下值為 0x00),因此,null 的型別標簽是 0,typeof null也因此回傳"object",
指標是一個變數,其儲存的值是一個地址,一般空指標儲存的地址為 0x00;物件的參考也是一個變數,儲存的值也是一個地址,比如:0x000001、0x000002,
而JavaScript會根據實際資料儲存值得到標簽型別,那么 typeof null === 'object',
NaN !== NaN
如果 var a = NaN , 那么 a !== a
這是 NaN 本身的一個特點,也只有NaN,比較之中不等于它自己,
所以判定是否為NaN有三種方式
isNaN(a) //為NaN或者強制轉換為數字后是NaN時,則回傳true
Number.isNaN(a) //僅當為NaN時為true
a !== a //成立的唯一情況是 a 的值為 NaN
[ ]![ ] , [ ]![ ]
對于前半段 []!==[]
一個變數在記憶體中都需要一個空間來存盤,而記憶體會根據其型別分配到堆疊記憶體(stack)或堆記憶體(heap),
最新的 ECMAScript標準 定義了 8 種資料型別:
Null:空指標物件Undefined:未定義Boolean:布林值Number:整數、浮點數、特殊值(Infinity、NaN)String:字串Symbol:一種實體是唯一且不可改變的資料型別BigInt:一種用于表示任意精度格式的整數的資料型別Object:物件
除了前面7種原始資料型別在宣告變數時是儲存在堆疊記憶體,而物件型別則是在堆疊記憶體中存盤一個參考地址,該地址指向堆記憶體的值,
let num = 1,
arr1 = [],
arr2 = [],
arr3 = arr2
console.log(arr1 === arr2) //false
console.log(arr2 === arr3) //true
記憶體分配如圖,雖然它們在堆記憶體中分配在不同位置的儲存的值是等價的,但進行 arr1 === arr2 判斷時會對比它們的參考地址,故不為真,
而 arr2 === arr3 其實本質就是 arr2 === arr2,因此,該改變arr3,其實就是改變arr2,這就引申出淺拷貝和深拷貝的話題
對于后半段 []==![]
以相等運算子 == 比較兩個值是否相等,在比較前將兩個被比較的值轉換為相同型別,即隱式轉換,轉換規則如下,詳情請查看 相等性判斷 檔案
根據上述規則,轉換程序如下:
[] == ![]
[] == false //優先執行邏輯非操作,回傳false
''== 0 //[]使用toString轉換;false轉換為數字
0 === 0 //''轉化為數字,進行全等判定,比較完畢 ,
所以,大多數情況下,不建議使用 == 判定,使用 === 結果更容易預測,由于沒有進行隱式轉換,=== 評估更快(雖然影響微小)
0.1 + 0.2 !== 0.3
這不僅是JavaScript的Bug,也是計算機語言的“通病”,
從最底層的電路來講,一般電路通過給電子器件施加電壓,根據其電壓高低狀態(高電平、低電平),也就是所謂的電子器件開關,來實作二值數字邏輯,即0和1,而正是這種方便快捷的狀態,決定了計算機采用二進制進行運算,
而小數的二進制大多為無限回圈的,如果用每個電子器件開關的狀態對應記錄這些無限回圈的二進制數字,這顯然是不可能的,
最終根據IEEE 754標準,0舍1入,使用64位固定長度來表示(ECMAScript?語言規范有所提及),所以有如下結果:
(0.1).toString(2)
//"0.0001100110011001100110011001100110011001100110011001101"
(0.2).toString(2)
//"0.001100110011001100110011001100110011001100110011001101"
(0.3).toString(2)
//"0.010011001100110011001100110011001100110011001100110011"
而0.1、0.2進行二進制加運算得到的結果應為
//0.0100110011001100110011001100110011001100110011001100111
//對應十進制為 0.30000000000000004
//因此 0.1 + 0.2 !== 0.3
a === 1 && a === 2 , a == 1 && a == 2
看似荒謬的比較,為什么會存在 a 能滿足上述條件呢?這得分開討論,
對于前半段 a === 1 && a===2
如果 a 是原始資料型別,那上述的全等判定就不能成立,所以 a 就需要通過函式進行變形,那么 a 是一個回傳1或著2的函式物件,
這個時候我們可以利用 getter 將物件屬性系結到查詢該屬性時將被呼叫的函式,而其物件正是 window 物件,
不難得出
let i = 1
Object.defineProperty(window, 'a', {
get() {
return i++
}
})
console.log(a === 1 && a === 2) //true
//當訪問 window.a 時候則會實行 a 函式
對于后半段 a === 1 && a===2
在提及 []==![] 討論過,當比較的兩者型別不一致的時候,將進行隱式轉換,對應的值會執行其內置的 ToPrimitive() 函式操作:
1、進行 valueOf(),如果得到的為原始資料型別(如Date型別會得到對應的數字),則回傳對應原始值,否則進行第2步,
2、進行 toString() ,回傳對應原始值,如果失敗,拋出 TypeError,
let a = {
i: 0,
valueOf() {
return this.i += 1
}
}
console.log(a == 1 && a == 2) //true
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/150297.html
標籤:JavaScript
