자바스크립트가 데이터를 처리하는 과정을 살펴봄으로써 기본형 타입과 참조형 타입이 서로 다르게 동작하는 이유를 이해하고, 이를 적절히 활용할 수 있게 되는 것을 목표로 한다.
- 데이터 타입의 종류
- 데이터 타입에 관한 배경지식
- 변수 선언과 데이터 할당
- 기본형 데이터와 참조형 데이터
- 불변 객체 (immutable object)
- undefined와 null
1. 데이터 타입의 종류
자바스크립트의 데이터 타입은 크게 기본형(원시형, primitive type)과 참조형(reference type)으로 나눌 수 있다.
2. 데이터 타입에 관한 배경지식
메모리와 데이터
0 또는 1만 표현할 수 있는 하나의 메모리 조각을 비트(bit)라고 한다.
메모리는 매우 많은 비트들로 구성되어 있는데, 각 비트는 고유한 식별자를 통해 위치를 확인할 수 있다.
하지만 비트 단위로 위치를 확인하는 것은 매우 비효율적이다.
검색 시간을 줄이고 더 많은 데이터를 표현하기 위해 바이트(byte)라는 단위가 생겨났다.
모든 데이터는 바이트 단위의 식별자, 더 정확하게는 메모리 주솟값을 통해 서로 구분하고 연결할 수 있다.
식별자와 변수
- 변수(variable): 변할 수 있는 무언가 (데이터)
- 식별자(identifier): 어떤 데이터를 식별하는 데 사용하는 이름 (변수명)
3. 변수 선언과 데이터 할당
변수 선언
[변수 선언]
메모리에서 비어있는 공간 하나를 확보하고, 이 공간의 이름을 a라고 지정한다.
var a;
데이터 할당
[변수 선언과 할당]
var a; // 변수 a 선언
a = 'abc'; // 변수 a에 데이터 할당
var a = 'abc'; // 변수 선언과 할당을 한 문장으로 표현
- 변수 영역에서 빈 공간 (@1003) 확보
- 확보한 공간의 식별자를 a로 지정
- 데이터 영역의 빈공간 (@5004)에 문자열 'abc' 저장
- 변수 영역에서 a라는 식별자 검색
- 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입
변수 영역에 값을 직접 대입하지 않는 이유?
- 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위함.
- 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장하는 것이 최적이다.
- 특히 중복된 데이터에 대한 처리 효율이 높아진다.
a = 'abcdef'; // a의 마지막에 def를 추가
✔️ 기존 문자열에 어떤 변환을 가하든 상관없이, 무조건 새로 만들어 별도의 공간에 저장하고 그 주소를 변수 공간에 연결한다.
4. 기본형 데이터와 참조형 데이터
불변값
변수(variable) VS 상수(constant)
변수와 상수를 구분하는 성질은 '변경 가능성'이다.
바꿀 수 있으면 변수, 바꿀 수 없으면 상수다.
변수와 상수를 구분 짓는 변경 가능성의 대상은 변수 영역 메모리이다.
한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당 할 수 있는지 여부가 관건이다.
불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역의 메모리이다.
✔️ 기본형 데이터인 숫자, 문자열, boolean, null, undefined, Symbol은 모두 불변값이다.
문자열 값도 숫자 값도 한 번 만든 값을 바꿀 수 바꿀 수 없다.
변경은 새로 만드는 동작을 통해서만 이뤄진다.
한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 한 영원히 변하지 않는다.
가변값
[참조형 데이터의 할당]
var obj1 = {
a: 1,
b: 'bbb'
};
- 변수 영역의 빈 공간(@1002)을 확보하고 그 주소의 이름을 obj1로 지정
- 임의의 데이터 저장 공간(@5001)에 데이터를 저장하려 보니 여러 개의 프로퍼티로 이루어진 데이터 그룹.
별도의 변수 영역을 마련 후 그 영역의 주소(@7103 ~?)를 @5001에 저장 - @7103, @7104에 각각 a, b 프로퍼티 이름을 지정
- 데이터 영역에서 숫자 1을 임의로 @5003에 저장하고 이 주소를 @7103에 저장.
문자열 'bbb' 역시 임의로 @5004에 저장하고 이 주소를 @7104에 저장
✔️ 기본형 데이터와의 차이는 '객체의 변수(프로퍼티) 영역'이 별도로 존재한다는 점이다.
[참조형 데이터의 프로퍼티의 재할당]
var obj1 = {
a: 1,
b: 'bbb'
};
obj1.a = 2;
✔️ 새로운 객체가 만들어진 것이 아니라 기존 객체 내부의 값만 바뀐다.
[중첩된 참조형 데이터(객체)의 프로퍼티 할당]
var obj = {
x: 3,
arr: [3, 4, 5]
};
- 변수 영역의 빈 공간(@1002) 확보 후 주소의 이름을 obj로 지정
- 임의의 데이터 저장공간(@5001)에 객체의 변수 영역 주소(@7103~?)를 저장
- @7103에 이름 x, @7104에 이름 arr를 저장
- 숫자 3을 임의로 @5002에 저장 후 이 주소를 @7103에 저장
- 임의의 데이터 저장공간(@5003)에 배열의 변수영역 주소(@8103~?)를 저장
- 배열의 요소 개수만큼 변수 공간을 확보하고 각각 인덱스를 부여
- 숫자를 검색해서 그 주소를 배열의 변수 영역에 저장하거나, 임의의 데이터 영역에 저장 후 그 주소를 배열의 변수 영역에 저장.
obj.arr[1]을 검색하는 과정
- odj라는 식별자를 가진 주소 검색 (@1002)
- 값이 주소이므로 그 주소로 이동(@5001)
- 값이 주소이므로 그 주소로 이동(@7103~?)
- obj.arr라는 식별자를 가진 주소 검색 (@7104)
- 값이 주소이므로 그 주소로 이동(@5003)
- 값이 주소이므로 그 주소로 이동(@8103~?)
- obj.arr[1] 검색 1: 인덱스 1에 해당하는 주소 검색 (@8104)
- obj.arr[1] 검색 2: 값이 주소이므로 그 주소로 이동 (@5004)
- obj.arr[1] 검색 2: 값이 숫자형 데이터이므로 4를 반환
변수 복사 비교
[변수 복사]
var a = 10;
var b = a;
var obj1 = {c: 10, d: 'ddd'};
var obj2 = obj1;
[변수 복사 이후 값 변경 결과 비교 - 객체의 프로퍼티 변경 시]
var a = 10;
var b = a;
var obj1 = {c: 10, d: 'ddd'};
var obj2 = obj1;
b = 15;
obj2.c = 20;
a !== b
obj1 === obj2
기본형 데이터와 참조형 데이터의 가장 큰 차이점
- 기본형 데이터는 주솟값을 복사하는 과정이 한 번만 이뤄지고, 참조형 데이터는 한 단계를 더 거치게 된다.
- 참조형 데이터가 '가변값'이라고 설명할 때의 '가변'은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부 프로퍼티를 변경할 때만 성립한다.
5. 불변 객체
불변 객체를 만드는 간단한 방법
참조형 데이터의 '가변'은 데이터 자체가 아닌 내부 프로퍼티를 변경할 때만 성립한다.
데이터 자체를 변경하고자 하면 (새로운 데이터를 할당하고자 하면) 기본형 데이터와 마찬가지로 기존 데이터는 변하지 않는다.
내부 프로퍼티를 변경할 필요가 있을 때, 매번 새로운 객체를 만들어 재할당하거나, 자동으로 새로운 객체를 만드는 도구를 활용하면 객체 역시 불변성을 확보할 수 있다.
[객체의 가변성에 따른 문제점]
var user = {
name: 'Jaenam',
gender: 'male'
};
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
var user2 = changeName(user, 'Jung');
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // Jung Jung
console.log(user === user2); // true
// 변경 전과 후에 서로 다른 객체를 바라보도록 수정이 필요.
[객체의 가변성에 따른 문제점의 해결 방법]
var user = {
name: 'Jaenam',
gender: 'male'
};
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender
};
};
var user2 = copyObject(user); // 얕은 복사만 수행한다.
user2.name = 'jung';
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.'); // 유저 정보가 변경되었습니다.
}
console.log(user.name, user2.name); // Jaenam Jung
console.log(user === user2); // false
// 대상 객체의 프로퍼티 개수에 상관없이 모든 프로퍼티를 복사하는 함수를 만드는 편이 더 좋을 것.
얕은 복사(shalloow copy)와 깊은 복사(deep copy)
얕은 복사
- 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다.
- 즉 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터의 주소를 가리키게 된다.
따라서, 사본을 바꾸면 원본도 바뀌고 원본을 꾸면 사본도 바뀐다.
깊은 복사
- 어떤 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때 사용한다.
- 객체의 프로퍼티 중 그 값이 기본형 데이터일 경우에는 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 한다.
- 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야만 비로소 깊은 복사가 된다.
[객체의 깊은 복사를 수행하는 범용 함수]
var copyObjectDeep = function(target) {
var result = {};
if (typeof target === 'object' && target !== null) { // typeof가 null 도 object로 반환하기 때문
for (var prop in target) {
result[prop] = copyObjectDeep(target[prop]);
}
} else {
result = target;
}
return result;
};
[JSON을 활용한 간단한 깊은 복사]
var copyObjectViaJSON = function (target) {
return JSON.parse(JSON.stringify(target));
};
var obj = {
a: 1,
b: {
c: null,
d: [1, 2],
func1: function () { console.log(3); }
},
func2: function () { console.log(4); }
};
var obj2 = copyObjectViaJSON(obj);
obj2.a = 3;
obj2.b.c = 4;
obj.b.d[1] = 3;
console.log(obj); // { a: 1, b: { c: null, d: [ 1, 3 ], func1: f() }, func2: f() }
console.log(obj2); // { a: 3, b: { c: 4]], d: [ 1, 2 ] } }
6. undefined와 null
undefined
사용자가 명시적으로 지정할 수도 있지만(비추천), 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여한다.
- 값을 대입하지 않은 변수. 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
- 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
- return 문이 없거나 호출되지 않는 함수의 실행 결과
null
비어있음을 명시적으로 나타내고 싶을 때 사용.
'아카이브 > JavaScript' 카테고리의 다른 글
[코어 자바스크립트] 06 프로토타입 (0) | 2021.12.07 |
---|---|
[코어 자바스크립트] 05 클로저 (0) | 2021.12.06 |
[코어 자바스크립트] 04 콜백함수 (0) | 2021.12.05 |
[코어 자바스크립트] 03 this (0) | 2021.12.04 |
[코어 자바스크립트] 02 실행 컨텍스트 (0) | 2021.11.30 |