過去在製作MySQL種子資料時,踩到call by reference的錯誤,在此稍微做筆記。
相關背景知識:
在JS中,資料的在記憶體的貯存方式有兩種,按值(by value)與按址(by reference)
基本型別(primitive type,分別有undefined、null、string、number、boolean、symbol)是按值保存
物件(Object,不屬於基本型別的都是物件,包含Object、Array、function)則是按址
假設有個變數a指派一個基本型別,而有另一個變數b指派a時,改變b並不會影響到a
let a = 'primitive type'
let b = a
b = 'another primitive type'console.log(a) // primitive type
console.log(b) // another primitive type
可是當a指派到物件時,b指派a再更動b的值時,a的值也會跟著修改:
const a = {key:'value'}
const b = a
console.log(a) // { key: 'value' }b.key = 'another value'
console.log(a) // { key: 'another value' }
console.log(b) // { key: 'another value' }
狀況
有兩個待做事項todo,請建立userId 1、2,讓這兩個id都有這兩個待做事項
const todos = [
{
name: "todo-1",
done: false
},
{
name: "todo-2",
done: false
}
]
指定輸出到陣列data中
data [
{ name: 'todo-1', done: false, userId: 1 },
{ name: 'todo-2', done: false, userId: 1 },
{ name: 'todo-1', done: false, userId: 2 },
{ name: 'todo-2', done: false, userId: 2 }
]
錯誤解法
for (let id = 1; id < 3; id++) {
for (let i = 0; i < todos.length; i++) {
const newTodo = todos[i]
newTodo.userId = id
data.push(newTodo)
}
}
輸出
data [
{ name: 'todo-1', done: false, userId: 2 },
{ name: 'todo-2', done: false, userId: 2 },
{ name: 'todo-1', done: false, userId: 2 },
{ name: 'todo-2', done: false, userId: 2 }
]
每次push資料到data時,把data內結果列印出來可以看到原本的值改變了;這邊犯了一個錯誤,當要建立newTodo時,直接指派物件,而不是用淺層或深層複製
正確做法(使用展開運算子做淺層複製):
for (let id = 1; id < 3; id++) {
for (let i = 0; i < todos.length; i++) {
const newTodo = {...todos[i]}
newTodo.userId = id
data.push(newTodo)
}
}
物件的複製方式
物件複製方式有兩種,淺層複製(shallow copy)與深層複製(deep copy),淺層是只有第一層能複製,例如前面所用的展開運算子,遇到第二層就沒有複製了
const object1 = { a: { b: 1 } }
const object2 = { ...object1 }object2.a.b = 100
console.log(object1) //{ a: { b: 100 } }
console.log(object2) //{ a: { b: 100 } }
淺層複製有幾種:
- 使用展開運算子(Spread Operator)
- Array.concat
- Array.slice
- Object.assign
深層複製
- jQuery的$.extend
- 轉成JSON再轉回來 (JSON.parse(JSON.stringify(object))),但只有單純資料的物件才行,像是function就不能用這種方式
- Lodash的
_.cloneDeep
- 手動複製(最麻煩,指派的值不能是物件)
var obj1 = { body: { a: 10 } };
var obj2 = { body: { a: obj1.body.a} };
obj2.body.a = 20;console.log(obj1); // { body: { a: 10 } }
console.log(obj2); // { body: { a: 20 } }console.log(obj1 === obj2); // false
console.log(obj1.body === obj2.body); // false
補充 call by sharing
有另一種情況是在function中對參數賦值,這時候便不是按址
const arr1 = {}function add(array){
array.name = 'Nya'
array = {foo:'bar'}
return array
}const arr2 = add(arr1)
console.log(arr1) // { name: 'Nya' }
console.log(arr2) // { foo: 'bar' }
會有這樣的原因主要是在函數中重新賦值物件時,它其實會建立一個同名的新物件,等同於下列情形:
const arr1 = {}let array = arr1
array.name = 'Nya'
array = {foo:'bar'}const arr2 = array
console.log(arr1) // { name: 'Nya' }
console.log(arr2) // { foo: 'bar' }
參考資料
關於JAVASCRIPT中的SHALLOW COPY(淺拷貝)及DEEP COPY(深拷貝)
JavaScript 核心篇 學習筆記: Chap.38 — 淺層複製及深層複製