變數宣告與 undefined
變數,就是一個「放東西的箱子」,這個箱子會取一個名稱,之後就可以用箱子的名稱去代指裡面裝的東西
宣告一個變數,例如:
var box = hello
這裡的 =
不是「等於」的意思
在 JavaScript 的 =
是「賦值」(賦予值)的意思
可以看作是 var box <= hello
代表「我要把 hello 放到 box 裡面」
注意!宣告變數不可以用「數字」開頭
例如:下面這樣是錯誤的
var 335box = 57
命名方式
變數的命名方式有分為兩種:
- 用底線來連接
var api_response = 57
- 駝峰式(建議用這種)
var apiResponse = 0
重點是!要統一!用了駝峰式就統一用駝峰式
變數的大小寫是有區別的
宣告變數 box 和 BOX 會是兩個不同的變數
var box = hello
var BOX = 456
變數 undefined
在 box 裡面沒有裝東西
var box
console.log(box)
會印出 undefined
「undefined」的意思是:有宣告這個變數,但是沒有給值
變數 is not defined
var box
console.log(boxkkk)
會印出 boxkkk is not defined
「is not defined」的意思是:根本沒有 boxkkk 宣告這個變數
用位元運算來判斷變數是奇數/偶數
var box = 57
console.log(box & 1)
會印出 1,因為 box 是奇數
運算子 ++
和 --
var a = 0
a = a + 5
console.log(a)
上面程式碼的意思是:
會先執行 =
右邊的,把 a + 5
算完後的值,再放到 a 裡面
因此會印出 5
a = a + 5
也可以寫成 a += 5
兩種寫法是同樣的意思
var a = 0
a += 5
console.log(a)
a = a - 5
也可以寫成 a -= 5
如果是要 +1 或 -1,有另一種更方便的寫法
a += 1
也可以寫成 a++
a -= 1
也可以寫成 a--
因為 +1 和 -1 在 JS 裡面很常用到,所以就決定給它一個專門的運算子來用
把 ++
放在“後面”跟放在“前面”的差別在哪呢?
如果是 a++
var a = 0
console.log(a++ && 30)
console.log('a:', a)
印出來的結果會是:
0
a: 1
第一句 console.log(a++ && 30)
會印出 0,
是因為 console.log(a++ && 30)
這句可以看成是:
console.log(a && 30)
a++
所以,
把 ++
放後面的話
第一步:會先執行 console.log(a && 30)
這句(這時的 a = 0,所以會印出 0)
第二步:再執行 a++
(這時的 a 才會是 1)
如果是 ++a
var a = 0
console.log(++a && 30)
console.log('a:', a)
印出來的結果會是:
30
a: 1
第一句 console.log(++a && 30)
會印出 30,
是因為 console.log(++a && 30)
這句可以看成是:
a++
console.log(a && 30)
所以,
把 ++
放前面的話
第一步:會先執行 a++
(這時的 a 會是 1)
第二步:再執行 console.log(a && 30)
這句(這時的 a = 1,所以會印出 30)
變數的各種型態
在宣告變數時,雖然不用指定變數的型態,但在 JS 運作時,還是會給變數一個型態
變數型態有分幾種:
- Primitive 基本型別
- object, undefined, function
Primitive 基本型別
Primitive 就是「最原始的型別」,分成三種:
- boolean (true 或是 false)
- number
- string (用單引號 or 雙引號包起來的東西)
typeof
用 typeof
後面加上想看的東西,
typeof
會回傳一個字串,告訴你那個東西是什麼型別
console.log('type of true', typeof true)
會印出:
type of true boolean
object, undefined, function
- 陣列、物件的型別都是屬於 object
- undefined 的型別就是 undefined
注意!沒有一個型別是叫做「array」
注意!null
的型別是 object
這是從 JavaScript 早期就存在的性質,沿用到現在
console.log('type of [1]', typeof [1])
console.log('type of {a: 1}}', typeof { a: 1 })
console.log('type of null', typeof null)
console.log('type of undefined', typeof undefined)
console.log('type of function', typeof function(){})
用 typeof
可以得到下面回傳的結果:
type of [1] object
type of {a: 1}} object
type of null object
type of undefined undefined
type of function function
陣列(Array)
array 是一種資料結構
用來存「性質相似」的資料
物件(Object)
object 也是一種資料結構
object 的格式是:
{
key: value,
key: value,
key: value
}
變數的運算
需要注意「型態」!
把字串轉型成「數字」
方式一:
用 Number(a)
把字串 a
轉成數字
var a = '58'
var b = 30
console.log(Number(a) + b)
方式二:
用一個 function 叫做 parseInt()
- Int 是 integer 的意思
- 傳入的參數
10
代表:我這個數字是「10 進位」
var a = '58'
var b = 30
console.log(parseInt(a, 10) + b)
需要注意「浮點數誤差」!
「浮點數」就是「小數」
為了避免碰到「浮點數誤差」,如果可以的話,盡量不要用到小數
例如:
var a = 0.1 + 0.2
console.log(a == 0.3)
結果竟然是回傳 false
var a = 0.1 + 0.2
console.log(a == 0.3)
console.log(a)
用 console.log(a)
來印出 a 的值,竟然是 0.30000000000000004,而不是 0.3
原因是:
電腦在存「小數」時,沒辦法存的那麼精準(會有一些誤差)
因此,我存了 0.2 這個數字,但在電腦裡可能其實是 0.2000000000000003
有些小數可以存的很精準,有些卻不行
更多關於浮點數的說明可參考 [CS101]初心者的計概與 coding 火球術 - 浮點數誤差、數字在電腦是怎麼儲存的
==
與 ===
一個等號: =
是「賦值」的意思
var a = 10
console.log(a = 5)
如果在 console.log()
裡面只放入一個等號(不小心打錯了,少打一個等號),結果就是回傳 5
var a = 20
console.log(a = 5)
// output: 5
因為它的執行順序會是:
- 先執行
a = 5
- 再執行
console.log(a)
執行順序:從右執行到左
var a = 20 == 20
console.log(a)
// output: true
會回傳 true 是因為:它的執行順序會是「從右執行到左」
- 先執行
20 == 20
,先判斷 20 是否等於 20(是 true) - 所以就是
var a = true
- 所以
console.log(a)
就會印出 true
但建議還是自己加上括號去決定它的執行順序:
var a = (20 == 20)
console.log(a)
==
與 ===
的差異
==
不會「比較兩個東西的型態」
兩個等號: ==
就是「判斷這兩個東西是不是相等」,但不會去管型態
var a = 20
console.log(a == 20)
// output: true
console.log(30 == '30')
//output: true
可以把「空字串」看成是「0」
console.log(0 == '')
// output: true
===
會「比較兩個東西的型態」
三個等號: ===
就是「判斷這兩個東西是不是相等」,且會去比較型態
number 不等於 string
console.log(30 === '30')
// output: false
console.log(0 === '')
// output: false
建議:永遠都用 ===
來判斷,這樣最不容易出錯
如果只有使用 ==
,可能會因為沒有判斷出型態不相同而出現問題
例如:console.log(30 + '30')
會等於 3030 而不是 60
從「object 的等號」真正理解變數
補充教學文章 從博物館寄物櫃理解變數儲存模型
console.log([] === []) // output: false console.log([1] === [1]) // output: false console.log({} === {}) // output: false console.log({a: 1} === {a: 1}) // output: false
以上的判斷結果都會是 false,為什麼呢?
注意!在做題目的時後,當我要判斷「陣列 arr
是否為一個空陣列」
這是錯誤寫法:
因為就算 arr
是一個空陣列,跟小括號裡面的 []
也會是不同的兩個空陣列(在兩個不同的記憶體位置上)
if(arr === []){
return 'empty'
}
這是正確寫法:
用「陣列長度是否等於 0」來判斷「arr
是否為空陣列」
if(arr.length === 0){
return 'empty'
}
先從 primitive 型別說起
在變數裡面放入一個 number,例如: var a = 20
a === 20
就會是 true 沒錯
因為:變數的箱子裡面放的就是「20」這個值
var a = 20
console.log(a === 20)
// output: true
只要變數是屬於 primitive 型別的(包括 number, string, boolean),因為都是直接存「值」,所以在判斷是否相等時,只會去比較「值」
這裡以 number 為例:
- 宣告一個變數
a = 3
- 宣告另一個變數 b,讓 b = a
- 這時,
a === b
就是 true(因為 a, b 的值都等於 3)
output:var a = 3 var b = a console.log('a:', a, 'b:', b) console.log(a === b)
現在,我把 b 的值改成 8a: 3 b: 3 true
- 修改 b 的值,並不會改到 a 的值
- 這時,
a === b
就是 false(因為 a, b 的值不相等了)
output:var a = 3 var b = a b = 8 console.log('a:', a, 'b:', b) console.log(a === b)
總結:a: 3 b: 8 false
當「變數」裡面裝的是 number, string, boolean 這些 primitive 的型別時,會有這三種特性(特性比較單純、直覺):
1. 變數裡面存的就是「值」,而不是去存「記憶體位置」
例如:var a = 3
在變數 a 裡面存的就是「3」這個值
2. 因為變數裡面存的是「值」,所以在判斷「兩個變數」是否相等時,只會去比較「值」。只要「兩個變數的值」相等,兩個變數就相等
3. 就算「變數的值」相等,但每個變數都是獨立的,會在「不同的記憶體位置」上,所以當我修改一個變數時,無論如何都不會去改到另一個變數
但是,對於「陣列、物件」來說,卻不是這樣子
當變數的型別是 object(陣列、物件)時,因為存的是「記憶體位置」,因此,在判斷「兩個陣列/兩個物件」是否相等時,不是去比較「值」,而是去比較「記憶體位置」
var obj = {
a: 1
}
console.log(obj === {a: 1})
// output: false
下圖是錯的:
在物件 obj
的箱子裡面,放的並不是 {a: 1}
這個值
其實,在 JavaScript 運作的底層是這樣的(陣列、物件都是這樣,因為 typeof 陣列、物件
的型別都是 object
):
注意!下面說明的情況,不會發生在「number, string, boolean」這三種屬於 primitive 的型別
var obj = {a: 1}
當我宣告變數 obj
等於一個物件 {a: 1}
時,它會先把物件放在某個地方叫做「記憶體位置」(是一個真正在電腦裡的記憶體位置,例如這裡假設的 0x01
)
在 obj
裡面存的東西其實是這個「記憶體位置 0x01
」,而不是直接存 {a: 1}
這個物件
我們沒有任何方法可以得知這個「記憶體位置」是哪裡,因為在使用 JavaScript 時,它就會直接把「這個記憶體位置上面的東西 {a: 1}
」給我
假設,我現在再宣告了另一個變數: obj2
var obj = {a: 1}
var obj2 = {a: 1}
儘管兩個物件存的“數值”是一樣的(都是 a: 1
),但是 obj2
會再建立一個「新的記憶體位置」(這裡假設是 0x05
)
因為 JavaScript 在做判斷時,比較的是「記憶體位置」。所以,obj
不會等於 obj2
,因為「兩個物件存的記憶體位置是不同的」(兩個箱子裡面放的是不同的記憶體位置)
讓兩個物件相等:指向同一個記憶體位置(改一個,也同時改到另一個)
怎麼做才會讓兩個物件相等?
我讓 obj
= obj2
,這樣兩個物件就會相等了
var obj = {
a: 1
}
var obj2 = obj
console.log(obj === obj2)
// output: true
當我改變 obj2.a
的值(讓 obj2.a = 50
),也會同時改到 obj.a
的值
var obj = {
a: 1
}
var obj2 = obj
obj2.a = 50
console.log('obj', obj)
console.log('obj2', obj2)
console.log(obj === obj2)
output:
obj { a: 50 }
obj2 { a: 50 }
true
原因是:
這也跟「記憶體位置」有關
因為此時的 obj
和 obj2
,裡面的東西是「指向同一個記憶體位置」
兩個物件會相等的原理
我原本有一個變數 obj
,裡面存的記憶體位置是 0x01
當我宣告 var obj2 = obj
,背後做的事情是:
宣告一個變數 obj2
,指向同一個物件 {a: 1}
(所以記憶體位置也是存 0x01
)
因此,這也是為什麼 obj === obj2
會是 true(因為是指向同一個記憶體位置、同一個物件)
這時,當我執行到 obj2.a = 50
,背後做的事情是:針對「同一個記憶體位置」上的物件,去新增、修改屬性
用 .
的方式去新增、修改屬性,例如:在 obj2
新增一個 obj2.b = 200
,obj
也會一起被新增 obj.b = 200
會改到 0x01
這個記憶體位置裡面的 a,因此,在改 obj2.a
的同時,也會一起改到 obj.a
讓兩個物件不相等:指向不同的記憶體位置(改一個,並不會影響到另一個)
當我「用 =
」讓 obj2
等於一個新的物件 {b: 1}
,背後做的事情是:obj2
會指向一個新的記憶體位置
obj2
就會斷開原本跟 obj
的連結,並建立另一個新的記憶體位置 0x05
,因此 obj
就不會跟 obj2
相等了(是兩個不同的物件)
var obj = {
a: 1
}
var obj2 = obj
obj2.a = 50
obj2 = {b: 1}
console.log('obj', obj)
console.log('obj2', obj2)
console.log(obj === obj2)
output:
obj { a: 50 }
obj2 { b: 1 }
false