자바스크립트에서 중첩된 객체 속성에 접근할 때 사용하는 기법 중 하나는 옵셔널 체이닝입니다. ES2020에 도입된 옵셔널 체이닝은 객체에 접근하는 방식을 변화시켰습니다.

중첩된 많은 속성들을 가진 객체가 있고, 우리가 특정 중첩 속성에 접근하고자 할 때, 일반적으로 각 속성이 존재하는지 확인한 후 해당 속성에 접근해야 합니다. 이 과정을 단 한 줄로 더 효율적으로 처리할 수 있는 방법이 바로 자바스크립트에서 옵셔널 체이닝을 사용하는 것입니다. 바로 나타나는 속성이 존재하는지 확신이 없더라도, 자바스크립트 옵셔널 체이닝을 통해 중첩 속성에 접근할 수 있습니다.

문법

자바스크립트 옵셔널 체이닝 연산자 `?.`는 확인하고자 하는 참조 옆에 배치됩니다. 참조가 NULL일 경우, 검사는 중단되고 undefined가 반환됩니다. 그렇지 않으면 중첩 속성의 값에 접근하게 됩니다. 이제 문법을 살펴볼까요.

obj.value?.property // value가 존재하면 value.property처럼 동작, 그렇지 않으면 NULL 반환
obj.value?.expression // value가 존재하면 value.expression처럼 동작, 그렇지 않으면 NULL 반환

존재하지 않는 속성 문제

자바스크립트 객체는 빠르게 복잡해질 수 있습니다. 그래서 그러한 객체의 내부 속성에 접근하기가 어려울 수 있습니다. 이제 객체의 존재하지 않는 속성이나 null 속성에 접근하려고 할 때 무슨 일이 발생하는지 살펴봅시다. 학생 객체가 주소 속성을 가지고 있고 각 주소에는 우편번호라는 내부 속성이 있다고 가정해 보겠습니다.

