0%

傳值與傳址

情況

某天,自己需要更改陣列資料時,遇到一個莫名其妙的情況。

修正前 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 原始資料陣列
let data = [
{
name: 'A'
},
{
name: 'B'
}
];
// 暫存資料
let temp = {
name: 'C'
};

data.forEach(item => {
if (item.name === 'B'){
item = temp
}
})
console.log(data[1]) // { name: 'B'}

變數 data 內的資料,居然還是原封不動!

修正後 :

1
2
3
4
5
6
data.forEach(item => {
if (item.name === 'B'){
item.name = temp.name
}
})
console.log(data[1]) // { name: 'C'}

變數 data 內的資料終於修改成功了!

不過,為什麼會這樣?


傳值

當我們宣告一個新的變數時,其預設值會是 undefined

若我們賦予這個變數一個值,後續取用這個變數時,就會是其值,

像是這樣 :

1
2
let a = 1
console.log(a) // 1

若我們再宣告一個變數 b並賦予其值為變數 a變數 b 的值會是?

1
2
let b = a
console.log(b) // ?

此時狀態會是”傳遞值”,也就是說變數 b的值會是 1,

若我們再將變數 a重新賦予值為 100,變數 b 也不會被改變其值。

在 JavaScript 裡,布林值、字串、數值、null、undefined 都是 Pass by value。

1
2
3
4
5
// 完整範例
let a = 1
let b = a
a = 100
console.log(b) // 1

傳址 / 傳參考

上面的情況都是純值,換成物件、陣列或是函式呢?

假設我們程式碼如下 : 變數 b 會是?

1
2
3
4
5
6
let a = {
name: 'A'
}
let b = a
a = 1
console.log(b.name) // ?

變數 b 不會受到變動,因為它還是傳值,因此為 'A'

若變更物件的屬性值呢?

1
2
3
4
5
6
let a = {
name: 'A'
}
let b = a
a.name = 'B'
console.log(a.name) // 'B'

變更物件屬性值時,變數 b連帶的也受到影響,也就是傳參考。

在 JavaScript 裡,物件、陣列、函式都是 Pass by reference。

那我們知道有這樣情況後,使用函式來看看會怎樣?

1
2
3
4
5
6
7
8
9
10
11
12
// 宣告一樣的變數作為範例
let d = {
name: 'D'
}
let e = {
name: 'E'
}
const fn = data => {
data = e
}
fn(d)
console.log(d) // ?

在上述的範例中,變數 d維持不變,因為它是傳值,

若改成 data.name = e.name,此時才會是傳參考。

回歸情況

回到一開始的程式碼,很單純地想讓資料變更,於是重新賦予其值,

但事實沒這麼簡單,由於 JavaScript 擁有兩種特性,傳值與傳參考,特別是物件,

單純的物件複製是傳值,修改物件屬性值則為傳參考,

於是產生了 Pass by sharing 來稱呼這種情況。

參考來源

  1. SimonAllen - Day17 傳值 by value 與傳址 by reference
  2. Kuro Hsu - 重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?
  3. OneJar - 你不可不知的 JavaScript 二三事#Day26:程式界的哈姆雷特 —— Pass by value, or Pass by reference?