[JavaScript] 學習心得 and 筆記

跟著Alpha Camp的課程, 寫了一個JS的小練習。

使用者輸入一個1~100的整數, 讓電腦去猜這個數字。需要回傳一些必要訊息, 並且要有input防呆機制。

完成這作業的過程中, 針對JS的特性做了些研究, 並將其筆記一下。

#1 要去看回傳值而非console.log

可幫助理解JS, 訓練debug能力
最好利用Repl之類的線上練習平台, 去觀察回傳值。才能正確理解每個指令到底會產生何種結果。Chrome的DevTools雖然也有回傳值, 但本身沒有提供語法速寫功能。

const num = '0'
typeof num // 'string'
console.log(num) // 0 無法得知型別
num // '0' 可得知是string

透過回傳值, 可以清楚理解 Number() 到底對值做了甚麼。

Number('')           // 0
Number(null) // 0
Number('3.6') // 3.6
Number('string') // NaN

#2 JavaScript是弱型別語言

用運算子時他不會報錯, 很難避免未預期的bug
之前用Ruby時, 如果用非數字的值跟數字進行運算比較時, Ruby會直接報錯。
但JS不會, 這類錯誤在JS中會從語法錯誤變成難以察覺的邏輯錯誤。

let x = '10'
0 < 1 // true
x > 1 // true
0 + x // '010'
let y = ''
y < 1 // true
y > 1 // false
let z = null
z < 1 // true
z > 1 // false
let n = NaN
NaN > 1 // false
NaN < 1 // false

推測, 後台運作為, 非 Number 的值會自動套上 Number() 才進行運算比較。
推測, NaN 是一個特殊 Number 與其他 Number 不存在大小關係。

#3 NaN 不是一種型別, 它是一個型別為 Number 的值

JS中神奇的玩意, NaN。意思為 Not a Number。

typeof NaN   // Number
Number('a') // NaN
Number('3') // 3

類似的操作, 在Ruby中會直接報錯

Ruby Integer('a')  // 報錯
Ruby Integer('3') // 3

無法利用比較運算子來判斷 NaN。
可以使用 Number.isNaN() 或是 isNaN()。

NaN === NaN               // false, 不是數字當然不是數字 (???
Number(NaN) // NaN (屬於Number型別的一個值)
Number(NaN) === NaN // false
更多關於NaN, 可參考
https://ithelp.ithome.com.tw/articles/10190873

#4 isNaN() 與 Number.isNaN() 的差異

看到 isNaN() 容易讓人誤以為NaN是一種data type!
後者為較新的功能, 用來解決前者的邏輯矛盾。
 isNaN() 這指令會讓人誤會NaN是一種type, 直覺認為它碰到非NaN的值應該要回傳true, 但實際上不是這樣。

isNaN(NaN)  // true
isNaN('hi') // true, 如果NaN是type, 這邊應該回傳 flase
isNaN('1') // false

isNaN() 過濾的並不是 NaN 這個值, 而是字面意思「是否Not a Number?」。
它碰到type不是Number的值時, 後台會包上Number(值), 轉換後才判定。

isNaN('string')  // true, it's not a Number
isNaN(1) // false
isNaN(1.6) // false
isNaN('0') // false, 因 Number('0')為 0
isNaN('') // false, 因 Number('')為 0
isNaN(null) // false, 因 Number('')為 0

但似乎很多人覺得這邏輯不通順, 所以JS新增了Number.isNaN()。
Number.isNaN() 會將 NaN 當作一種type來嚴格判斷。

Number.isNaN(NaN)      // true
Number.isNaN('string') // false
Number.isNaN(1) // false
Number.isNaN(1.6) // false
Number.isNaN('0') // false
Number.isNaN('') // false
Number.isNaN(null) // false

我是這樣理解的。

isNaN()        // 類似 0 == '0' true 不判斷type
Number.isNaN() // 類似 0 === '0' flase 判斷type

單純判斷User input值時, isNaN() 會很好用。因為User input不會出現Boolean , undefined 之類雜七雜八的東西。User input只會回傳字串。唯一特別的情況只有空白字串, 以及Esc取消時會回傳null。

小結:

isNaN()        // 單純過濾非數字, 非數字皆回傳 true (有例外)
Number.isNaN() // 它會把 NaN 當作是一個type來判斷
MDN上有更深的探討

#5 如何判斷, User Input 是否為 1~100 的「整數」?

