iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
1
Modern Web

JavaScript基本功修煉系列 第 3

JavaScript基本功修練:Day3 - 基本型別(I) - 數字

這篇主要概述JavaScript基本型別的意思,並針對講解數字這個型別。明天才會繼續一併講完字串、布林、undefinednullsymbol

在進入正題前,先看看JavaScript有什麼資料型別:

型別(Data type)

基本型別(Primitive data type)

  • 數字(Number)
  • 字串(String)
  • 布林(Boolean)
  • undefined
  • null
  • symbol (ES6新增)

物件型別(Object)

  • 物件(Object)
  • 陣列(Array)
  • 函式(Function)
  • 還有更多...

注意只有變數的值才會有型別,變數本身並不會有型別之分。

另外,基本型別的資料是不可改變(immutable),意思是我們不能更改屬於基本型別的值。如果我們要更改它的值,只能為該變數賦予一個新的基本型別的值。相反,物件型別是可以被修改的,不一定要重新賦予。

我們可以用typeof去查詢該值是屬於什麼型別:

typeof(10) //number
typeof('10') //string
typeof(true) //boolean
typeof({}) //object
typeof([]) //object
typeof(null) //object
typeof(undefined) //undefined

奇怪,null是屬於object?是的,有些開發者認為這是JS本身一直存在的bug。因為這個bug已經存在好多年,如果改掉了就會使以前建好的網站出現問題,所以索性將錯就錯就好了。

型別可被隨時更改

JavaScript是屬於動態語言弱型別語言

  • 動態語言:宣告變數時,不會要求指定值的型別,並容許在中途更改型別。只有在執行程式碼的當下,才會去確認型別並執行。
var x = 10;
x = '10';
console.log(x); //10
console.log(typeof(x)); //string
  • 弱型別語言:會隱性轉換型別。例如把一個字串加上一個數字時,即使判斷到兩個型別並不一樣,也會隱性把兩者當成字串一樣黏起來完成運算。
var y = 10 + '1';
console.log(y); //101

聽起來很靈活方便,但如果沒留意好型別轉換就好容易藏下bug。例如我曾經在撈取伺服器回傳的資料時,直接拿仍然是字串型別的數字去做運算,結果就計算出奇奇怪怪的數字,但因為當時console沒有報錯,菜鳥的我沒有察覺到哪裏出問題,還花了一點時間去找。

數子(number)

常規數字

就是整數或帶有小數點的數字,例如: 1、1.1。

特別數字(special numeric values)

包括NaNInfinity-Infinity。它們雖然不是一個數字,但在JavaScript的世界裏,它們是屬於數字(numbers)這個型別。

Infinity

  • Infinity 是指數學上的無限大
  • -Infinity 是指數學上的負無限大

例如以下的運算會得出Infinity

NaN

  • NaN (Not a number),MDN的解釋是:這個值不是一個數字。但有些開發者覺得這個解釋模糊,他們主張更清晰的解說,例如引用You Don't Know JS這本書中的解釋:

It would be much more accurate to think of NaN as being “invalid number,” “failed number,” or even “bad number,” than to think of it as “not a number.”

NaN is a kind of “sentinel value” (an otherwise normal value that’s assigned a special meaning) that represents a special kind of error condition within the number set. The error condition is, in essence: “I tried to perform a mathematic operation but failed, so here’s the failed number result instead.

重點就是:
NaN是指一個無效的數字,一個錯誤的狀態。這個錯誤狀態源於JavaScript嘗試執行一個數學運算,但結果失敗,所以只能得出一個無效的數值。

到底在什麼情況下會出現NaN?
MDN舉了幾個例子:

截圖自MDN

總括來說,情況包括:把本來不能轉型為數字的值強行轉為數字(e.g把字串轉成整數)、計算失敗(e.g -1平方根)、把NaN放入運算等等。

要注意的是,NaN 不等於任何數字,也不等於NaN它自己

如何判斷該變數是否NaN?
傳統的做法就是把變數放入isNaN()裏檢查,看看該變數是否NaN

isNaN()的問題

isNaN()一直存在誤判的問題,舉個例子:

明明是一個字串,但isNaN()就判斷它是true可是,我們知道字串就是字串,為什麼是true?。
這個問題是因為isNaN先把()裏的值轉型成數值,如果不能轉成數值,這個值就是NaN了。

