심볼이 뭐지...?
-> 심볼은 값으로 익명의 객체 속성(object property)을 만들 수 있는 특성을 가진 원시 데이터 형식이다. (유일무이한 식별자를 만드는 데 사용)const one = Symbol("hi"); const two = Symbol("hi"); console.log(one === two); // false
ex) API 상태 관련 변수 값
export const apiStatus = {
IDLE: Symbol('IDLE'),
PENDING: Symbol('PENDING'),
SUCCESS: Symbol('SUCCESS'),
ERROR: Symbol('ERROR')
}
변할 수 있는 수
로 변할 수 있는 데이터
변수명
let a;
a = "abc";
a = "abcd";
라고 a를 설정했을 때 메모리 영역은 어떻게 변할까?
주소(변수) | 1002 | 1003 | 1004 | 1005 |
데이터(변수) | 이름 : a 값 : @5004 -> @5005 |
|||
주소(데이터) | 5002 | 5003 | 5004 | 5005 |
데이터(데이터) | "abc" | "abcd" |
변수 영역에서 빈 공간(@1003)을 확보
확보한 공간의 식별자를 a로 지정
데이터 영역의 빈 공간(@5004)에 문자열 "abc"를 저장
변수 영역에서 a라는 식별자 검색(@1003)
앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입
데이터 영역의 빈 공간(@5005)에 문자열 "abcd"를 저장
변수 영역에서 a라는 식별자 검색(@1003)
앞서 저장한 문자열의 주소(@5005)를 @1003의 공간에 대입
이렇게 하는 이유는?
-> 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위해!
-> 중복된 데이터에 대한 처리 효율 상승
1-1에서 말한 기본형 데이터는 모두 불변 값이다. 이유는 1-3에서 설명했듯이 값을 바꾸어도 데이터 영역에서 새로 생성하기 때문이다.
그럼 참조형 데이터는 어떨까? 결론부터 말하자면 가변 값이다.(따로 설정하지 않으면)
let obj1 = {
a = 1,
b = "bbb"
};
obj1.a = 2;
주소(변수) | 1002 | 1003 | 1004 | 1005 | 1006 |
데이터(변수) | 이름 : obj1 값 : @5002 |
||||
주소(데이터) | 5002 | 5003 | 5004 | 5005 | 5006 |
데이터(데이터) | @7103 ~ ? | 1 | "bbb" | 2 |
주소(객체 @5001의 변수) | 7103 | 7104 | 7105 | 7106 | 7107 |
데이터(객체 @5001의 변수) | 이름 : a 값 : @5004 -> @5006 |
이름 : b 값 : @5005 |
변수 obj1이 바라보고 있는 주소 @5002는 변하지 않았다.
즉, 새로운 객체가 만들어진 것이 아니라 기존의 객체 내부의 값만 바뀐 것이다.
let obj = {
x = 3,
arr = [3, 4, 5]
};
변수 영역에서 빈 공간(@1002)을 확보하고 그 주소의 이름을 obj로 지정
데이터가 객체이므로 별도의 변수 영역(@7103 ~ ?)을 마련하고 그 영역의 주소를 @5001에 저장
@7103에 이름 x를 @7104에 이름 arr을 지정
데이터 영역에서 숫자 3 검색 -> 없으므로 @5002에 저장하고 이 주소를 @7103에 저장
@7104에 저장할 값은 배열이므로 별도의 변수 영역(@8104 ~ ?)을 마련하고 그 영역의 주소(@8104 ~ ?)를 @5003에 저장하고 @5003을 @7104에 저장
배열의 요소가 3개이므로 3개의 변수 공간을 확보하고 각각 인덱스를 부여(0, 1, 2)
데이터 영역에서 숫자 3을 검색(@5002) 있으므로 그 주소를 @8104에 저장
숫자 4는 없으므로 @5004에 저장 -> @8105에 저장
숫자 5도 없으므로 @5005에 저장 -> @8106에 저장
주소(변수) | 1001 | 1002 | 1003 | 1004 | 1005 |
데이터(변수) | 이름 : obj 값 : @5001 |
||||
주소(데이터) | 5001 | 5002 | 5003 | 5004 | 5005 |
데이터(데이터) | @7103 ~ ? | 3 | @8104 ~ ? | 4 | 5 |
주소(객체 @5001의 변수) | 7103 | 7104 | 7105 |
데이터(객체 @5001의 변수) | 이름 : x 값 : @5002 |
이름 : arr 값 : @5003 |
주소(배열 @5003의 변수) | 8104 | 8105 | 8106 |
데이터(배열 @5003의 변수) | 이름 : 0 값 : @5002 |
이름 : 1 값 : @5004 |
이름 : 2 값 : @5005 |
그렇다면 obj.arr[1]을 검색하면 그 과정은 어떻게 될까?
obj라는 식별자를 가진 주소를 찾음(@1002)
값이 주소이므로 그 주소로 이동(@5001)
값이 주소이므로 그 주소로 이동(@7103 ~ ?)
arr이라는 식별자를 가진 주소를 찾음(@7104)
값이 주소이므로 그 주소로 이동(@5003)
값이 주소이므로 그 주소로 이동(@8104 ~ ?)
인덱스 1에 해당하는 주소로 이동(@8105)
값이 주소이므로 그 주소로 이동(@5004)
값이 숫자형 데이터이므로 4를 반환
만약 여기서 obj.arr = "str"; 처럼 재할당을 내리면 어떻게 될까?
@5006에 문자열 "str"을 저장하고 그 주소를 @7104에 저장한다.
그렇게 된다면 @5003과 @8104 ~ ?은 참조 카운트가 0이 되고 가비지 컬렉션(GC)의 수거 대상이 된다.
수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 된다.
가비지 컬렉션(GC)은 뭐지...?
-> 메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능
mark-and-sweep라고 불리는 알고리즘을 활용한다.
let a = 10;
let b = a;
let obj1 = { c: 10, d: "ddd" };
let obj2 = obj1;
b = 15;
obj2.c = 20;
console.log(a); // 10
console.log(b); // 15
console.log(obj1); // { c: 20, d: 'ddd' }
console.log(obj2); // { c: 20, d: 'ddd' }
console.log(a === b); // false
console.log(obj1 === obj2); // true
변수 a와 b는 서로 다른 주소를 바라보게 됐으나 변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있기 때문이다.
기본형은 값을 복사하고 참조형은 주솟값을 복사한다고 알고 있지만 이는 틀린 내용이다.
어떤 데이터 타입이든 변수에 할당하기 위해서는 주솟값을 복사해야 하기 때문이다.
다만 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고, 참조형은 한 단계를 더 거치게 된다는 차이가 있다.
obj2.c = 20; 처럼 내부 프로퍼티를 변경할 때가 아닌 obj2 = {c : 20, d : "ddd"}를 변경하면 기본형 데이터처럼 값이 달라진다.
즉, 참조형 데이터가가변 값
이라고 설명할 때의가변
은 내부의 프로퍼티를 변경할 때만 성립된다.
앞에서 말한 거처럼 내부 프로퍼티를 변경하면 원본 객체도 변하게 된다. 이런 현상을 방지하기 위해 불변 객체가 필요하다.
-> 라이브러리나 object.assign 등의 메서드가 사용될 수 있다.
-> 또한, 다양한 얕은 복사, 깊은 복사, 함수를 통해 표현할 수 있다.
없음
을 나타내는 값은 undefined와 null이 있다. undefined는 어떤 변수에 값이 존재하지 않을 경우를 의미하고 null은 사용자가 명시적으로 '없음'을 표현하기 위해 대입한 값
-> 없음을 표현하고 싶을 때는 undefined가 아닌 null을 사용하자
let a;
let obj = { c: 10 };
let func = function () {};
let c = func();
console.log(a); // undefined
console.log(obj.c); // 10
console.log(obj.d); // undefined
console.log(objj); // ReferenceError: objj is not defined
console.log(c); // undefined