자바스크립트 중급 #5 옵셔널 체이닝과 nullish 병합
#4 디스트럭처링/spread 깊이 에 이어, 이번 글은 ES2020에 추가된 짧지만 강력한 두 연산자 — **옵셔널 체이닝(?.)**과 **nullish 병합(??)**을 정리합니다.
?. — 옵셔널 체이닝
#
깊은 객체에 안전하게 접근할 수 있는 연산자입니다.
// 옛날
const street = user && user.address && user.address.street;
// 모던
const street = user?.address?.street;같은 일을 절반 길이로. user가 null/undefined 면 거기서 멈추고 undefined를 반환합니다. 그 뒤로 진행하지 않습니다.
동작 규칙 #
?.의 의미는 정확히 한 줄로:
왼쪽 값이
null또는undefined이면 거기서 멈추고undefined반환. 그 외에는 평범하게 평가.
const obj = null;
obj?.foo; // undefined (null이라 멈춤)
obj?.foo.bar; // undefined (멈춰서 .bar 평가 안 됨)
const obj2 = { foo: { bar: 42 } };
obj2?.foo?.bar; // 42 (모두 의미 있는 값)
obj2?.unknown?.x; // undefined
중간에 한 번이라도 null/undefined가 있으면 거기서 stop, 결과는 undefined.
함수 호출 — ?.()
#
존재할지 모르는 메서드/콜백을 안전하게 호출.
const callback = options.onSuccess;
callback?.();
obj.greet?.();callback이 정의되어 있으면 호출하고, 없으면 아무 일도 않습니다. 콜백 옵션을 받는 함수에서 자주 사용하는 패턴입니다.
배열/대괄호 — ?.[]
#
const items = config?.items;
const first = config?.items?.[0];
const dynamic = obj?.[someKey];?.에 대괄호를 직접 붙이지는 못하고 — ?.[] 또는 ?.[key]로 적습니다.
?.의 함정 — 모든 곳에 쓰지 말 것
#
옵셔널 체이닝이 편하다고 모든 곳에 사용하면 버그가 숨어 들어갑니다.
function processUser(user) {
return user?.id?.toUpperCase(); // ✗ 너무 방어적
}user가 정말로 옵셔널이면 OK. 하지만 함수 인자로 항상 user가 들어와야 하는 게 맞다면, ?.가 버그를 조용히 삼킵니다. user가 null로 들어왔을 때 그게 진짜 문제일 수 있습니다.
좋은 가이드: 정말 없을 수 있는 경우에만 ?.를 씁니다. “혹시 모르니까” 식으로 모든 곳에 적용하지 않습니다.
?? — Nullish 병합
#
null 또는 undefined 일 때 기본값을 주는 연산자.
const name = user.name ?? '익명';
const age = user.age ?? 0;왼쪽 값이 null/undefined 면 오른쪽, 아니면 왼쪽 그대로.
?? vs || — 차이는 falsy와 nullish
#
옛날에는 ||로 같은 일을 했습니다. 차이가 미묘하지만 중요합니다.
const a = 0 || 10; // 10 (0은 falsy)
const b = 0 ?? 10; // 0 (0은 nullish가 아님)
const c = '' || 'default'; // 'default'
const d = '' ?? 'default'; // ''
const e = false || true; // true
const f = false ?? true; // false
||는 falsy 7가지(false/0/-0/0n/’’/null/undefined/NaN)에 대해 모두 발동합니다. ??는 정확히 null/undefined 두 가지에만 발동합니다.
이게 왜 중요하냐면 — 0, '', false 같은 의미 있는 값이 falsy 라는 게 자주 사고를 만들었습니다.
function withTimeout(timeout) {
const ms = timeout || 5000; // ✗ timeout=0 을 막아 버림
// ...
}
withTimeout(0); // ms = 5000, 0이 무시됨
function withTimeout(timeout) {
const ms = timeout ?? 5000;
// ...
}
withTimeout(0); // ms = 0
**기본값 패턴은 거의 항상 ??**가 의도와 맞습니다. ||는 “비어있거나 0이면 기본값” 이 정말 의도일 때만 써요.
둘이 만나면 — ?. + ??
#
자주 묶입니다.
const username = response?.user?.name ?? '익명';
// | |
// | 비어 있을 때 기본값
// 깊은 안전 접근
const port = config?.server?.port ?? 3000;깊은 위치를 안전하게 접근하면서 비어 있으면 기본값 — 한 줄로 해결됩니다. 모던 자바스크립트의 가장 흔한 관용구 중 하나입니다.
??=와 친구들 — 논리 할당 연산자
#
ES2021에 추가된 짧은 형태.
let user = { name: '커티스' };
user.email ??= 'default@example.com';
// user.email 이 null/undefined면 할당, 아니면 그대로
세 가지 변형이 있습니다.
a ||= b; // a가 falsy 면 a = b
a ??= b; // a가 nullish 면 a = b
a &&= b; // a가 truthy 면 a = b
||=와 ??=의 차이는 위에서 본 것과 같습니다 — falsy vs nullish.
함정 — 연산자 우선순위 #
??와 ||/&&를 섞을 때는 괄호가 필수입니다. 자바스크립트가 컴파일 단계에서 막아 줍니다.
const a = null ?? true && false; // ✗ SyntaxError
const b = (null ?? true) && false; // OK — false
const c = null ?? (true && false); // OK — false
언어 차원에서 의도가 모호한 표현을 막은 것입니다. 실수를 줄여줍니다.
옵셔널 체이닝과 디스트럭처링 #
const { user: { name } = {} } = response ?? {};복잡해 보이지만 두 단계만 풀면 됩니다.
response ?? {}— response가 null/undefined면 빈 객체로 시작user: { name } = {}— user가 없으면 빈 객체에서 시작 (그러면 name = undefined)
이렇게 적는 게 너무 빡빡하면 차라리 풀어 쓰는 게 좋습니다.
const user = response?.user;
const name = user?.name ?? '익명';옵셔널 체이닝과 단순 검사 — 어느 게 좋을까 #
// 옵셔널 체이닝
if (user?.address?.street) {
// ...
}
// 명시적 검사
if (user && user.address) {
// ...
}대부분의 경우 ?.가 짧고 직관적입니다. 다만 검사 자체가 의도라면 명시적 검사가 더 의도를 드러내요. 거의 컨벤션 차이라 팀 스타일 따르세요.
마무리 #
이번 글에서 정리한 내용:
?.— 왼쪽이 null/undefined면 멈추고 undefined 반환?.(),?.[]형태로 함수 호출 / 배열 접근에도 사용- 모든 곳에
?.를 사용하지 말 것 — 진짜 옵셔널인 경우에만 ??— null/undefined 일 때만 발동??vs||— 0, ‘’, false 같은 의미 있는 값을 살리느냐 차이- 기본값 패턴은 거의 항상
??가 의도와 맞음 ??=,||=,&&=— 논리 할당 연산자??를||/&&와 섞을 때는 괄호 필수
다음 글(#6 fetch API와 에러 처리)에서는 브라우저/Node 양쪽에서 동작하는 표준 네트워크 도구 — fetch의 사용법과 에러 처리, AbortController까지 다룹니다.