Skip to Content
BlogJavaScript 정리기초 정리심화개념 Proxy, Reflect, 디바운스/쓰로틀

심화개념 Proxy, Reflect, 디바운스/쓰로틀

Proxy (get/set 트랩, 객체 동작 가로채기)

Proxy는 객체에 대한 기본 동작(속성 읽기, 쓰기, 삭제 등)을 가로채서 커스텀 로직을 끼워 넣을 수 있게 해주는 객체다. “이 객체에 접근하거나 값을 바꾸려고 할 때, 그 전에 내가 먼저 확인하고 처리하겠다”는 개념이다.

const user = { name: "gori", age: 20 }; const proxyUser = new Proxy(user, { get(target, key) { console.log(`${key} 읽음`); return target[key]; }, set(target, key, value) { if (key === "age" && value < 0) { console.log("나이는 음수가 될 수 없습니다"); return false; } target[key] = value; return true; } }); console.log(proxyUser.name); // "name 읽음" 출력 후 "gori" 반환 proxyUser.age = -5; // "나이는 음수가 될 수 없습니다" (변경 안 됨)

get, set처럼 동작을 가로채는 함수를 **트랩(trap)**이라고 부른다. 객체 값을 읽거나 쓸 때마다 유효성 검사, 로깅 같은 로직을 자동으로 끼워 넣을 수 있다.



Reflect

Reflect는 객체를 다루는 기본 동작(읽기, 쓰기, 삭제 등)을 메서드로 정리해둔 내장 객체다. Proxy의 트랩 이름과 1:1로 대응해서, 둘은 항상 함께 쓰인다.

const handler = { set(target, key, value) { return Reflect.set(target, key, value); // target[key] = value와 같은 동작 } };
const user = { name: "gori" }; Reflect.get(user, "name"); // "gori" Reflect.set(user, "age", 20); // user.age = 20 Reflect.has(user, "name"); // true ("name" in user와 동일)

Proxy가 동작을 가로채는 역할이라면, Reflect는 그 동작을 표준화된 방식으로 실행해주는 역할이다.



얕은 복사 vs 깊은 복사

얕은 복사는 객체의 최상위 속성만 새로 복사하고, 중첩된 객체는 원본과 같은 참조를 공유한다.

const original = { name: "gori", address: { city: "서울" } }; const copy = { ...original }; copy.name = "고리"; console.log(original.name); // "gori" (영향 없음) copy.address.city = "부산"; console.log(original.address.city); // "부산" (영향 받음)

깊은 복사는 중첩된 객체까지 모두 새로 복사해서, 원본과 완전히 분리된다.

const copy2 = structuredClone(original); copy2.address.city = "부산"; console.log(original.address.city); // "서울" (영향 없음)
구분복사 범위방법
얕은 복사최상위 속성만spread(...), Object.assign
깊은 복사중첩 객체까지 전부structuredClone


메모리 관리 & 가비지 컬렉션

JS는 메모리를 자동으로 관리한다. 더 이상 어디서도 참조되지 않는 값을 엔진이 찾아서 자동으로 회수하는데, 이 과정을 **가비지 컬렉션(GC)**이라고 한다.

let user = { name: "gori" }; user = null; // 이 객체를 참조하는 곳이 없어짐 → GC 대상
function createUser() { const user = { name: "gori" }; return user; } let gori = createUser(); // gori가 계속 참조하고 있어서 GC 대상 아님

이벤트 리스너나 타이머를 정리하지 않고 계속 쌓아두면, 그 안에서 참조하는 데이터가 회수되지 않고 메모리에 남는다. 이를 메모리 누수라고 한다. 다 쓴 리스너는 removeEventListener로, 다 쓴 타이머는 clearInterval로 정리하는 것이 좋다.



디바운스 & 쓰로틀

짧은 시간에 반복적으로 발생하는 이벤트(스크롤, 타이핑 등)의 실행 빈도를 제어하는 기법이다.

디바운스 — 이벤트가 멈추고 일정 시간이 지난 뒤 마지막 한 번만 실행한다.

function debounce(callback, delay) { let timerId; return function (...args) { clearTimeout(timerId); timerId = setTimeout(() => callback(...args), delay); }; } const search = debounce((keyword) => { console.log(`"${keyword}" 검색`); }, 500);

쓰로틀 — 이벤트가 아무리 자주 발생해도 일정 간격마다 최대 한 번만 실행한다.

function throttle(callback, delay) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; callback(...args); } }; } const handleScroll = throttle(() => { console.log("스크롤 위치:", window.scrollY); }, 300);
구분동작 방식예시
디바운스멈추고 일정 시간 후 1번만 실행검색어 자동완성
쓰로틀일정 간격마다 1번씩 실행스크롤 이벤트 처리


마지막 수정