'hello world'這個例子裏呢?
因為如果強行把'hello world'轉成數值,會得出NaN,因此會變成isNaN(NaN),而答案就變成了true

Number()去看'hello world',顯示NaN

Number('hello world') //NaN

為了解決先轉型做數字而導致誤判的這個問題,ES6提出了使用Number.isNaN()的建議。以下是ES6官方文檔的解釋:


截圖自ECMAScript® 2015 Language Specification

由此可見,Number.isNaN()不會事先把()裏的值轉為數值,而是判斷該值是否一個數值,因此避免了剛才誤判的問題。回到剛才'hello world'的例子,這一次終於顯示false

Number.isNaN('hello world') //false

二進位浮點數算術標準的問題

JavaScript並不能精準讀取所有小數點的數字

先看看以下的運算:

剛學JS的我被結果嚇到。

首先,有兩個重點要了解:

  • 電腦是用二進位儲存數字(人類平時讀寫數字都是用十進位)。如果不懂二進位可以先看看這個簡單易明的YouTube影片
  • JavaScript是用IEEE二進位浮點數算術標準(IEEE 754)去處理數字,所有數字都是雙精度浮點數(double precision floating point numbers),以64bits儲存

想了解更多關於JavaScript所使用的IEEE 754可看這個YouTube影片,雖然後面code的部分我沒跟上XD,但前部分的解說挺易懂的~

以這個例子為例:
0.1+0.2 = 0.30000000000000004

電腦會把十進位數字變成二進位:
0.1 = 0.0001100110011001101...(無限循環)
0.2 = 0.00110011001100110011...(無限循環)

0.1和0.2,在二進制的世界裏,是一個循環小數,原理就像平時我們1/3時,會有不斷循環的小數點數值。基於IEEE 754原則,JavaScript在儲存每一個數字時,並不會把該數字所有小數點都精準地保留起來。因為這裏只有64位(64bits)給它放數字,所以在過程中,它一定要把捨棄掉某些小數點,導致它最後不能精確還原原本的十進位數值,只能是盡量貼近。

例如0.1和0.2,即使有超多的小數在後面,也要把一些捨棄掉。

但為什麼這個例子又沒事呢?
0.1+0.3 = 0.4

我嘗試用二進制計算機相加0.1和0.3的二進制數字,發現正正是等於0.4的二進制數字,所以就解釋到為什麼會順利得出0.4這個結果。

0.4的二進制:0.0110011001100110011

0.1+0.3的二進制相加(如下圖):
0.0001100110011001101 + 0.01001100110011001101
= 0.011001100110011

為了避免這個誤差問題,最直接的方法是把數字轉為整數才去作其他處理,但對於大數的處理,比較推薦用JS函式庫,例如number precision、math.js等等去作處理。

總結

數字:

  • 可分為常規數字、特別數字
  • NaN是指一個無效的數字,錯誤的狀態
  • Number.isNaN()isNaN()更精準找出該值是否NaN
  • 注意二進位浮點數算術標準的問題

想不到數字這個基本型別都寫了差不多一篇,明天會繼續把其餘的(字串、布林、null等等)都一併講完,感謝你的閱讀!

參考資料

You Don't Know JS

弱型別、動態語言
弱类型、强类型、动态类型、静态类型语言的区别是什么?
你不可不知的 JavaScript 二三事#Day2:資料型態的夢魘——動態型別加弱型別(1)

isNaN問題
Better NaN check with ES6 Number.isNaN
从 Number.isNaN 与 isNaN 的区别说起

二進位浮點數算術標準的問題
重新認識 JavaScript: Day 03 變數與資料型別
0.1 + 0.2不等于0.3?为什么JavaScript有这种“骚”操作?


上一篇
JavaScript基本功修練:Day2 - 瀏覽器與JavaScript引擎
下一篇
JavaScript基本功修練:Day4 - 基本型別(II) - 字串、布林、null、undefined、Symbol
系列文
JavaScript基本功修煉31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
O口O
iT邦新手 4 級 ‧ 2022-07-31 09:12:48

請問一下:
isNaN("Hello") -> True(hello非數值)

這應該沒什麼不對吧?
Number.isNaN("Hello"); -> False(這邊我才搞不懂為什麼?)

我要留言

立即登入留言