04. 기본형 데이터와 참조형 데이터
불변값
- 변수(variable)와 상수(constant)를 구분하는 성질은 '변경 가능성'이다. 바꿀 수 있으면 변수, 바꿀 수 없으면 상수다.
- 불변값과 상수는 다른 개념이다. 변수와 상수를 구분 짓는 대상은 변수 영역 메모리이다. 한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당할 수 있는지 여부가 관건이다. 반면 불변성 여부를 구분할 때의 대상은 데이터 영역 메모리이다.
- 기본형 데이터인 숫자, 문자열, Boolean, null, undefined, Symbol 은 모두 불변값이다.
let a = 'abc'; // 변수 a의 기존 문자열 'abc'에 'def'를 추가하면 'abc'가 'abcdef'로 바뀌는 것이 아닌
a = a + 'def'; // 새로운 문자열 'abcdef'를 만들어 그 주소를 변수 a에 저장한다.
let b = 5; // 컴퓨터는 일단 데이터 영역에서 5를 찾고, 없으면 데이터 공간을 하나 만들어 저장한다.
let c = 5; // 이미 만들어놓은 데이터가 있으니 그 주소를 재활용한다.
b = 7; // 변수 b 값을 5에서 7로 바꾸고자 할 때 기존에 저장된 5 자체를 7로 바꾸는 것이 아닌
// 기존에 저장했던 7을 찾아서 있으면 재활용, 없으면 새로 만들어 저장한다.
- 불변값은 이렇듯 한번 데이터 영역에 만든 값은 바꿀 수 없고, 변경은 새로 만드는 동작을 통해서만 이뤄진다.
- 한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 한 영원히 변하지 않는다.
가변값
- 기본형 데이터는 모두 불변값이라고 했는데, 그렇다면 참조형 데이터는 모두 가변값일 것 같은 느낌이 든다.
- 기본적인 성질은 가변값인 경우가 많지만, 설정에 따라 변경 불가능한 경우도 있고, 아예 불변값으로 활용하는 방안도 있다.
// 1
let object1 = {
a: 1,
b: 'bbb'
};
//2
object1.a = 2
변수 영역 | 주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
데이터 | 이름 : object1 값 : @5001 |
||||||
데이터 영역 | 주소 | ... | 5001 | 5002 | 5003 | 5004 | ... |
데이터 | @7103 ~ ? | 1 | 'bbb' | 2 | |||
객체 @5001의 변수 영역 |
주소 | ... | 7103 | 7104 | 7105 | 7106 | |
데이터 | 이름 : a 값 : @5002 바뀐 값: @5004 |
이름 : b 값 : @5003 |
- 기본형 데이터와의 차이는 '객체의 변수(Property) 영역'이 별도로 존재한다는 점이다.
- 위 표를 보면 객체가 별도로 객체 내부의 변수 영역을 할당했을 뿐 '데이터 영역'은 기존 메모리 공간 그대로 활용한다.
- 데이터 영역에 저장된 값은 불변값이지만 변수에는 얼마든지 다른 값을 대입할 수 있다.
- 바로 이 부분 때문에 흔히 참조형 데이터는 불변하지 않다(가변값이다) 라고 하는 것이다.
- 2번의 경우 객체 내부의 a의 값을 2로 재할당하려 할 때, 우선 데이터 영역에서 2 값을 가진 데이터를 찾고 없다면 빈 공간에 저장한다. 그리고 객체 변수 영역의 @7103 주소로 가서 값을 바꾼다. 이 과정에서 데이터를 제외한 변수의 주소는 바뀌지 않으며 새로운 객체가 만들어지는 것이 아닌 기존의 객체 내부의 값만 바뀌는 것이다.
변수 복사 비교
- 기본형 데이터와 참조형 데이터의 변수 복사 차이를 알아보자.
let a = 10;
let b = a;
let object1 = {c:10, d:'ddd'};
let object2 = object1;
- 우선 변수 a와 b는 같은 데이터 주소를 갖는다. (ex 주소 @5001, 데이터 10)
- 변수 object1과 object2는 같은 데이터 그룹의 주소를 가지며 객체 내부에 있는 변수 c는 변수 a와 같은 데이터 주소를 갖는다.
let a = 10;
let b = a;
let object1 = {c:10, d:'ddd'};
let object2 = object1;
b = 15;
object2.c = 20;
- 변수 복사 이후 값을 변경했을 때의 결과를 코드 형식으로 표현하면 다음과 같다.
- a !== b / object1 === object2
- 변수 a와 b는 서로 다른 주소값을 바라보게 됐지만, 변수 object1과 object2는 여전히 같은 객체를 바라보고 있다. 이 때 object2는 사실상 변하지 않은 것이다.
- 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고(변수 -> 데이터 주소값),
- 참조형은 한 단계를 더 거치기 때문(변수 -> 데이터 그룹 주소값 -> 객체 각 변수 영역의 주소값)에 데이터 그룹 주소값이 변하지 않는 이상 달라진 것이 아니다.
- 정리하자면 참조형 데이터가 '가변값'이라고 설명할 때의 '가변'은 참조형 데이터 자체(object2.c = 20)를 변경할 경우가 아니라 그 내부의 프로퍼티(object2 = {c:20, d: 'ddd'})를 변경할 때만 성립한다.
05. 불변 객체
객체의 가변성에 따른 문제점
let user = {
name: 'John',
gender: 'male'
};
let changeName = function(user, username) {
let newUser = user;
newUser.name = username;
return newUser;
};
let user2 = changeName(user, 'Kim');
if (user !== user2) console.log('유저 정보가 변경되었습니다.');
console.log(user.name, user2.name); // Kim Kim
console.log(user === user2); // true
- 코드의 의도는 user2의 name을 Kim으로만 바꾸려 했는데 이 과정에서 user와 user2의 name이 모두 바뀌는 문제가 발생했다.
- 이로 인해 user와 user2가 동일하다고 판단하는 문제까지 발생했고, 이를 위해 다음과 같이 바꿔보기로 했다.
let user = {
name: 'John',
gender: 'male'
};
let changeName = function(user, username) {
return {
name: username,
gender: user.gender
};
};
let user2 = changeName(user, 'Kim');
if (user !== user2) console.log('유저 정보가 변경되었습니다.'); // 유저 정보가 변경되었습니다.
console.log(user.name, user2.name); // John Kim
console.log(user === user2); // false
- changeName 함수가 새로운 객체를 반환하도록 수정했다. 다만 새로운 객체를 만들면서 변경할 필요가 없는 기존 객체의 프로퍼티(gender)를 하드코딩으로 입력했다.
- 이런 식으로는 대상 객체에 정보가 많을수록, 변경해야할 정보가 많을수록 입력하는 수고가 늘게 된다.
// 기존 정보를 복사해 새로운 객체를 반환하는 함수 (얕은 복사)
const copyObject = function (target) {
let result = {};
for (let i in target) {
result[i] = target[i];
}
return result;
};
let user = {
name: 'John',
gender: 'male'
};
let user2 = copyObject(user);
user2.name = 'Kim';
if (user !== user2) console.log('유저 정보가 변경되었습니다.'); // 유저 정보가 변경되었습니다.
console.log(user.name, user2.name); // John Kim
console.log(user === user2); // false
- copyObject 함수를 통해 간단히 객체를 복사하고 내용을 수정하는데 성공했다. 다만 '얕은 복사만을 수행한다' 는 부분이 아쉬운데 그 부분은 다음 파트에서 좀 더 설명해보도록 하겠다.
얕은 복사와 깊은 복사
- 얕은 복사는 바로 아래 단계의 값만 복사하는 방법이고, 깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법이다.
- 위 예제는 얕은 복사만 수행했는데, 이 말은 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다는 의미이다.
// copyObject 함수가 있다는 가정하에
let user = {
name: 'John',
favoriteCity: {
korea: 'Seoul',
france: 'Paris',
japan: 'Osaka'
}
};
let user2 = copyObject(user);
user2.name = 'Kim';
console.log(user === user2); // false
user.favoriteCity.korea = 'Suwon';
console.log(user.favoriteCity.korea === user2.favoriteCity.korea); // true
user2.favoriteCity.france = '';
console.log(user.favoriteCity.france === user2.favoriteCity.france); //true
- 위 예시에서 얕은 복사를 통해 user2를 만들어 name을 수정하면 두 객체는 다르다는 결과가 나온다. 하지만 한 단계 더 들어간 객체인 favorityCity를 수정하면 원본과 사본 모두 바뀌어 같다는 문제가 발생한다.
- 이는 내부 프로퍼티들은 기존 데이터를 그대로 참조하기 때문이다.
- 이 문제를 해결하기 위해서는 user.favorityCity 프로퍼티에 대해서도 불변 객체로 만들 필요가 있다.
프로퍼티(Property) : '속성' 이라는 뜻으로, JavaScript에서는 객체 내부의 속성을 뜻한다.
// 중첩된 객체에 대한 깊은 복사
let user2 = copyObject(user);
user2.favoriteCity = CopyObject(user.favoriteCity);
user.favoriteCity.korea = 'Sokcho';
console.log(user.favoriteCity.korea === user2.favoriteCity.korea); //false
user2.favoriteCity.japan = '';
console.log(user.favoriteCity.japan === user2.favoriteCity.japan); //false
- 위와 같이 수정했을 때 내부 프로퍼티까지 복사해 완전히 새로운 데이터가 만들어진 것을 확인할 수 있다.
- 어떤 객체를 복사할 때 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 프로퍼티 중 그 값이 기본형 데이터일 경우 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 한다.
- 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야 비로소 깊은 복사가 되는 것이다.
- 정리하자면 깊은 복사는 데이터 자체를 통째로 복사해 완전히 독립적인 메모리를 갖게 해주는 복사 방법이며, 얕은 복사는 아주 최소한만 복사를 하며 값 자체를 복사하는 것이 아니라 주소값을 복사하여 같은 메모리를 가리키는 방법이다.
Chapter 1 후기
챕터 1을 읽기까지 약 30페이지 정도가 소요됐다. 정말 짧은 양인데 이만큼 읽고 나서 드는 생각은, '확실히 동작하는 원리를 이해하면서 학습하는 것이 중요하다' 였다. 단순히 기술 면접을 준비하기 위해 깊은 복사와 얕은 복사에 대해 블로그 글을 보며 읽었을 때는 크게 와닿지도 않고 내용도 이해하기 어려웠는데, 이렇게 동작 원리를 하나하나 학습하면서 이해하다보니 개념이 저절로 습득됐다. 또한 이러한 기초를 탄탄히 다져야 라이브러리에서 발생하는 에러만이 아닌 더 깊은 에러의 원인을 보다 빠르게 파악해 해결할 수 있겠다는 생각이 들었다. 앞으로 6챕터 정도 남아있는데 매일은 아니더라도 꾸준히 읽어서 이 시리즈를 온전히 마무리하고 개발자로써 한층 더 탄탄히 성장하고 싶다.
참고로 마지막 파트인 undefined 와 null 부분도 작성하려 했지만 이미 학습한 내용이라 따로 적지 않을 예정이다.
대신 둘의 차이를 알고 싶다면 아래 링크에서 확인하는 것을 추천한다.
https://nyol.tistory.com/147
반응형
'Programming > 4. JavaScript & React' 카테고리의 다른 글
007_TDD, 그리고 Storybook (0) | 2022.02.14 |
---|---|
006_코어 자바스크립트 챌린지_3 (0) | 2022.02.02 |
004_Create React App 기본 구조에 대해 알아보자! (0) | 2022.01.18 |
003_코어 자바스크립트 챌린지_1 (0) | 2022.01.12 |
002_indexOf() 와 findIndex() (0) | 2021.07.18 |
댓글