```javascript
const student = {
  Address: {
   PostCode: '123'
  }
}

우리가 student.Address.PostCode에 직접 접근을 시도하면서 존재 유무를 확인하지 않으면 오류가 발생할 수 있습니다. student.Address.PostCode의 존재를 확인한 후 사용하는 코드를 작성해야합니다.

if (student && student.Address != null && student.Address.PostCode != null) {
  return student.Address.PostCode;
}

만일 50개의 중첩된 속성이 있었다면, 위와 같은 if문을 작성하는 것은 금방 지루해질 것입니다. 이런 경우에 옵셔널 체이닝은 구세주와 같은 역할을 합니다.

이제 옵셔널 체이닝을 사용하여 위의 조건을 단 한 줄로 재작성해 봅시다.

return student?.Address?.PostCode

 이렇게 함으로써, 우리는 PostCode에 접근하기 전에 학생과 주소의 존재 여부를 명시적으로 확인하지 않아도 됩니다. 만약 위의 어떤 것이 NULL이거나 존재하지 않는다면, 자바스크립트는 오류를 던지는 대신 undefined를 반환합니다. 그래서 아래 코드는 undefined를 던질 것입니다.

return student?.StreetName?.PostCode

하지만 아래 코드는 오류를 발생시킵니다.

return student.StreetName.PostCode

이는 StreetName이 학생 객체 내에 존재하지 않는 속성이기 때문입니다.

함수 호출과 옵셔널 체이닝

함수 호출의 경우에도 자바스크립트 옵셔널 체이닝을 사용할 수 있습니다. API의 어떤 메소드를 사용할 때 사용할 수 없는 경우에 유용합니다. 옵셔널 체이닝을 사용하면 오류를 던지는 대신 자바스크립트는 undefined를 반환합니다.

return obj.functionName?.()

만약 우리가 숙제를 출력하는 함수를 가진 학생 객체를 가지고 있다면, 함수 호출 시 옵셔널 체이닝을 사용하는 방법을 살펴봅시다.

const student = {
  homework() {
    console.log("Homework is done")
  }
}

위의 코드는 “Homework is done”을 콘솔에 출력할 것입니다.

return student.homework?.()

주의할 점은, 위 예제에서 우리는 학생 객체가 이미 존재한다는 것을 알고 있습니다. 그래서 우리는 학생 뒤에 ?.를 사용하지 않았습니다. 만약 학생 객체의 존재 여부를 미리 알지 못한다면, 우리는 student?.homework?.()를 사용해야 합니다.

이제 존재하지 않는 함수를 호출하는 예를 살펴봅시다.

const student = {
  Address: {
    PostCode: '123'
  }
}

아래의 첫 번째 구문은 오류를 발생시키지 않는 반면, 두 번째 구문은 “student.homework는 함수가 아닙니다”라는 형식의 오류를 반환합니다.

return student.homework?.() //undefined를 반환합니다.
return student.homework() //오류를 발생시킵니다.

괄호 표현식과 옵셔널 체이닝

자바스크립트에서 옵셔널 체이닝은 속성 이름을 평가하는 괄호 표현식과 함께 사용될 수 있습니다.

학생 객체에 Jake라는 이름이 있다고 가정해 봅시다. 자바스크립트 옵셔널 체이닝을 사용하여 name 표현식을 접근하는 방법을 살펴봅시다.

const student = {"name" : "Jake"}

다음과 같이 배열 인덱스와 함께 옵셔널 체이닝을 사용할 수 있습니다. 만약 배열이 존재하고 NULL이 아니라면, 다음 표현식은 5번째 인덱스의 요소를 반환합니다.

return arr?.[5]

옵셔널 체이닝은 할당의 왼쪽에 사용될 수 없습니다
옵셔널 체이닝은 할당의 왼쪽에서는 유효하지 않습니다. 즉, 값을 할당할 수 없습니다.

이를 아래 코드 조각으로 이해해봅시다.

const student = {
  Address: {
    PostCode : '123'
  }
}


위의 작업은 문법 오류로 이어집니다. 왜냐하면 자바스크립트 옵셔널 체이닝 연산에 값을 할당할 수 없기 때문입니다.

student?.Address = 1 //문법 오류를 발생시킵니다.
student?.Address?.PostCode = 2 //문법 오류를 발생시킵니다.

단락 회로

자바스크립트 옵셔널 체이닝은 ?.의 왼쪽 부분이 존재하지 않거나 NULL인 경우 표현식의 실행을 중단하고(undefined 반환) 더 이상의 속성에 접근하지 않습니다. 이를 단락 회로라고 합니다.

let student = {}
return student?.Address?.PostCode

위의 표현식은 student.Address에서 이미 평가가 중단되었기 때문에 undefined를 반환합니다. 따라서 student.Address.PostCode에 접근하기 전에 표현식이 단락 회로(short-circuit)되었습니다. 이것을 자바스크립트에서 단락 회로라고 합니다.

옵셔널 체이닝 활용 예시

이제 자바스크립트 옵셔널 체이닝의 적용 예시를 통하여 이해를 돕도록 하겠습니다.

아래 예시에서는 이름과 주소 속성을 가진 학생 객체를 살펴보겠습니다.

// teacher 객체가 존재하지 않기 때문에, teacher.Name에 접근하면 오류가 발생합니다. 하지만 옵셔널 체이닝을 사용할 경우 오류 대신 undefined가 반환됩니다.
return student?.Name // Jake를 반환합니다.
return teacher?.Name //undefined를 반환합니다.
return teacher.Name //오류를 발생시킵니다.

객체에서 콜백과 fetch 메소드를 사용할 때 구조 분해 할당(destructuring assignment)을 하게 되면, 존재하지 않는 값이 있을 수 있습니다. 이러한 값은 존재를 증명하기 전까지는 함수로 호출될 수 없습니다. 이를 방지하기 위해 콜백을 다룰 때 옵셔널 체이닝을 사용할 수 있습니다. 가끔은 선택적 콜백이 정의되어 있지 않다면 메소드가 호출되지 않을 수도 있습니다. 이러한 불일치를 피하기 위해 자바스크립트 옵셔널 체이닝을 사용합니다.

// 함수 호출 시 옵셔널 체이닝을 사용하지 않으면 아래와 같은 일이 일어납니다.
function functionName(data, error) {
try {
  // 데이터로 무언가를 한다
} catch (err) {
    err.message; // error가 정의되지 않으면 예외가 발생한다
  }
}

error가 정의되지 않았다면, 예외가 발생합니다. 옵셔널 체이닝을 통해 이 문제를 방지할 수 있습니다.

// 함수 호출에서 옵셔널 체이닝 사용
function functionName(data, error) {
try {
  // 데이터로 무언가를 한다
} catch (err) {
    error?.(err.message); // error가 정의되지 않았다면 예외가 발생하지 않는다
  }
}

이제 error가 정의되지 않았더라도 예외가 발생하지 않습니다.

앞서 확인한 것처럼, 우리는 옵셔널 체이닝 연산자를 여러 번 쌓을 수 있습니다. 만약 중간에 어떤 매개변수가 null 값을 가지면 실행이 중단됩니다.

const student = {
  Name : "Jake",
  Address: {
    PostCode : 123
  },
  Grades: {
    Physics: 100,
    Chemistry: 99,
    Maths: 82
  }
};
return student?.Address?.PostCode //123
return student.Street.PostCode //error
return student?.Grades?.Maths //82
return student?.Total?.English //undefined
return student.Total.English //error

위 코드 예제에서, 우리는 모든 반환문에서 한 번 이상 옵셔널 체이닝을 쌓았습니다.
첫 번째는 주소 속성 내부의 우편번호 속성을 검사하고 123을 반환합니다.
두 번째 반환문에서는 존재하지 않는 Street 속성 내부의 우편번호 속성에 접근하려고 시도합니다. 그러나 옵셔널 체이닝이 Street 속성에서 실행을 중단시키고 undefined를 반환합니다.
세 번째 반환문에서는 Grades 속성 내부의 수학 속성에 접근하여 82를 반환합니다.
네 번째 문장에서는 존재하지 않는 Total 속성 내부의 영어 속성에 접근합니다. 그러나 오류가 발생하지 않고 실행이 Total 속성에서 중단되며 undefined가 반환됩니다.
하지만 다섯 번째 문장에서는 옵셔널 체이닝을 사용하지 않았기 때문에 오류가 발생합니다.

null 병합 연산자(Nullish Coalescing) ?? 연산자와 함께 사용하는 방법

null 병합 연산자 ??는 논리 연산자로, 왼쪽 피연산자가 null 또는 undefined가 아니면 왼쪽 피연산자를 반환합니다. 그렇지 않으면 오른쪽 피연산자를 반환합니다. 자바스크립트 옵셔널 체이닝은 null 병합 연산자와 결합하여, 왼쪽 표현식에서 undefined를 반환하는 경우 기본값을 반환하도록 사용될 수 있습니다. 다음 예에서 우리는 학생이 물리 성적이 존재한다면 그것을 반환하고, 그렇지 않으면 ‘Grade Unknown’을 반환하는 함수를 가지고 있습니다.

function studentPhysicsGrade(student) {
  const physicsGrade = student?.Grades?.Physics ?? "Grade Unknown";
}

studentPhysicsGrade({
  name: "Sam",
  Grades: {
    Physics: 100,
    Chemistry: 99,
    Maths: 82
  }
}); // 100을 반환합니다.

studentPhysicsGrade({
  name: "Rosa",
  details: { age: 19 },
}); // 'Grade Unknown'을 반환합니다.

studentPhysicsGrade({
name: "Katie",
Grades: {
    English: 67,
    Chemistry: 20,
    Maths: 100
  }
}); // 'Grade Unknown'을 반환합니다.