function outer() {
let count = 0; // 외부 함수의 지역 변수
return function inner() {
count++;
console.log(`현재 count는 ${count}입니다.`);
};
}
const counter = outer(); // outer는 실행되고, inner가 반환됨
counter(); // 현재 count는 1입니다.
counter(); // 현재 count는 2입니다.
겉으로만 봤을 때는 counter를 실행하면 count가 각각 1, 1로 출력될 것 같지만 실제로는 아니다.
outer() 함수는 실행되고 사라졌지만,그 안에서 만든 inner() 함수는 count를 기억하고 있다.
왜냐하면 count가 클로저에 포함되어서 메모리에 살아있기 때문이다.
클로저의 핵심 구성
구성 요소
설명
외부 함수 (outer)
지역 변수를 선언하는 함수
내부 함수 (inner)
외부 함수의 지역 변수에 접근하는 함수
반환
내부 함수가 외부로 반환되어 사용됨
기억
외부 함수의 지역 변수를 내부 함수가 기억 (→ 클로저)
그렇다면 클로저를 왜 쓰는걸까?
1. 데이터 보호 / 은닉 (캡슐화): 외부에서 직접 접근 못하게 하고, 함수로만 조작하도록 할 수 있음.
[객체와 Map의 차이점 💡] 1) 객체는 주로 문자열을 key로 사용하고, 순서가 보장되지 않음. (비록 ES6 이후 객체는 키의 순서를 어느 정도 보장하지만 Map보다는 덜 신뢰할 수 있음) 2) Map은 임의의 자료형을 key로 사용 가능하고, 항상 순서를 보장하며, 성능이 더 뛰어날 때가 많음.
[개념 알고가기 🔍] 1) 가상 DOM(Virtual DOM)이란? 가상 DOM은 실제 DOM(Document Object Model)의 가벼운 복사본으로, React는 이걸 메모리 상에서 유지하면서 UI 변화를 먼저 여기에 반영한다. 즉, 실제 브라우저의 DOM을 직접 만지지 않고 모의 시뮬레이션을 해보는 느낌이다. (DOM은 HTML을 브라우저가 이해할 수 있게 구조화한 트리 구조의 객체들을 말한다.)
실제 DOM은 느리기 때문에 DOM을 직접 바꾸는 작업은 브라우저에 많은 부담을 준다. 그래서 React는 직접 DOM을 건드리지 않고가상 DOM이라는 중간 단계를 둔 것이다! 2) 가상 DOM이 실제 쓰이는 방식 ① 컴포넌트를 처음 렌더링 할 때 JSX를 기반으로 가상 DOM 트리를 만들고, 이걸 바탕으로 실제 DOM을 생성해서 브라우저에 보여준다.
② 컴포넌트가 업데이트 될 때 변경된 내용을 반영한 새로운 가상 DOM을 만들고, 이전 가상 DOM과 새 가상 DOM을 비교(diffing)하여 바뀐 부분만 찾아서 실제 DOM에 적용한다. (최소한의 조작)
2. 실제 DOM으로 변경 (초기 렌더링)
처음 렌더링할 때는 React가 가상 DOM을 기반으로 실제 DOM을 생성해서 브라우저에 붙여준다.
* 이걸 마운팅(Mounting)이라고 함.
3. 상태(state)나 props 변경 → 리렌더링 발생
const [count, setCount] = useState(0);
위의 코드를 보면 버튼 클릭 시 setCount(1) 호출되고, React는 다시 컴포넌트를 재실행한다.
그리고 새 가상 DOM을 만들어서 이전 가상 DOM과 비교를 시작한다.
4. Diffing (차이 비교)
React는 이전 가상 DOM과 새로운 가상 DOM을 비교하여 바뀐 부분만 추려낸다.(예: <span>0</span> → <span>1</span>)
이 과정을 Diffing Algorithm이 처리한다. (효율적으로 트리 비교)
5. Reconciliation (진짜 DOM 업데이트)
Diffing 결과를 바탕으로 실제 브라우저의 DOM에 필요한 부분만 업데이트한다.
전체를 다시 그리지 않고, 바뀐 부분만 갱신하기 때문에 성능이 아주 좋고 부드럽다.
[요약]
컴포넌트 구현 → 가상 DOM 생성 → (초기) 실제 DOM으로 변환 → [상태 변경 발생] → 새 가상 DOM 생성 → 이전과 비교 (Diffing) → 차이점만 실제 DOM에 반영 (Reconciliation) → 화면 업데이트 + 후처리 (useEffect 등)
var, let, const는 크게 변수의 범위(스코프) / 중복 선언 / 호이스팅으로 구분하여 비교할 수 있다.
변수의 범위로 보는 차이점
1. var는 함수 스코프(function scope)를 가지는 변수이다. 즉, 변수를 선언한 함수 내에서만 유효하고, 함수 밖에서는 접근할 수 없다.
함수 바깥에서 선언하면 전역 변수(global variable)로 취급된다.
* 함수 스코프에서만 가지므로 if문이나 for문 등에서는 적용이 되지 않는다.
function sayHi () {
var say = 'Hi';
console.log(say); // Hi 출력
}
console.log(say); // Error 발생
if(true) {
var title = 'JavaScript';
console.log(title); // JavaScript 출력
}
console.log(title); // JavaScript 출력
for(var i = 1; i < 4; i++;) {
console.log(i); // 1부터 3까지 출력
}
console.log(i); // 4 출력
2. let, const은 블록 스코프(block scope)를 가지므로, 변수를 선언한 {} 안에서만 유효하고, 그 밖에서는 접근할 수 없다.
function sayHi () {
let say = 'Hi';
console.log(say); // Hi 출력
}
console.log(say); // Error 발생
if(true) {
const title = 'JavaScript';
console.log(title); // JavaScript 출력
}
console.log(title); // Error 발생
for(let i = 1; i < 4; i++;) {
console.log(i); // 1부터 3까지 출력
}
console.log(i); // Error 발생
중복 선언
1. var는 같은 이름으로 선언된 변수를 다시 선언할 수 있다.
중복 선언 시에는 나중에 선언된 값으로 덮어씌워진다.
var title = 'JavaScript';
console.log(title); // JavaScript 출력
var title = 'HTML';
console.log(title); // HTML 출력
title = 'CSS';
console.log(title); // CSS 출력
2. let는 같은 스코프 내에서 같은 이름의 변수는 다시 선언할 수 없다.
let title = 'JavaScript';
console.log(title); // JavaScript 출력
let title = 'HTML';
console.log(title); // Syntax Error
title = 'CSS';
console.log(title); // CSS 출력
3. const도 같은 스코프 내에서 같은 이름의 변수는 다시 선언할 수 없다.
또한, 선언 후 재할당이 불가능하고, 한번 값이 할당되면 그 값을 변경할 수 없다.
하지만 객체나 배열 같은 참조형 데이터의 경우, 객체의 속성이나 배열의 요소를 변경할 수 있다.
const title = 'JavaScript';
console.log(title); // JavaScript 출력
const title = 'CSS' // Syntax Error
title = 'HTML'; // Type Error
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 가능
호이스팅(Hoisting)
[호이스팅이란? 🔍]
호이스팅은 자바스크립트의 변수 선언과 함수 선언이 코드 실행 전에 끌어올려지는 현상이다.
자바스크립트는 코드를 실행하기 전에 선언을 먼저 처리하고, 그 다음에 실제 코드 실행을 처리한다는 특징이 있다.
하지만 호이스팅이 어떻게 작동하는지에 따라 다르게 동작할 수 있다.
1. var로 선언된 변수는 선언만 호이스팅되고 초기화는 되지 않는다.
즉, 변수가 함수의 제일 위로 끌어올려지지만 값은 할당되지 않은 상태로 올라간다.
console.log(title); // undefined 출력
var title = 'This is title';
2. let, const으로 선언된 변수도 호이스팅이 일어나지만, 변수 선언 전에 접근하면 ReferenceError가 발생한다.
초기화 전까지는 Temporal Dead Zone (TDZ)에 있기 때문에 값을 참조할 수 없다.
console.log(title); // Reference Error
console.log(name); // Reference Error
let title = 'This is title';
const name = 'My name is name';
// 나쁜 예
list.forEach(item => {
const el = document.createElement('li');
el.textContent = item;
document.body.appendChild(el); // 매번 DOM 수정
});
// 좋은 예
const fragment = document.createDocumentFragment();
list.forEach(item => {
const el = document.createElement('li');
el.textContent = item;
fragment.appendChild(el);
});
document.body.appendChild(fragment); // 한 번만 DOM 수정
비동기 처리 활용
문제: 모든 스크립트를 동기로 실행하면 로딩 막힘.
[해결방법]
<script async> 또는 <script defer> 사용
API 요청은 fetch나 axios로 비동기 처리
<!-- 좋은 예: HTML 렌더링 끝나고 스크립트 실행 -->
<script src="main.js" defer></script>
[script 실행 순서에 관한 내용은 아래 글의 '자바스크립트 코드는 언제 실행될까?' 부분 참고!] ⤵️
React Query는 자동으로 데이터 캐싱을 하고, 동일한 queryKey로 요청하면 서버에 다시 요청하지 않고 캐시된 데이터를 반환한다.
최초 로딩 시 서버 요청
같은 컴포넌트가 다시 렌더링되면 캐시된 데이터 사용
설정에 따라 일정 시간이 지나면 자동으로 다시 요청 (stale time)
[개념 알고가기 🔍] 1) staleTime이란? - 신선한 상태 유지 시간 ① 동작 방식 (staleTime: 10000으로 설정된 경우) 쿼리가 처음 실행되면 데이터를 받아오고, 그 시점부터 10초 동안은 stale 상태가 아님. 이 기간 동안은 React Query가 자동으로 재요청(refetch) 하지 않음. 10초가 지나면 데이터가 stale(오래됨)으로 간주되고, 다음 조건 중 하나가 일어나면 다시 데이터를 요청(refetch) 해 : 컴포넌트가 다시 mount되거나, 창에 다시 포커스되거나, 네트워크가 다시 연결될 때 등
② 언제 유용할까? 너무 자주 데이터를 새로 불러오지 않아도 되는 경우 (예: 공지사항, 프로필, 상품 목록 등 자주 바뀌지 않는 데이터)
2) cacheTime이란? - 캐시 유지 시간 ① 동작 방식 (cacheTime: 300000으로 설정된 경우) 컴포넌트가 unmount되어도 데이터를 바로 삭제하지 않음. 이 cacheTime이 끝날 때까지 메모리 안에 캐시로 유지 다시 같은 쿼리가 mount되면, 서버에 요청하지 않고 캐시된 데이터를 바로 사용함.
② 언제 유용할까? - 사용자가 같은 페이지로 자주 왔다 갔다 할 때 - 리스트 → 상세 → 다시 리스트 같은 구조에서 유용 - 불필요한 재요청을 줄여 성능 최적화 가능
staleTime이 길면 = 네트워크 요청 줄어듦, 대신 데이터는 오래됨 가능성 ↑
cacheTime이 길면 = 빠른 화면 전환 가능, 하지만 메모리 사용량 ↑
[간단 요약]
옵션
의미
기본 값
staleTime
데이터를 신선하다고 간주할 시간 (ms)
0 (즉시 stale 처리)
cacheTime
데이터가 사용되지 않아도 캐시에 유지되는 시간 (ms)
5분 (300000)
[추천 예시] 1) 실시간 데이터 (ex. 실시간 주가, 라이브 채팅 등) ➡️ staleTime: 0, refetchInterval 사용해서 실시간 유지 2) 일반 리스트 (ex. 게시글 목록) ➡️ staleTime: 30초~1분, cacheTime: 3~5분 3) 잘 바뀌지 않는 데이터 (ex. 유저 프로필) ➡️ staleTime: Infinity (영원히 fresh), cacheTime: Infinity (계속 보관)
자주 사용하는 옵션들
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 5000, // 5초 동안은 신선한 데이터로 간주
cacheTime: 1000 * 60 * 5, // 5분간 캐시 유지
refetchOnWindowFocus: true, // 창 포커스될 때 refetch
});