옵셔널 체이닝
옵셔널 체이닝 (Optional chaining, ?.)
객체의 속성에 접근하거나 함수를 호출할 때 사용하는 연산자입니다. 이 연산자로 접근하는 객체나 호출하는 함수가 undefined나 null이면, 표현식이 단락(short-circuit)되어 오류를 던지는 대신 undefined로 평가됩니다. Baseline Widely available — 2020년 7월부터 여러 브라우저에서 지원되는 잘 정착된 기능입니다.
본문
옵셔널 체이닝(?.) 연산자는 객체의 속성에 접근하거나 함수를 호출합니다. 이 연산자로 접근한 객체나 호출한 함수가 undefined 또는 null이면, 표현식은 오류를 던지는 대신 undefined로 단락됩니다.
시도해 보기 (Try it)
const adventurer = {
name: "Alice",
cat: {
name: "Dinah",
},
};
const dogName = adventurer.dog?.name;
console.log(dogName);
// Expected output: undefined
console.log(adventurer.someNonExistentMethod?.());
// Expected output: undefined
구문 (Syntax)
obj?.prop
obj?.[expr]
func?.(args)
설명 (Description)
?. 연산자는 체이닝 연산자 .와 같지만, 참조가 nullish(null 또는 undefined)일 때 오류를 일으키는 대신 undefined 반환값으로 단락된다는 점이 다릅니다. 함수 호출에 사용하면 주어진 함수가 존재하지 않을 때 undefined를 반환합니다.
이로 인해 참조가 없을 가능성이 있는 체이닝된 속성에 접근할 때 표현식이 더 짧고 단순해집니다. 어떤 속성이 필수인지 보장이 없는 객체의 내용을 탐색할 때도 유용합니다.
예를 들어 중첩 구조를 가진 객체 obj를 생각해 봅시다. 옵셔널 체이닝 없이 깊이 중첩된 하위 속성을 조회하려면 중간 참조들을 다음과 같이 검증해야 합니다:
const nestedProp = obj.first && obj.first.second;
obj.first.second의 값에 접근하기 전에 obj.first의 값이 null(그리고 undefined)이 아님을 확인합니다. 이는 obj.first를 검사하지 않고 obj.first.second에 직접 접근할 때 발생할 오류를 막아줍니다.
이것은 JavaScript의 관용적인 패턴이지만, 체인이 길어지면 장황해지고 안전하지 않습니다. 예를 들어 obj.first가 null이나 undefined가 아닌 falsy 값(예: 0)이라면 여전히 단락되어 nestedProp이 0이 되어, 이는 바람직하지 않을 수 있습니다.
하지만 옵셔널 체이닝 연산자(?.)를 사용하면 obj.first.second에 접근하기 전에 obj.first의 상태에 따라 명시적으로 검사하고 단락시킬 필요가 없습니다:
const nestedProp = obj.first?.second;
. 대신 ?. 연산자를 사용함으로써 JavaScript는 obj.first.second에 접근을 시도하기 전에 obj.first가 null 또는 undefined가 아닌지 암시적으로 확인합니다. obj.first가 null이나 undefined라면 표현식은 자동으로 단락되어 undefined를 반환합니다.
이는 다음 코드와 동등하지만, 임시 변수가 실제로 생성되지는 않습니다:
const temp = obj.first;
const nestedProp =
temp === null || temp === undefined ? undefined : temp.second;
옵셔널 체이닝은 선언되지 않은 루트 객체에는 사용할 수 없지만, 값이 undefined인 루트 객체에는 사용할 수 있습니다.
undeclaredVar?.prop; // ReferenceError: undeclaredVar is not defined
함수 호출과 옵셔널 체이닝 (Optional chaining with function calls)
존재하지 않을 수도 있는 메서드를 호출하려 할 때 옵셔널 체이닝을 사용할 수 있습니다. 예를 들어 구현이 오래되었거나 사용자 기기에 기능이 없어 메서드를 사용할 수 없는 API를 사용할 때 유용합니다.
함수 호출에 옵셔널 체이닝을 사용하면, 메서드를 찾을 수 없을 때 예외를 던지는 대신 표현식이 자동으로 undefined를 반환합니다:
const result = someInterface.customMethod?.();
그러나 그런 이름의 속성이 있는데 함수가 아니라면, ?.를 사용해도 여전히 TypeError 예외("someInterface.customMethod is not a function")가 발생합니다.
참고:
someInterface자체가null이나undefined라면 여전히TypeError예외("someInterface is null")가 발생합니다.someInterface자체가null이나undefined일 수 있다고 예상되면 이 위치에서도?.를 사용해야 합니다:someInterface?.customMethod?.().
eval?.()는 간접 eval 모드로 진입하는 가장 짧은 방법입니다.
표현식과 옵셔널 체이닝 (Optional chaining with expressions)
대괄호 표기법(bracket notation)과 함께 옵셔널 체이닝 연산자를 사용할 수도 있는데, 이를 통해 표현식을 속성 이름으로 전달할 수 있습니다:
const propName = "x";
const nestedProp = obj?.[propName];
이는 배열에 특히 유용한데, 배열 인덱스는 대괄호로 접근해야 하기 때문입니다.
function printMagicIndex(arr) {
console.log(arr?.[42]);
}
printMagicIndex([0, 1, 2, 3, 4, 5]); // undefined
printMagicIndex(); // undefined; if not using ?., this would throw an error: "Cannot read properties of undefined (reading '42')"
잘못된 옵셔널 체이닝 (Invalid optional chaining)
옵셔널 체이닝 표현식의 결과에 할당하는 것은 유효하지 않습니다:
const object = {};
object?.property = 1; // SyntaxError: Invalid left-hand side in assignment
템플릿 리터럴 태그는 옵셔널 체이닝이 될 수 없습니다:
String?.raw`Hello, world!`;
String.raw?.`Hello, world!`; // SyntaxError: Invalid tagged template on optional chain
new 표현식의 생성자는 옵셔널 체이닝이 될 수 없습니다:
new Intl?.DateTimeFormat(); // SyntaxError: Invalid optional chain from new expression
new Map?.();
단락 (Short-circuiting)
표현식과 함께 옵셔널 체이닝을 사용할 때, 왼쪽 피연산자가 null이나 undefined라면 그 표현식은 평가되지 않습니다. 예를 들어:
const potentiallyNullObj = null;
let x = 0;
const prop = potentiallyNullObj?.[x++];
console.log(x); // 0 as x was not incremented
이후의 속성 접근도 평가되지 않습니다.
const potentiallyNullObj = null;
const prop = potentiallyNullObj?.a.b;
// This does not throw, because evaluation has already stopped at
// the first optional chain
이는 다음 코드와 동등합니다:
const potentiallyNullObj = null;
const prop =
potentiallyNullObj === null || potentiallyNullObj === undefined
? undefined
: potentiallyNullObj.a.b;
다만 이 단락 동작은 속성 접근의 연속된 하나의 "체인" 안에서만 일어납니다. 체인의 일부를 괄호로 묶으면 이후의 속성 접근은 여전히 평가됩니다.
const potentiallyNullObj = null;
const prop = (potentiallyNullObj?.a).b;
// TypeError: Cannot read properties of undefined (reading 'b')
이는 다음 코드와 동등합니다(temp 변수는 실제로 만들어지지 않습니다):
const potentiallyNullObj = null;
const temp = potentiallyNullObj?.a;
const prop = temp.b;
예제 (Examples)
기본 예제
이 예제는 어떤 Map에서 CSS라는 멤버가 없을 때 해당 멤버의 name 속성 값을 찾습니다. 따라서 결과는 undefined입니다.
const myMap = new Map();
myMap.set("JS", { name: "Josh", desc: "I maintain things" });
const nameBar = myMap.get("CSS")?.name;
옵셔널 콜백 또는 이벤트 핸들러 다루기
구조 분해 패턴으로 객체에서 콜백이나 fetch 메서드를 사용한다면, 그 존재를 검사하지 않는 한 함수로 호출할 수 없는 존재하지 않는 값이 있을 수 있습니다. ?.를 사용하면 이 추가 검사를 피할 수 있습니다:
// Code written without optional chaining
function doSomething(onContent, onError) {
try {
// Do something with the data
} catch (err) {
// Testing if onError really exists
if (onError) {
onError(err.message);
}
}
}
// Using optional chaining with function calls
function doSomething(onContent, onError) {
try {
// Do something with the data
} catch (err) {
onError?.(err.message); // No exception if onError is undefined
}
}
옵셔널 체이닝 연산자 중첩하기
중첩 구조에서 옵셔널 체이닝을 여러 번 사용할 수 있습니다:
const customer = {
name: "Carl",
details: {
age: 82,
location: "Paradise Falls", // Detailed address is unknown
},
};
const customerCity = customer.details?.address?.city;
// This also works with optional chaining function call
const customerName = customer.name?.getName?.(); // Method does not exist, customerName is undefined
nullish 병합 연산자와 결합하기
값을 찾지 못했을 때 기본값을 만들기 위해 옵셔널 체이닝 뒤에 nullish 병합 연산자를 사용할 수 있습니다:
function printCustomerCity(customer) {
const customerCity = customer?.city ?? "Unknown city";
console.log(customerCity);
}
printCustomerCity({
name: "Nathan",
city: "Paris",
}); // "Paris"
printCustomerCity({
name: "Carl",
details: { age: 82 },
}); // "Unknown city"
명세 (Specifications)
옵셔널 체이닝은 ECMAScript 언어 명세에 정의되어 있습니다.
브라우저 호환성 (Browser compatibility)
이 기능은 대부분의 현대 브라우저에서 널리 지원됩니다(Baseline Widely available). 자세한 호환성 표는 MDN의 브라우저 호환성 섹션을 참고하세요.
더 알아보기
- Nullish 병합 연산자 (
??) - 옵셔널 체이닝 (MDN)
- 관련 연산자: 구조 분해 할당,
this,new,typeof