JavaScript 的變數


Posted by saffran on 2020-12-01

變數宣告與 undefined

變數,就是一個「放東西的箱子」,這個箱子會取一個名稱,之後就可以用箱子的名稱去代指裡面裝的東西

宣告一個變數,例如:

var box = hello

這裡的 = 不是「等於」的意思

在 JavaScript 的 = 是「賦值」(賦予值)的意思

可以看作是 var box <= hello
代表「我要把 hello 放到 box 裡面」

注意!宣告變數不可以用「數字」開頭

例如:下面這樣是錯誤的

var 335box = 57

命名方式

變數的命名方式有分為兩種:

  1. 用底線來連接
    var api_response = 57
    
  2. 駝峰式(建議用這種)
    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 就是「最原始的型別」,分成三種:

  1. boolean (true 或是 false)
  2. number
  3. 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

因為它的執行順序會是:

  1. 先執行 a = 5
  2. 再執行 console.log(a)

執行順序:從右執行到左

var a = 20 == 20
console.log(a)
// output: true

會回傳 true 是因為:它的執行順序會是「從右執行到左」

  1. 先執行 20 == 20,先判斷 20 是否等於 20(是 true)
  2. 所以就是 var a = true
  3. 所以 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)
    var a = 3
    var b = a
    console.log('a:', a, 'b:', b)
    console.log(a === b)
    
    output:
    a: 3 b: 3
    true
    
    現在,我把 b 的值改成 8
  • 修改 b 的值,並不會改到 a 的值
  • 這時,a === b 就是 false(因為 a, b 的值不相等了)
    var a = 3
    var b = a
    b = 8
    console.log('a:', a, 'b:', b)
    console.log(a === b)
    
    output:
    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

原因是:
這也跟「記憶體位置」有關

因為此時的 objobj2,裡面的東西是「指向同一個記憶體位置」

兩個物件會相等的原理

我原本有一個變數 obj,裡面存的記憶體位置是 0x01

當我宣告 var obj2 = obj,背後做的事情是:

宣告一個變數 obj2,指向同一個物件 {a: 1}(所以記憶體位置也是存 0x01

因此,這也是為什麼 obj === obj2 會是 true(因為是指向同一個記憶體位置、同一個物件)

這時,當我執行到 obj2.a = 50,背後做的事情是:針對「同一個記憶體位置」上的物件,去新增、修改屬性

. 的方式去新增、修改屬性,例如:在 obj2 新增一個 obj2.b = 200obj 也會一起被新增 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

#javascript







Related Posts

淺談 DOM Clobbering 的原理及應用

淺談 DOM Clobbering 的原理及應用

How to Set Up Firewall with UFW on Ubuntu 20.04

How to Set Up Firewall with UFW on Ubuntu 20.04

關係&函數(Relations & Functions)

關係&函數(Relations & Functions)


Comments