User input一律回傳字串type, 但還是有這些可能性需要做防呆

「string」, 「Number(int/float)」, 「Enter/空白」, 「Esc/取消」
Enter/空白提交   // 回傳 空字串''
Esc/取消 // 回傳 null

比較特別的是, User提交空白時, 如果用 Number() 檢測input是否為數字, 會回傳 0。條件範圍又剛好介在 1~100, 可以蒙混過關(不需特別檢測空白提交)。

let input = prompt('請輸入') // 若input空白, 則回傳 '' (空字串)
Number('') // 0
Number('0') // 0
if (Number('') < 1 || Nuumber('') > 100)  // 這會變 true, 要注意

方法1, 用 Number() 過濾非數字, 用 Number.isInteger() 過濾浮點數

let input = prompt('請輸入') // 必回傳 字串
Number(input) // string to number, 非數字回傳 NaN
Number.isInteger(input) // 若為 int 回傳 true, NaN 回傳 flase
[1..100] // 最後判斷範圍

方法2, 用 Math.floor() 過濾浮點數

let input = prompt(‘請輸入’)  // 必回傳 字串
Number(input) // string to number, 非數字回傳 NaN
input === Math.floor(input) // 只有 int 在捨去小數後, 仍會等於自身
[1..100] // 最後判斷範圍

#6 不要用 if else

改用 if return 或是 if continue 或是 if { }
這是看網路上很多人分享的經驗, 推薦不要使用 if else, 改用 return 之類的方法代替。因為if else當巢狀結構複雜時容易造成閱讀障礙。

function check() {
let input = prompt()
  if (是否按取消) {
取消了
} else if (是否為數字) {
不是數字喔
} else if (是否為整數) {
不是整數喔
} else {
條件符合
}
}

如果改成return, 會又短又好懂

function check() {
let input = prompt()

if (是否按取消) return 取消了
if (是否為數字) return 不是數字喔
if (是否為整數) return 不是整數喔
條件符合
}

&& 與 || 之類的邏輯運算, 如果很長時也最好將其拆開

function check() {
let person = 個人資料

if (是否成年 && 是否有房 && 是否有車 && 是否已婚 ) {
發送邀請
} else {
不發送邀請
}
}

如果上式不是虛擬碼, 肯定會長到一瞬間很難讀懂。可以用return拆成多行。雖然多行, 但要好讀很多。

function check() {
let person = 個人資料

if (是否成年) return 不發送邀請
if (是否有房) return 不發送邀請
if (是否有車) return 不發送邀請
if (是否已婚) return 不發送邀請
發送邀請
}

但是return只能作用於function, 非function時可以這樣寫。

if (點讚) { 讚+1 }
if (點負評) { 負評+1 }
if (點分享) { 導入分享pop-up視窗 }

如果是在迴圈內也可以利用 continue 和 break 辦到。

while (迴圈開關) {
let input = prompt()

if (是否按取消) { break }
if (是否為數字) { 不是數字喔; continue }
if (是否為整數) { 不是整數喔; continue }
迴圈開關 = flase
打印input
}

#7 Global全域變數, Local區域變數與作用域

funtion內宣告的Local變數, 外面是抓不到的!
JS只有兩種變數, Local 與 Global。有前綴語的是 Local, 反之為 Global。相比之下, Ruby在這部分複雜許多。但這篇不討論Ruby的Class系統。

// Global變數
num = 0
// Local變數
var x = 0
let y = 1
const z = 2

在外層宣告的 Local變數 可作用於內層 (子層)

const x = 0
function check() {
return console.log(x)
}
check() // 0

在內層宣告的 Local變數 不可作用於外層 (父層)

function check() {
const x = 0
}
check()
console.log(x) // 報錯, 未定義變數 x

Global變數就算在內層宣告, 外層也抓的到

function check() {
// Global宣告
x = 0
}
check()
console.log(x) // 0

#8 function函式的兩種寫法

如題, JS提供了兩種寫法。

常規寫法:

function check(x, y) {
return x + y
}

類似物件導向的寫法:

let check = function (x, y) {
return x + y
}

這兩種寫法同義, 但常規寫法有個特性, run的時候會自動把函式抬到最前方。所以JS可以出現這種顛倒世界一般的寫法。

check(1, 2)  // 3
function check(x, y) {
return x + y
}

如果用另一種寫法則不行

check(1, 2)  // 報錯, 未定義函式check()
let check = function (x, y) {
return x + y
}