[Javascript] 14. JavaScript References VS Copying
1. Copying
// start with strings, numbers and booleans
let age = 100;
let age2 = age;
console.log(age, age2);
age = 200;
console.log(age, age2);
let name = 'wes';
let name2 = name;
console.log(name, name2);
name = 'wesley';
console.log(name, name2);
문자열, 숫자, 불린 자료형을 변수로 할당하면, 해당 값은 메모리에 저장된다. 위 코드에서, age에 100을 할당하고, age2에 age를 할당한 후 콘솔 창에 나타내 보면 100과 100으로 출력될 것이다. 이후, age의 값을 200으로 바꾸고 콘솔 창에 출력해 보면, age는 200으로 바뀌겠지만, age2는 age임에도 불구하고 그 값은 여전히 100이다. 왜냐하면, age2는 age의 값을 참조하는 것이 아니라, 변하기 전의 age의 값을 복사한 것이므로, age의 값이 변한다고 해서 age2의 값이 같이 변하지는 않는다. 문자열을 대상으로 한 name 역시 결과는 같다.
2. References
// Let's say we have an array
const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];
// and we want to make a copy of it.
const team = players;
console.log(players, team);
// You might think we can just do something like this:
team[3] = 'Lux';
console.log(players, team);
반면, 배열이나 객체 등은 변수에 할당해도 해당 데이터가 메모리에 저장되는 것이 아니고, 해당 데이터를 참조하도록 되어 있다. 위와 같이, team이라는 변수에 players 배열을 할당하면, 두 변수, team과 players는 같은 배열을 참조하게 된다. 그러므로, team 배열의 요소를 바꾸면, players 배열의 요소도 바뀌게 된다.
3. Copying An Array
// So, how do we fix this? We take a copy instead!
const players2 = ['Wes', 'Sarah', 'Ryan', 'Poppy'];
const team2 = players2.slice();
team2[3] = 'Lux';
console.log(players2, team2);
이는 다양한 방법으로 해결이 가능한데, 특히, 배열에서는 slice API를 이용할 수 있다. slice(start, end)는 start와 end를 입력하지 않으면 배열을 전부 복사하여 반환한다. 같은 배열을 참조하는 것이 아닌, 배열을 복사하여 반환하므로, team2[3]의 값을 변경해도 players2의 값은 변하지 않는다.
// or create a new array and concat the old one in
const team3 = [].concat(players2);
team3[3] = 'Hi-ha';
console.log(players2, team3);
혹은, 빈 배열을 하나 만든 후, concat으로 원본 배열과 합치는 방법을 이용할 수도 있다. 이렇게 만들어진 team3의 요소를 변경해도, 마찬가지로 players2는 영향을 받지 않는다.
// or use the new ES6 Spread
const team4 = [...players2];
team4[3] = 'Holy!';
console.log(players2, team4);
또는 Spread를 이용해서 players2의 요소들을 그대로 가져올 수 있다.
const team5 = Array.from(players2);
team5[3] = "Huhehe";
console.log(players2, team5);
혹은 Array.from()을 통해 복사해 올 수도 있다.
4. Copying An Object
// now when we update it, the original one isn't changed
// The same thing goes for objects, let's say we have a person object
// with Objects
const person = {
name: 'Wes Bos',
age: 80
};
// and think we make a copy:
const captain = person;
captain.number = 99;
console.log(person, captain);
이번에는 객체를 복사한다고 가정해 보자. 위 코드에서는, captain이 person을 참조하므로, 콘솔 창을 확인하면 person에도 number: 99라는 데이터가 추가되어 있을 것이다.
// how do we take a copy instead?
const cap2 = Object.assign({}, person, { number: 69, age: 12 });
console.log(person, cap2);
Object.assign(target, source) 매소드를 통해 복사를 진행할 수 있다. target은 빈 객체, source는 person 객체와 { number: 69, age: 12 } 객체로 지정한다. assign 매소드는, source로 같은 key를 가진 복수의 객체가 주어진 경우, 더 뒤에 위치한 객체의 값으로 대체한다. 이후, person과 cap2를 콘솔창에 출력하면, person의 number과 age는 변하지 않은 채 80, 99를 유지하고 있을 것이다.
5. Shallow Copy
위에서 언급한 다섯 가지 방법은 모두 얕은 복사에 해당한다.
const players3 = ['Wes', 'Sarah', 'Ryan', ['Poppy', 'Boppy']];
const team2S = players3.slice();
team2S[3][1] = 'Lux';
console.log(players3, team2S);
const team3S = [].concat(players3);
team3S[3][1] = 'Hi-ha';
console.log(players3, team3S);
const team4S = [...players3];
team4S[3][1] = 'Holy!';
console.log(players3, team4S);
const team5S = Array.from(players3);
team5S[3][1] = "Huhehe";
console.log(players3, team5S);
이를테면, 위와 같이 players3의 마지막 요소를 배열로 바꿔 보자. 그 후, 위의 네 가지 방법을 통해 각각 복사를 한 뒤, players3의 마지막 요소의 마지막 요소를 바꿔 보면, 복사를 진행했음에도 불구하고 players3의 내용이 바뀐다. 즉, 위 네 가지 방법을 사용해 복사를 진행하면, 배열의 요소가 배열이나 객체인 경우에는 이를 참조하게 된다.
객체의 경우에도 마찬가지이다.
// Things to note - this is only 1 level deep - both for Arrays and Objects. lodash has a cloneDeep method, but you should think twice before using it.
const wes = {
name: 'Wes',
age: 100,
social: {
twitter: '@wesbos',
facebook: 'wesbos.developer'
}
}
const dev = Object.assign({}, wes);
dev.social.facebook = 'hiwes.developer'
console.log(wes, dev);
얕은 복사를 하였으므로 wes.social.facebook의 값도 hiwes.developer로 바뀌게 된다.
6. Deep copy
const dev2 = JSON.parse(JSON.stringify(wes));
dev2.social.facebook = 'goodbyeWes.developer'
console.log(wes, dev2);
JSON.stringify로 해당 객체나 배열을 문자열로 돌린 뒤, JSON.parse로 다시 객체나 배열로 되돌리면 깊은 복사가 진행된다. 이를 통해 중첩 구조를 가진 객체나 배열도 완전하게 복사할 수 있으며, 이에 따라 위 코드에서 wes.social.facebook의 값은 변하지 않는다.