자바스크립트 중급 #5 옵셔널 체이닝과 nullish 병합

#4 디스트럭처링/spread 깊이 에 이어, 이번 글은 ES2020에 추가된 짧지만 강력한 두 연산자 — **옵셔널 체이닝(?.)**과 **nullish 병합(??)**을 정리합니다.

?. — 옵셔널 체이닝 #

깊은 객체에 안전하게 접근할 수 있는 연산자입니다.

옛 방식 vs 옵셔널 체이닝
// 옛날
const street = user && user.address && user.address.street;

// 모던
const street = user?.address?.street;

같은 일을 절반 길이로. usernull/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가 들어와야 하는 게 맞다면, ?.버그를 조용히 삼킵니다. usernull로 들어왔을 때 그게 진짜 문제일 수 있습니다.

좋은 가이드: 정말 없을 수 있는 경우에만 ?.를 씁니다. “혹시 모르니까” 식으로 모든 곳에 적용하지 않습니다.

?? — 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에 추가된 짧은 형태.

??= — null/undefined 일 때만 할당
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 ?? {};

복잡해 보이지만 두 단계만 풀면 됩니다.

  1. response ?? {} — response가 null/undefined면 빈 객체로 시작
  2. user: { name } = {} — user가 없으면 빈 객체에서 시작 (그러면 name = undefined)

이렇게 적는 게 너무 빡빡하면 차라리 풀어 쓰는 게 좋습니다.

풀어쓰기 — 종종 더 명확
const user = response?.user;
const name = user?.name ?? '익명';

옵셔널 체이닝과 단순 검사 — 어느 게 좋을까 #

단순 검사 vs 체이닝
// 옵셔널 체이닝
if (user?.address?.street) {
  // ...
}

// 명시적 검사
if (user && user.address) {
  // ...
}

대부분의 경우 ?.가 짧고 직관적입니다. 다만 검사 자체가 의도라면 명시적 검사가 더 의도를 드러내요. 거의 컨벤션 차이라 팀 스타일 따르세요.

마무리 #

이번 글에서 정리한 내용:

  • ?. — 왼쪽이 null/undefined면 멈추고 undefined 반환
  • ?.(), ?.[] 형태로 함수 호출 / 배열 접근에도 사용
  • 모든 곳에 ?.를 사용하지 말 것 — 진짜 옵셔널인 경우에만
  • ?? — null/undefined 일 때만 발동
  • ?? vs || — 0, ‘’, false 같은 의미 있는 값을 살리느냐 차이
  • 기본값 패턴은 거의 항상 ??가 의도와 맞음
  • ??=, ||=, &&= — 논리 할당 연산자
  • ??||/&&와 섞을 때는 괄호 필수

다음 글(#6 fetch API와 에러 처리)에서는 브라우저/Node 양쪽에서 동작하는 표준 네트워크 도구 — fetch의 사용법과 에러 처리, AbortController까지 다룹니다.

X