物件傳參考是 JS 中非常重要的特性,純值和物件在賦值時的行為模式不同:
先來看看傳值的程式碼:
var name1 = 'Ryder'
var name2 = name1
name2 = 'Jack'
console.log( name1, name2 )//Ryder , Jack
這個狀況很好理解,name1 的資料賦值給 name2,nam2 獨立修改資料,不影響 name1 ,但如果是物件的傳參考狀況呢?
var obj1 = { name:'Ryder' }
var obj2 = obj1
obj2.name = 'Jack'
console.log( obj1.name ,obj2.name) //Jack , Jack
答案會是兩邊都會是 Jack ,並且若再使用 console.log(obj1 === obj2)
他也會回傳 true
,這是因為在 JS 中,物件賦值時是傳參考的。
那什麼是傳參考呢?
根據上面程式碼,一行一行來講解
var obj1 = { name:'Ryder' }
這樣物件本就會有一個獨立的記憶體,目前稱做 00x1,情況大致如圖:
obj2 = obj1
當 obj1
賦值給 obj2
時,其實這段就是提供物件參考的記憶體,因此稱作傳參考,情況如圖:obj1
、 obj2
的記憶體指向是相同記憶體,因此當我們使用 obj2.name
修改時,兩個物件的內容都會被修改。再來看看例外狀況:
試著為 obj2
賦予新物件:
var obj1 = { name:'Ryder' }
var obj2 = obj1
obj2 = { name:'Ryder' }
connsole.log(obj1 === obj2) //false
結果之所以回傳 false
是因為 obj2
有在使用物件實字 {}
建立一個新物件,此時會生成一個新的記憶體指向,用圖表示就是:
最後來看看一個狀況特別的延伸的範例:
var 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
都令人疑惑,這邊一行一行來說明:
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 在編譯這種一行執行的程式碼時,是不會為了個別『運算式』去建立記憶體,因此這種一行程式碼,都還是會使用原始的記憶體指向。