艾草:「來,接球!」
(我看著眼前一顆球飛了過來,正準備伸出手接時,突然變成了兩顆。)
「咦咦,為何?」
艾草:「我用魔法複製了一顆球呀!」
「這麼好用,那我能不能拿來複製房子呀?」
艾草:「沒那麼好的事,如果複製大型物件像房子,魔法只能複製出它的樣子,但你去改房子的擺設,也會連動影響到原本的房子,因為它們實際上還是存在同一個空間。」
「它的界線在哪呀?求教學。」
(不知道有沒有機會複製錢錢 ($ε $ ) )
在開始介紹傳值、傳參考前想先補充一個小知識。
其實我們每宣告一個變數,都會有相對應的記憶體空間存放變數,也會有另一個記憶體空間存放變數的值,例如你宣告一個變數 a
值為數字 1 的話,可以想像記憶體會如下表格:
了解了記憶體空間的小概念後,讓我們接著學習囉!
在值為原始型別的情況下如果設變數定 a
、 b
的值相同時,比較 a
與 b
會得到以下結果:
let a = 1;
let b = 1;
console.log(a === b)//true
在原始型別的情況下,是去比較這兩個值是否相等。
而當將 a
的值賦予給 b
時,其實它會直接拷貝 a
的值數字 1 過來,因為是複製的關係,所以今天將 b
重新賦予一個數字型別的 2 ,也不會影響到 a
。
let a = 1;
let b = a;
b = 2;
console.log(a)//1
在原始型別的情況下,因為是傳值的關係,所以就算我改變了變數 b
的值,也並不會連帶影響到變數 a
,他們的變數記憶體指向就是兩條平行線,如圖:
而物件型別(包含函式)傳參考 (by reference)有什麼特性呢?
在針對物件去做比較時會發現以下情況:
let obj = {
key:'value'
}
let newObj = {
key:'value'
}
console.log(obj === newObj)//false
會發現裡面的屬性與值明明是一樣的,但回傳比對結果卻是 false
,因為物件是傳參考,所以比對的是記憶體位置,還不太了解沒關係,讓我們繼續看下去。
let obj = {
key:'value',
};
let newObj = obj;
console.log(obj === newObj) //true
newObj.name = "王小明";
console.log(obj)//{key: "value", name: "王小明"}
像這樣明明我們改的是 newObj
卻連 obj
也更動了,原因就是因為物件是透過傳參考的形式,傳參考代表 newObj
其實是指向 obj
的記憶體空間,如圖:
我們簡單替他們的記憶體空間編碼為 0x001,可以清楚地看到兩者都是指向同一個記憶體空間,所以當你改動 newObj
時也會同時改動到 obj
。
補充:在變數宣告章節有提到關於 const
宣告原始型別難以被重新賦予值,但物件型別因為是傳參考的特性,所以只要不改變記憶體空間就不會報錯,如下:
//不會報錯
const obj = {
key:'value'
}
obj.name = "王小明";
//會報錯
const obj = {
key:'value'
}
obj = {};//Uncaught TypeError: Assignment to constant variable.
只要不直接賦予該變數一個新的物件 {}
大括號,只是單純去修改物件值,不影響到記憶體空間的情況下,可以透過 const
去宣告物件型別的值。
小結:原始型別的值是透過拷貝的方式,所以並不會互相影響,物件型別會共用同一個記憶體空間,所以會互相連動。
而該如何避免這個情況呢?
我們可以透過以下幾種方法:
淺層拷貝指可以將第一層的位置的記憶體位置指向其他記憶體空間,但如果物件內又包了一個物件的值,該物件的值還是會與原物件共用一個記憶體空間:
Object.assign()
語法,可以複製一個或多個物件所有的屬性到新物件上,如下:
let obj = {
key:'value'
}
let newObj = Object.assign({},obj);
console.log(obj === newObj)//false
展開運算子 ...
,透過 ES6 的新語法展開運算子也可以達成淺層拷貝:
let obj = {
key:'value'
}
let newObj = {
...obj
};
console.log(obj === newObj)//false
直接使用將物件轉字串又轉回物件的方式。
let obj = {
key:'value'
}
let newObj = JSON.parse(JSON.stringify(obj));
console.log(obj === newObj)//false
Object.assign()
、展開運算子JSON.parse(JSON.stringify(obj))
請問以下敘述何者錯誤?
A 原始型別的情況下是傳值,物件型別的情況是傳參考
B 用 const
宣告的情況下,無論原始型別或物件型別,都不能修改任何值
C 如果想淺層拷貝可以透過展開運算子、Object.assign()
語法
D 傳參考指的是記憶體會指向同一個記憶體位置
解答:用 const
宣告的物件型別,因為物件傳參考的特性,在不透過 =
重新賦予記憶體空間的情況下,進行屬性值的新增、修改並不會報錯。
0 陷阱!0 誤解!8 天重新認識 JavaScript!(iT邦幫忙鐵人賽系列書)
Vue 3 實戰影音課程(六角學院)