Symbol
변경이 불가한 데이터로, 유일한 식별자를 만들어 데이터를 보호하는 용도로 사용할 수 있습니다.
const sKey = Symbol("Hello");
const user = {
key: "일반 정보",
[sKey]: "민감한 정보", // 대괄호 표기법
};
console.log(user.key); // "일반 정보"
console.log(user["key"]); // "일반 정보"
console.log(user[sKey]); // "민감한 정보"
// 심볼은 유일한 식별자를 만들어 낸다.
// 따라서 sKey !== Symbol("Hello") 이다.
console.log(user[Symbol("Hello")]); // undefined
Symbol을 사용하는 예제
const birthKey = Symbol("birth");
const emailsKey = Symbol("Emails");
const heropy = {
firstName: "Heropy",
lastName: "Park",
age: 38,
[birthKey]: new Date(1985, 11, 16, 17, 30),
[emailsKey]: ["ehddud1006@naver.com"],
};
for (const key in heropy) {
console.log(heropy[key]);
}
// 심볼로 생성한 키는 순회로 접근할 수 없다.
/*
Heropy
Park
38
*/
// 다음처럼 심볼을 사용한 식별자로 직접 접근해줘야한다.
console.log(heropy[birthKey]);
console.log(heropy[emailsKey]);
그런데 심볼을 사용한 것이 어떤 장점이 있는지 모르겠다. 객체 순회도 안되고 써야 하는 이유가 없다.
나는 그냥 타입스크립트의 리터럴 문자열 사용할 것 같다.
BigInt
숫자 데이터가 안정적으로 표시할 수 있는 최대치(2^53 - 1)보다 큰 정수 표현 가능하게 하는 데이터 타입이다.
// 숫자 데이터가 안정적으로 표시할 수 있는 최대치(2^53 - 1)보다 큰 정수 표현 가능
console.log(1234567890123456789012345678901234567890); // 1.2345678901234568e+39
// 아래 두 코드는 같은 표현이다. BigInt("123") === 123n
console.log(1234567890123456789012345678901234567890n); // "1234567890123456789012345678901234567890n"
console.log(BigInt("1234567890123456789012345678901234567890")); // "1234567890123456789012345678901234567890n"
console.log(typeof 1234567890123456789012345678901234567890n); // bigint
// bigint와 숫자는 형 변환을 해줘야 연산할 수 있다.
// number + number 또는 bigInt + bigInt 로 통일해주어야한다.
const a = 11n;
const b = 22;
console.log(a + BigInt(b)); // "33n"
console.log(Number(a) + b); // 33
불변성 & 가변성
- 불변성: 생성된 데이터가 메모리에서 변경되지 않는다.
- 가변성: 생성된 데이터가 메모리에서 변경될 수 있다.
- 자바스크립트 원시 타입은 불변성을, 참조 타입(배열, 객체, 함수)은 가변성을 가진다.
원시 타입의 값에 해당되는 데이터는 콜 스택에, 참조 타입에 해당되는 데이터는 힙에 저장된다.
다음 코드를 자바스크립트 엔진의 콜 스택과 힙의 구조를 통해 이해 보자.
let a = 1
let b = a
식별자 a와 식별자 b 가 콜스택의 1의 값이 저장된 메모리 주소를 가리킨다.
만약 b에 새로운 값을 할당하면 어떻게 될까?
b = 2
콜스택에 2라는 값이 새롭게 저장되고, b가 2가 저장된 메모리 주소를 가리키게 된다.
변수에 객체값을 할당하게 되면 콜스택의 렉시컬 환경에 등록된 변수는 메모리 힙에 저장된 데이터의 주소를 가르키게 된다.
let a = { x: 1 }
let b = a
만약 이렇게 객체의 값을 바꿔주면 어떻게 될까?
이게 제일 처음 입문했을 때 분명히 b 객체의 값을 변경했는데 왜 a의 값도 바뀌는 거지? 하는 부분이다.
a와 b 모두 메모리 힙의 같은 객체를 가리키고 있기 때문이다.
b.x = 2
=== 일치 연산자의 비밀
=== 일치 연산자는 사실 메모리 주소가 같은지 비교하는 연산자이다.
참조 타입은 주의점
참조 타입은 똑같은 데이터가 만들어지더라도 기본적으로 새로운 메모리 주소에 할당한다.
let a = { x : 1 }
let b = { x : 1 }
b.x = 2
console.log(b) // { x : 2 }
console.log(a) // { x : 1 }
따라서 참조형 데이터는 똑같은 데이터를 가지고 있더라도 === 일치 연산자를 사용했을 때 false 값을 얻게 된다.
같은 메모리 주소가 아니기 때문이다.
console.log({} === {}) // false
얕은 복사 & 깊은 복사
- 얕은 복사(Shallow Copy) - 참조형의 1차원 데이터만 복사
- 깊은 복사(Deep Copy) - 참조형의 모든 차원 데이터를 복사
얕은 복사
const a = { x : 1 }
const b = Object.assign({}, a)
const c = { ...a }
하지만 위와 같은 얕은 복사방법은 참조형 안에 참조형이 들어있는 객체에서는 제대로 복사가 되지 않아 의도치 않은 데이터 변경이 발생할 수 있다.
메모리 구조로 살펴보면 다음과 같다.
결과적으로 얕은 복사된 객체 또한 0xFF02 의 객체를 참조하고 있기 때문에, 0xFF02의 데이터가 변경되면 변수 a, b 모두 값이 바뀌게 된다.
위와 같은 문제를 해결하기 위해서는 깊은 복사를 해야 한다.
깊은 복사
깊은 복사를 구현하는 것은 번거롭다. 따라서 lodash와 같은 라이브러리의 도움을 받는 것이 좋다.
가비지 컬렉션 (GC, Garbage Collection)
- 자바스크립트의 메모리 관리 방법으로 자바스크립트 엔진이 자동으로 데이터가 할당된 메모리에서 더 이상 사용되지 않는 데이터를 해제하는 것을 말한다.
- 가비지 컬렉션은 개발자가 직접 강제 실행하거나 관리할 수 없다.
변수에 객체값을 할당하게 되면 콜스택의 렉시컬 환경에 등록된 변수는 메모리 힙에 저장된 데이터의 주소를 가리키게 된다.
let a = { x: 1 }
let b = a
만약 이렇게 객체의 값을 바꿔주면 어떻게 될까?
이게 제일 처음 입문했을 때 분명히 b 객체의 값을 변경했는데 왜 a의 값도 바뀌는 거지? 하는 부분이다.
a와 b 모두 메모리 힙의 같은 객체를 가리키고 있기 때문이다.
b.x = 2
이때 만약 1이라는 데이터 값이 더 이상 참조 되지 않는다면 GC는 해당 데이터를 메모리에서 해제시킨다.
클로저
함수가 선언될 때의 유효범위(렉시컬 범위)를 기억하고 있다가, 함수가 외부에서 호출될 때 그 유효범위의 특정 변수를 참조할 수 있는 개념을 말한다.
클로저의 사용예시
사용될 HTML 코드
아래 코드는 isRed라는 색상이 붉냐 붉지 않냐라는 상태 변수가 있고, 클릭할 때마다 상태값이 변화하는 뭐 그런 코드이다.
클로저를 사용하면 아래와 같은 코드로 바꿀 수 있다.
이것이 더 나은 코드인 이유는 다음과 같다.
- 상태 관리를 위해 변수를 여러 개 선언할 필요가 없다.
- 캡슐화가 되어있어 외부에서 핸들러를 제외한 방법으로는
메모리 누수(Memory Leak)
더 이상 필요하지 않은 데이터가 해제되지 못하고 메모리를 계속 차지하는 현상이다.
- 불필요한 전역 변수 사용
- 분리된 노드 참조
- 해제하지 않은 타이머
- 잘못된 클로저 사용
불필요한 전역 변수 사용
전역객체(window)의 프로퍼티 데이터는 GC가 사용하지 않는 것으로 판단해도 제거하지 않기 때문에 불필요한 프로퍼티를 만드는 것을 지양해야 한다.
분리된 노드 참조
아래 코드에서 이벤트 리스너가 동작하면 parent 노드가 제거된다.
이때 노드는 DOM에서 제거된 것이고, parent라는 변수에는 찾아놓았던 요소를 그대로 참조하고 있다.
이러한 현상을 막기 위해서는 클릭 이벤트가 발생할 때 변수에 요소를 할당하는 것이다.
그런데 이것도 클릭할 때마다 요소를 찾는 비용이 든다. 메모리 누수, 클릭할 때 마다 드는 비용 중에 trade off 정해야 할 것 같다.
그럼 이제 클릭될 때마다 요소를 찾기 때문에 parent remove 가 된 이후 클릭 이벤트가 발생하면 null 값은 remove 메서드를 가지고 있지 않다는 오류가 생긴다.
if 문을 사용해도 되고, 다음처럼 and 연산자를 사용해서 에러 핸들링 해주면 된다.
해제하지 않은 타이머
setInterval 은 제거해주지 않으면 계속해서 반복 실행 된다.
따라서 setInterval을 별도의 함수로 선언하여 clearInterval을 해줘야 한다.
잘못된 클로저 사용
다음 클로저 패턴에서 리턴되는 함수를 살펴보면 a 변수가 계속 참조되고 있다.
그런데 정작 해당 함수에는 a 변수가 사용될 필요가 없다. 이름을 반환하는 함수니까 말이다.
const getName = () => {
let a = 0;
return (name) => {
a += 1;
console.log(a);
return `Hello ${name}~`;
};
};
const fn = getName();
console.log(fn("ehddud"));
console.log(fn("joy"));
위와 같은 클로저로 fn 함수를 호출하면 다음과 같은 출력 값을 얻을 수 있다.
fn 이 호출될 때마다 a 값이 변하니 GC는 a를 메모리에서 제거하지 않는다.
// 1
// ehddud
// 2
// joy
따라서 다음과 같이 클로저를 만들어야 한다. 사용하지 않는 변수는 코드를 작성할 때 지워주자.
const getName = () => {
return (name) => {
return `Hello ${name}~`;
};
};
const fn = getName();
console.log(fn("ehddud"));
console.log(fn("joy"));
콜 스택, 태스크 큐, 이벤트 루프
다음 코드를 자바스크립트 엔진은 어떻게 실행시킬까?
setTimeout 함수가 호출된다. 이때 타이머는 Web APIs에서 동작하고 시간이 종료되면 Web APIs 가 태스크 큐로 옮긴다.
그 후 console.log(2)가 호출된다. [출력: 2]
콜 스택이 비어있는 것을 확인하면 이벤트 루프가 태스크 큐의 콜백 함수를 콜 스택으로 옮긴다.
따라서 console.log(1) 이 출력된다. [출력: 2 , 1]
알쏭달쏭 퀴즈
다음 코드는 어떤 순서를 가질까?
다음 코드는 어떤 순서를 가질까?
'카카오 테크 캠퍼스 > HTML CSS JS Advanced' 카테고리의 다른 글
[카테캠 선택강의] 정규표현식 (0) | 2023.05.29 |
---|---|
[카테캠 선택강의] 기타 Web APIs (1) | 2023.05.26 |
[카테캠 선택강의] 반응형 적용 (0) | 2023.05.17 |
[카테캠 선택강의] 섹션과 푸터 만들기 (2) | 2023.05.16 |
[카테캠 선택강의] IntersectionObserver (2) | 2023.05.15 |