Day24 | JS 關於物件的傳參考、淺層拷貝、深層拷貝
❒ 物件傳參考的特性
JavaScript 賦予一個值到變數上時會有兩個特性
- 傳值 ( Call by Reference )
- Boolean
- Null
- Undefined
- Number
- String
- …
- 傳參考 ( Call by Sharing )
- 物件 ( 陣列、函式 )
➊ 傳值 ( Call by Reference )
純值賦值是透過複製的方式,所以前者與後者各自獨立,當後者修改時不會影響前者。
純值:為基本型別 ( number、string、boolean、null、undefined )。
範例
1 | var person = '小明'; |
var person2 = person;為把person的值帶過去person2就為傳值,純值傳值是一種複製的方式。person2接收了person的值後再另外賦予person2新的值就不會和person有關聯性。
➋ 傳參考 ( Call by Sharing )
物件賦值是透過傳參考的特性,所以前者與後者都共用同一個記憶體空間的參考路徑,後者修改時前者也會跟著修改。
- 「 物件、陣列、函式 」皆為傳參考特性,會先另外建立一個記憶體空間把值寫入。
- ❗ 注意:物件變數新增一個新的物件
{}就會產生一個新的記憶體空間,就不會相互影響 ( 如下方範例 2. 3 )。
範例1. 物件傳參考
1 | var person = { |
解析:
程式碼中我們宣告了一個
person(var person = { name: '小明', money: 1000,};) 表示在記憶體準備了一個參考路徑,這個參考位置包含了name和money。( 如下右圖,0x01 是為了記憶自訂義名稱 )

當定義 person 為一個物件時,person 帶入的會是 0x01 這個記憶體的參考路徑,並不是帶入一個完整的物件內容。當參考路徑指向此物件時 ( 傳參考 ) 就可透過這種方式來取得裡面的
name與money屬性。所以person並不會把name與money存到它的記憶體空間,只會傳入一個參考。

var person2 = person;時會把person原本的參考位置一樣傳到person2,所以person和person2共用的是同一個參考路徑 ( 右邊格子 0x01 ),所以person2修改時也會一併修改到person。- 這段程式碼只有產生一個參考路徑,這個參考路徑對應一個物件,所以只有產生一個物件,
person2person兩者皆共用同一物件。
- 這段程式碼只有產生一個參考路徑,這個參考路徑對應一個物件,所以只有產生一個物件,
物件賦予值是以傳參考方式進行。
所以答案為
true。
範例 2. 物件傳參考
1 | var person = { |

解析:
- 把
person2指向一個新的空物件{ … },它就會產生另一個參考路徑 0x02,這時產生另一個物件也就是產生另一個參考路徑,所以person與person2就不會有任何關聯,互不影響。 - 所以答案為
false。
範例 3. 物件傳參考
1 | var person = { |
person2 = { name: '小明' }中就算person2與person內屬性與屬性值相同,但因分別為獨立的兩個物件兩個參考路徑,所以不會互相影響。- 所以答案為
false。
❒ 物件傳參考實作範例
❗ 注意:新增一個新的物件就會產生一個新的記憶體空間,前者與後者不會相互影響。
實作 1
1 | var family = { |
解析:
在
var member = family.members;時,member與family.members都還是同個參考路徑。

member賦予一個新的物件member = { ming: '大明' }開始,就拆成兩個參考路徑了,因為「 物件變數中看到新的{}就表示會產生一個新的記憶體空間 」。

但是換成另種寫法 :
member直接修改屬性而非產生{},member就會和family.members使用同一個參考路徑,而它們兩個也會相互影響。1
2
3
4
5
6
7
8
9
10
11var family = {
name: '小明家',
members: {
father: '老爸',
mother: '老媽',
ming: '小明',
},
}
var member = family.members;
member.ming = '大明';
console.log(family);
實作 2
1 | var a = { |
解析:
此寫法會造成無限循環,不斷指向自己。
1
2
3
4
5
6
7
8
9
10{
x: 1,
y: {
x: 1,
y: {
x: 1,
y: {.... 無限循環}
}
}
}a產生一個參考路徑 0x01 屬性為x→a指向 0x01 參考路徑 →a新增一個y屬性且屬性值為a,指回原參考路徑 0x01。從a裡面找y就會指回原參考路徑一直指向自己造成無限循環。
實作 3
1 | var a = { |
拆解:
var b = a;這時變數b與a都還是同一個參考路徑 0x01。

a.y = a = { x: 2 };a = { x: 2 };為運算式,所以x: 2會賦予到y上,a.y = a = { x: 2 };這行是同時執行的沒有執行順序的問題,會等於a = a.y = { x: 2 };。所以a.y的參考路徑會是原本的a參考路徑 0x01 而非a新的參考路徑 0x02。a新增一個y屬性,其屬性值為 a ( 參考物件 0x01 ) → 屬性值a新增一個物件,只要新增物件就會新增一個參考路徑,這邊a新增物件的新參考路徑為 0x02 裡面有x屬性與屬性值 2 (a.y參考路徑由 0x01 變 0x02 )。
答案:
console.log(a.y);印出undefined。- a 參考路徑變 0x02 而裡面並沒有 y 屬性,所以為
undefined。
- a 參考路徑變 0x02 而裡面並沒有 y 屬性,所以為
console.log(b);印出{ x:1, y: { x:2 } }。console.log(a === b.y);印出true。
實作 4
1 | var a = { x: 1}; |
解析:
- 到
var b = a;時,a與b都還是同個參考路徑 0x01。 a.x = { x: 2 };,a.x被賦予新物件,當有新物件就會產生一個新的參考路徑,所以 0x01 的x屬性值參考路徑變成 0x02。a.y = a = { y: 1};為運算式所以它們會同時執行沒有順序問題,而a.y = a為最原始的 a 參考路徑 0x01,所以 0x01 多一個y屬性,y的屬性值為新的參考路徑 0x03 ( 有新物件就會產生一個新的參考路徑 )。
答案:
1 | //a |
❒ 淺層複製與深層複製
由於物件有傳參考的特性,所以當兩物件需要拆開處理,就會產生一些困擾,這時就可使用 for in、淺層複製、深層複製來解決這個問題。
❒ 淺層拷貝 ( shallow Copy )
缺點: for in 方式只能做到第一層的複製,第一層以上的都還是會有傳參考特性。
➊ for in
結構: for ( var key in 原物件名稱 ) {},當中的 key 為原物件的屬性。
範例 1.
1 | var family = { |
- 因為淺層拷貝只能移除第一層的傳參考特性,所以
console.log(family.members.ming === newFamily.members.ming);會為true。
➋ 使用 jQuery 方式
記得先載入 jQuery CDN
結構: jQuery.extent({}, 原物件名稱)
範例
1 | var family = { |
- 因為淺層拷貝只能移除第一層的傳參考特性,所以
console.log(family.members.ming === newFamily.members.ming);會為true。
➌ Object.assign ( ES6 方式 )
結構: Object.assign({}, 原物件名稱)
範例
1 | var family = { |
- 因為淺層拷貝只能移除第一層的傳參考特性,所以
console.log(family.members.ming === newFamily.members.ming);會為true。
❹ … 展開的方式 ( 筆者較常用此方式 )
結構: { ...原物件名稱 }
範例
1 | var family = { |
- 因為淺層拷貝只能移除第一層的傳參考特性,所以
console.log(family.members.ming === newFamily.members.ming);會為true。
❒ 深層拷貝
不論物件裡面有幾層,都可透過深層拷貝的方式移除物件傳參考的特性。
使用方式: 把原本物件轉為字串再轉回物件,這種方式可以移除傳參考的特性。
結構:
- 使用 JSON 方式把原物件轉為字串 →
JSON.stringify(原物件名稱) - 透過
JSON.parse把字串再轉回物件 →JSON.parse(JSON.stringify(原物件名稱))
範例
1 | var family = { |
- 因為深層拷貝可以移除物件內所有層的傳參考特性,所以
console.log(family.members.ming === newFamily.members.ming);會為false。
淺層拷貝範例
1 | function changeName(data) { |

- 在
Object.assign執行淺層拷貝時,先執行了changeName(data),物件是傳參考概念而函式也是物件所以也具有傳參考特性。 changeName(data)中 data 參數換成 family,changeName(family)裡面執行了family.name = '杰倫家';會一併修改到family的參考路徑中name的屬性值,所以family.name會印出杰倫家。- 因為淺層拷貝只會移除第一層的傳參考特性 ( 複製 0x01 第一層到 0x03 ),所以
family2.members.jay = '杰倫';中members內為第二層還是保有傳參考特性 ( 0x03 中的 members 參考路徑還是 0 )。
答案:
1 | console.log(`family.name, ${family.name}`); // 杰倫家 |
參考資訊
- 六角學院 - JavaScript 核心篇