(Day11) 物件參考特性
傳值與傳參考
物件傳參考是 JS 中非常重要的特性,純值和物件在賦值時的行為模式不同:
- 純值在賦值時會是傳值
- 物件型別都是傳參考(參考記憶體)
先來看看傳值的程式碼:
1
2
3
4var name1 = 'Ryder'
var name2 = name1
name2 = 'Jack'
console.log( name1, name2 )//Ryder , Jack傳參考範例
這個狀況很好理解,name1 的資料賦值給 name2,nam2 獨立修改資料,不影響 name1 ,但如果是物件的傳參考狀況呢?答案會是兩邊都會是 Jack ,並且若再使用1
2
3
4var obj1 = { name:'Ryder' }
var obj2 = obj1
obj2.name = 'Jack'
console.log( obj1.name ,obj2.name) //Jack , Jackconsole.log(obj1 === obj2)
他也會回傳true
,這是因為在 JS 中,物件賦值時是傳參考的。
那什麼是傳參考呢?
根據上面程式碼,一行一行來講解
- 首先再建立一個物件時,該物件便會創建一個記憶體,也就是範例中的:這樣物件本就會有一個獨立的記憶體,目前稱做 00x1,情況大致如圖:
1
var obj1 = { name:'Ryder' }
- 再來
obj2 = obj1
當obj1
賦值給obj2
時,其實這段就是提供物件參考的記憶體,因此稱作傳參考,情況如圖:
也因為obj1
、obj2
的記憶體指向是相同記憶體,因此當我們使用obj2.name
修改時,兩個物件的內容都會被修改。
物件實字 {} 建立新記憶體
再來看看例外狀況:
試著為obj2
賦予新物件:
1
2
3
4
5var obj1 = { name:'Ryder' }
var obj2 = obj1
obj2 = { name:'Ryder' }
connsole.log(obj1 === obj2) //false
結果之所以回傳false
是因為obj2
有在使用物件實字{}
建立一個新物件,此時會生成一個新的記憶體指向,用圖表示就是:
特殊範例
最後來看看一個狀況特別的延伸的範例:
1
2
3
4
5
6var obj1 = { a:1 }
var obj2 = obj1
obj1.a = { a:2 }
obj1.b = obj1 = { b:1 }
obj1 //{b: 1}
obj2 //{a: {a:2}, b: {b:1}}
這邊出現的結果不論是obj1
、obj2
都令人疑惑,這邊一行一行來說明:
1
var obj1 = { a:1 }
- obj1 創立一個物件此時誕生一個新記憶體 00x1 ,內容是
{ a:1 }
,狀況如圖:
- obj2 賦值實際獲得的是 00x1 記憶體,如圖:
- obj1 中的 a 屬性從原本純值 1 替換成新物件
{ a:2 }
,此時也會建立新記憶體 00x2 ,如圖:
- 接下來是重點
obj1.b = obj1 = { b:1 }
根據運算子相依性特性obj1 = { b:1 }
會先執行,同時我們也看到obj1
重新賦值一個新物件,因此會誕生一個新記憶體 00x3 ,並且obj1
指向的記憶體會被更改成 00x3,如圖:
- 接下來是將
obj1 = { b:1 }
這段回傳的{ b:1 }
賦予到obj1.b
上,但要注意的是我們輸入的是obj1.b = obj1 = { b:1 }
這段只是一行程式碼,JS 實際上在編譯時不會為了obj1 = { b:1 }
馬上建立一段新的記憶體,因此obj1.b
實際指向的並非是後來變更的 00x3 而是變更前的 00x1 ,因此就會是 00x1 又塞入了 00x3 內容,如圖:
最後使用console.log(obj1 === obj2.b)
答案也會是 true 了。
P.S. 最讓人疑問的便是obj1.b = obj1 = { b:1 }
這段。
而這段的重點是,JS 在編譯這種一行執行的程式碼時,是不會為了個別『運算式』去建立記憶體,因此這種一行程式碼,都還是會使用原始的記憶體指向。
參考資料
- JavaScript 核心篇 (六角學院)
- JavaScript 核心觀念(27)-物件-物件參考觀念的實際運作模式