1) 데이터 타입
1. 데이터 타입의 종류
-
javasscript의 데이터 타입은 크게 두 가지로 나뉜다: 기본형, 참조형
- 기본형: number, string, boolean, null, undefined, symbol
- 참조형: object(Map, WeakMap, Set, WeakSet), array, function, date, regexp
-
기본형: 할당/연산 시 복제된다
- 값이 담긴 주솟값을 바로 복제
- 불변성(immutability)를 띈다.
-
참조형: 할단/연산 시 참조된다.
- 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제
2. 데이터 타입에 관한 배경지식
- 모든 데이터는 바이트 단위의 식별자, 즉 ‘메모리 주솟값’을 통해 서로 구분하고 연결할 수 있다.
- 변수: variable. 변할 수 있는 데이터 (숫자, 문자열, 객체, 배열 등)
- 식별자: 어떤 데이터를 식별하는데 사용하는 이름. (=변수명)
3. 변수 선언과 데이터 할당
변수 선언:
var a // 변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 한다.- 즉, 변수는 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇
- 컴퓨터가 위와 같이 변수를 선언하면 메모리 영역에서 아래와 같은 작업을 수행한다.
| 주소 | .. | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: a / 값: |
데이터 할당:
var a // 변수 a 선언
a = 'abc' // 변수 에 데이터 할당
var a = 'abc' // 변수 선언과 할당을 한 문장으로 표현- 실제로 해당 ‘a’ 변수 데이터에 문자열 ‘abc’를 직접 저장하지는 않는다. 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 문자열 ‘abc’를 저장하고, 그 주소를 변수 영역에 저장한다.
변수 영역
| 주소 | .. | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: a / 값: @5004 |
데이터 영역
| 주소 | .. | 5002 | 5003 | 5004 | 5005 | … |
|---|---|---|---|---|---|---|
| 데이터 | ‘abc’ |
1) 변수 영역에서 빈 공간 (@1003)을 확보한다.
2) 확보한 공간의 식별자를 a로 지정한다.
3) 데이터 영역의 빈 공간(@5004)에 문자열 'abc'를 저장한다.
4) 변수 영역에서 a라는 식별자를 검색한다.(@1003).
5) 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입한다.- 변수 영역에 값을 직접 대입하지 않고, 한단계를 더 거치는 이유?
- 데이터 변환을 자유롭게 할 수 있게 함과 동시에, 메모리를 더욱 효율적으로 관리
- js는 숫자형 데이터에 8바이트(64비트)의 공간을 확보하는데, 문자열은 정해진 규격이 없다. 메모리 용량이 가변적이므로 ‘확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업’이 필요 없음.
- 결국, 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장해야함
4. 기본형 데이터와 참조형 데이터
불변값
- 변수(variable)과 상수(constant)를 구분하는 성질은 ‘변경 가능성’
- 불변값은 상수가 아님.
- 변수 vs 상수 구분 짓는 변경 가능성의 대상은 변수 영역의 메모리
- 불변성 여부를 구분할 때의 변경 가능성은 데이터 영역의 메모리
- 기본형 데이터(숫자, 문자열, boolean, null, undefined, Symbol) 모두 불변값
불변성 예시:
var a = 'abc' // 변수 a에 'abc' 데이터 주소 할당
a = a + 'def' // 기본 'abc'에 'def' 추가가 아니라, 새로운 문자열 'abcdef'를 만들어 a에 주소 저장
// 즉, 'abc'와 'abcdef'는 완전 별개의 데이터
var b = 5 // 변수 b에 숫자 5 할당. 데이터 영역에서 5 찾고, 없으면 데이터 공간 만들어 저장
var c = 5 // b에 할당한 숫자 5의 주솟값 재활용
b = 7 // 기존 5값을 변경하지 않고, 데이터 영역에서 7을 찾거나 생성해서 주솟값 변경- 이처럼 문자열 값은 한번 만들면 변경할 수 없다.
- 변경은 오로지 새로 만드는 동작을 통해서만 이뤄진다.
- 한번 만들어진 값은 가비지 컬렉팅을 하지 않은 한 영원히 변하지 않는다.
가변값
- 참조형 데이터의 기본적인 성질은 가변값이 경우가 많다.
- 설정에 따라 변경 불가하게 활용할 수도 있음
참조형 데이터 할당:
var obj1 = {
a: 1,
b: 'bbb',
}변수 영역
| 주소 | .. | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: obj1, 값: @5001 |
데이터 영역
| 주소 | .. | 5001 | 5002 | 5003 | 5004 | … |
|---|---|---|---|---|---|---|
| 데이터 | @7103 ~ ? | 1 | ‘bbb’ |
객체 @5001의 변수 영역
| 주소 | .. | 7103 | 7104 | 7105 | 7106 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: a, 값: @5003 | 이름: b, 값: @5004 |
1) 컴퓨터는 변수 영역의 빈 공간(@1002)를 확보하고, 그 주소 이름을 obj1으로 지정
2) 임의의 데이터 저장공간 (@5001)에 저장하려고 보니 여러개의 프로퍼티로 이뤄진 데이터 그룹임.
이 그룹 내부의 프로퍼티를 저장하기 위해 별도의 변수 영역 마련 후, 그 영역의 주소(@7103 ~?)를 @5001에 저장
3) @7103 및 @7104에 각각 a와b라는 프로퍼티 이름 지정
4) 데이터 영역에서 숫자 1 검색.
검색 결과 없으므로 임의로 @5003에 저장하고 이 주소를 @7103에 저장.
'bbb'역시 마찬가지로 작업- 기본형 데이터와의 차이는 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 점이다.
- 객체가 별도로 할애한 영역은 ‘변수 영역’일 뿐 ‘데이터 영역’은 기존의 메모리 공간 그대로 활용. 즉, 데이터 영역에 저장된 값은 불변값
- 하디만 변수에는 언제든지 다른 값 대입할 수 있다. 이 점이 가변성.
- 따라서 참조형 데이터는 불변(immutable)하지 않다고 한다.
가비지 컬렉터:
- 어떤 데이터에 대해 자신의 주소를 참조하는 변수의 갯수를 ‘참조 카운트’ 라고 한다. (ex. 주소 @5003을 참조하는 주소는 @7103 하나 이므로 참조 카운트는 1)
- 이때, @7103의 데이터 값을 @5004로 변경하면 (예: obj1 = {a: “bbb”, b: “bbb”}) 해당 데이터(@5003)의 참조 카운트는 0이 된다.
- 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 된다.
- 가비지 컬렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량 포화 상태 시 자동으로 수거한다.
변수 복사 비교
var a = 10
var b = a
var obj1 = { c: 10, d: 'ddd' }
var obj2 = obj1변수 영역
| 주소 | .. | 1001 | 1002 | 1003 | 1004 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: obj1, 값: @5001 | 이름:b, 값: @5001 | 이름: obj1, 값: @5002 | 이름: obj2, 값: @5002 |
데이터 영역
| 주소 | .. | 5001 | 5002 | 5003 | 5004 | … |
|---|---|---|---|---|---|---|
| 데이터 | 10 | @7103 ~ ? | ‘ddd’ |
객체 @5002의 변수 영역
| 주소 | .. | 7103 | 7104 | 7105 | 7106 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: c, 값: @5001 | 이름: d, 값: @5003 |
- 변수를 단순히 선언, 할당, 복사하는 과정은 기본형 데이터와 참조형 데이터 모두 같은 주소를 바라보는 점에서 동일하다.
- 복사 과정은 동일하지만, 데이터 할당 과정과 복사 이후의 동작에서 차이가 발생함.
(1) 변수 복사 이후 값 변경 결과 비교 - 객체의 프로퍼티 변경 시
var a = 10
var b = a
var obj1 = { c: 10, d: 'ddd' }
var obj2 = obj1
b = 15
objc2.c = 20위 처럼 복사된 객체의 프로퍼티를 변경하면 아래와 같은 흥미로운 결과가 나온다:
a !== b
obj1 === obj2변수 a와 b는 서로 다른 주소를 바라보게 되었으나, 변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있다.
왜 그럴까?
- 기본형 데이터를 복사한 변수 b의 값을 바꾸면, b 변수의 데이터 값(주소)만 달라진다.
- 반면, 참조형 데이터 복사 변수 obj2의 프로퍼티 값을 바꾸면, obj1, obj2의 값(주소)는 동일하면서, 해당 값(주소)에 있던 데이터가 변경된다.
- 즉, 변수 a와 b는 서로 다른 주소를 바라보게 되었으나, 변수 obj1과 obj2는 같은 객체를 바라본다.
메모리를 자세히 들여다 보면, 아래와 같이 변경이 되어있다.
변수 영역
| 주소 | .. | 1001 | 1002 | 1003 | 1004 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: obj1, 값: @5001 | 이름:b, 값: @5004 | 이름: obj1, 값: @5002 | 이름: obj2, 값: @5002 |
데이터 영역
| 주소 | .. | 5001 | 5002 | 5003 | 5004 | 5005 | … |
|---|---|---|---|---|---|---|---|
| 데이터 | 10 | @7103 ~ ? | ‘ddd’ | 15 | 20 |
객체 @5002의 변수 영역
| 주소 | .. | 7103 | 7104 | 7105 | 7106 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: c, 값: @5005 | 이름: d, 값: @5003 |
-
“기본형은 값을 복사하고 참조형은 주솟값을 복사한다” 그럼 이 말은 어떤 뜻일까?
- 엄밀히 따지면 js의 모든 데이터 타입은 참조형이다
- 다만, 기본형은 주솟값을 한번만 복사하고, 참조형은 한단계 더 거치므로 차이가 생긴다
-
단, 아래 예시처럼 참조형 데이터의 속성이 아닌 참조형 데이터 객체 자체를 변경하면 값이 달라진다.
var obj1 = { c: 10, d: 'ddd' }
var obj2 = obj1
obj2 = { c: 20, d: 'ddd' }- 즉, 참조형 데이터가 ‘가변값’일 때는 참조형 데이터 자체를 변경할 경우가 아니라, 내부의 프로퍼티를 변경할 때만 성립한다.
5. 불변 객체
불변 객체를 만드는 간단한 방법
- 불변 객체 (immutable object)는 최근 React, Vue.js, Angular 등의 라이브러리 프레임워크 뿐만 아니라, 함수형 프로그래밍, 디자인 패턴 등에서도 매우 중요함
- 객체도 데이터 차제를 변경하고자 하면 (새로운 데이터 할당) 기본형과 마찬가지로 기존 데이터는 변하지 않는다 (불변성)
- 내부 속성 변경 시 가변성
- 따라서, 내부 속성 변경시마다 매번 새로운 객체를 만들어 재할당을 하거나, 자동으로 새로운 객체를 만드는 도구를 활용하면 객체도 불변성을 확보할 수 있음
- ex) immutable.js, immmer js, immutability-helper 등의 라이브러리. ES6의 spread operator, Object.assign 메서드
왜 불변 객체가 필요할까?
- 값으로 전달받은 객체에 변경을 주더라도, 원본 객체는 변하지 않아야 하는 경우가 있음.
var user = {
name: 'Kim',
gender: 'female',
}
var changeName = function (user, newName) {
var newUser = user
newUser.name = newName
return newUser
}
var user2 = changeName(user, 'Jung')
console.log(user.name, user2.name) // Jung Jung위 예시처럼 user2 호출 시, 내부 프로퍼티만 변경함으로써 기존 user 객체를 동시에 변경하였다. (가변성)
user와 user2가 다른 값을 가질 수 있도록 하려면 아래와 같이 새로운 객체를 반환하도록 수정하면 된다.
var user = {
name: 'Kim',
gender: 'female',
}
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
}
}
var user2 = changeName(user, 'Jung')
console.log(user.name, user2.name) // Kim Jung하지만, 변경된 코드의 경우 변경할 필요가 없는 기존 객체의 ‘gender’속성도 하드코딩으로 입력했다. 객체에 정보가 많을수록 비효율적이게 되는데, 프로퍼티 갯수에 상관없이 모든 프로퍼티를 복사하는 함수를 만들 수 있다.
// 얕은 복사
var copyObject = function (target) {
var result = {}
for (var prop in target) {
result[prop] = target[prop]
}
}- 물론 협업하는 모든 개발자들이 copyObject라는 얕은 복사를 사용하면 문제가 되지 않는다. (=user 객체가 곧 불변 객체)
- 하지만, 그 규칙을 지키지 않을 수도 있으므로 아예 프로퍼티를 변경할 수 없게 제약을 거는게 더 안전하다.
얕은 복사와 깊은 복사
-
얕은 복사(shallow copy): 바로 아래 단계의 값만 복사.
- ex) 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사
- 즉, 원본과 사본이 동일한 참조형 데이터 주소를 가리키므로 사본을 바꾸면 원본도 변경됨
-
깊은 복사(deep copy): 내부의 모든 값들을 하나하나 찾아서 전부 복사.
- ex) 중첩된 객체에서 프로퍼티 내부의 값들까지 전부 복사. 재귀로 구현 가능
// 깊은 복사
var copyObjectDeep = function (target) {
var result = {}
if (typeof target === 'object' && target !== null) {
for (var prop in target) {
result[prop] = copyObjectDeep(target[prop])
}
} else {
result = target
}
return result
}- target이 객체인 경우에는 내부 프로퍼티를 순회하며 copyObjectDeep 함수를 재귀적으로 호출하고, 객체가 아닌경우에는 target을 그대로 지정한다.
- 이 함수는 원본과 사본이 서로 완전히 다른 객체를 참조하게 하여 어느쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않는다.
6. undefined와 null
undefined와null은 자바스크립트에서 모두 ‘없음’을 나타낸다. 하지만 미세하게 차이점이 있다.
1. undefined
- 자바스크립트 엔딘은 사용자가 어떤 값을 지정할 것이라고 예상되는 상황임에도 실제로 그렇게 하지 않았을 때 undefined를 반환한다.
-
- 값을 대입하지 않은 변수. 즉, 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
-
- 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
-
- return 문이 없거나 호출되지 않는 함수의 실행 결과
-
2. null
- null은 ‘비어있음’을 명시적으로 나타내고 싶을 때 사용한다.
- typeof null은 object라고 반환되는데, 이는 자바스크립트 자체 버그이다. 따라서 변수의 값이 null인지 확인하려면 일치 연산자로 확인을 해줘야 한다.
undefined와 null을 구분하기 위해서는 동등 연산자 대신, 일치 연산자(===)를 사용해야 한다.
var n = null
console.log(typeof n) // object
console.log(n == undefined) // true
console.log(n == null) // true
console.log(n === undefined) // false
console.log(n === null) // true참고
- 정재남, 『코어 자바스크립트』, 위키북스(2019), p1-35.