javascript basics #6 옵셔널 체이닝 '?.'

Jake Seo·2020년 10월 26일
1

javascript-basics

목록 보기
6/7

javascript basics #6 옵셔널 체이닝 '?.'

이 글은 javascript.info를 참조하여 쓰여졌습니다.

  • 이 문법은 비교적 최근의 문법으로 오래된 브라우저들은 polyfills가 필요할 수 있다.

옵셔널 체이닝 ?.은 중첩된 오브젝트 프로퍼티에 접근하는 데에 가장 안전한 방법으로, 중간 프로퍼티가 존재하지 않은 경우에도 안전하게 접근이 가능하다.

'존재하지 않는 프로퍼티' 문제

이를테면 우리에게 user 오브젝트가 있고, user.address.street 이라는 프로퍼티에 접근하려 한다고 해보자.

let user = {};

alert(user.address.street);

TypeError: Cannot read property 'street' of undefined

위의 코드에서 에러가 일어날 것은 자명하다. 위의 에러는 이미 예상했던 결과이다.

실무에서는 이러한 에러가 발생하지 않도록 막는 것이 중요하다. 만일, street이 없더라도 에러보다는 차라리 undefined 값을 얻는 것이 훨씬 안정적일 것이다.

웹개발에서는, 웹 API를 통하여 얻은 오브젝트를 다룰 일이 많다. 그런데, 잘못된 셀렉터 등을 사용하거나 존재하지 않는 오브젝트를 참조하는 경우에는 null을 리턴할 수 있다.

// 클래스가 'elem'인 엘리먼트가 없다면?
let html = document.querySelector('.elem').innerHTML;

이 경우에 엘리먼트가 없다면 error가 발생하는데, 에러가 발생하면 프로세스들이 멈추기 때문에, 여기서 결과로 그냥 html = null을 받고싶다면 어떻게 해야 할까?

if와 같은 효과를 갖는 ? 연산자를 통해 해결할 수 있다.

let user = {};

alert(user.address ? user.address.street : null);

위와 같이 작성하면 에러는 없지만 user.address가 두번이나 들어가기에 아름답지 않다.

만일 더 깊은 프로퍼티를 선택해야 하는 경우에는 더 많이 ? 연산자를 써야 할 것이다.

alert(user.address ? user.address.street ? user.address.street.name : null : null);

위와 같이 작성하면 해당 코드를 이해하는데도 많은 수고가 필요하다.

위의 방식보다는 차라리 && 연산자를 이용하면 더 괜찮은 코드를 작성할 수 있다.

let user = {};
alert(user.address && user.address.street && user.address.street.name); // undefined and no error

하지만 위의 방법도 이상적이진 않다. 보다시피 user.address라는 부분을 이미 3번이나 반복했다.

그러한 연유로 ?.라는 새로운 옵셔널 체이닝이 언어에 추가됐다.

옵셔널 체이닝

옵셔널 체이닝 ?.?.이전 부분이 undefined이거나 null이면 내부 프로퍼티를 찾는 것을 중지하고 즉시 null이나 undefined를 반환한다.

null이나 undefined가 아닌 경우에는 '값이 존재한다'고 말할 것이다.

value?.props는 어떻게 해석될까?

  • 값이 존재한다면, value.props와 같은 의미를 가질 것이다.
  • 값이 존재하지 않는다면, (값이 null 또는 undeinfed일 때) null 또는 undefined를 반환한다.

user.address.street을 가장 안전하게 호출하는 방법은 다음과 같다.

let user = {};
alert(user?.address?.street);

이전의 코드에 비하면 상당히 클린해진 코드이다. user 오브젝트가 존재하지 않아도 동작한다.

let user = null;

alert(user?.address); // undefined
alert(user?.address.street); // undefined

?. 는 바로 앞의 값을 옵셔널하게 하는 것이 가능하지만 모든 값을 옵셔널하게 하는 것이 아니다.

이를테면 user?.address.street.nameuser 오브젝트에 대해 작동하겠지만, 뒤의 다른 프로퍼티에 대해서는 작동하지 않는다. 뒤의 프로퍼티까지 작동하게 하고 싶다면 뒤의 .?.으로 바꾸어줘야 할 것이다.

옵셔널 체이닝을 남용하지 않기

?.는 반드시 존재하지 않아도 괜찮은 값에만 사용해야 한다.

이를테면, 위의 예제 코드들에서 user 오브젝트는 반드시 존재해야 했지만, address는 존재하지 않아도 됐다. 그렇다면 우리는 user.address?.street과 같이 작성하는 것이 바람직하다.

user?.address?.street은 바람직하지 않다. 반드시 존재해야 하는 user 오브젝트를 우리가 실수로 넣지 않거나 해도 올바른 에러메세지를 볼 수 없기 때문이다. 이런 일이 일어났을 때 디버깅하는 일은 매우 어렵다.

?. 앞의 변수는 반드시 선언되어 있어야 한다

만일 user라는 변수가 선언되어있지도 않다면?

// ReferenceError: user is not defined
user?.address;

변수는 let/const/var 등을 이용하여 선언되어 있어야 한다.

Short-circuiting (단락)

?.는 존재하지 않는 값일 때, 바로 evaluation 자체를 중지시킨다. 만일, 해당 부분에 추가적인 함수 호출이나 사이드 이펙트가 있다면 동작하지 않는다.

let user = null;
let x = 0;

user?.sayHi(x++); // "sayHi" 라는 메소드 자체가 존재하지 않기 때문에 실행이 'x++' 부분까지 가지 않는다.

alert(x); // x는 그대로 0

다른 방식의 활용: ?.(), ?.[]

옵셔널 체이닝 ?.는 연산자가 아니라 특수한 문법 구조이다. 그래서 함수나 []와 같은 square brackets와도 잘 작동한다.

?.()는 존재하지 않을 수 있는 함수를 호출할 때 좋다.

let userAdmin = {
  admin() {
    alert("I am admin");
  }
};

let userGuest = {};

userAdmin.admin?.(); // I am admin
userGuest.admin?.(); // nothing (no such method)

userGuest.admin?.() 부분에서는 함수가 존재하지 않기 때문에 중간에 evaluation을 중지한다. ?.을 쓰지 않았다면 에러가 났을 것이다.

?.[] 역시 잘 작동한다. 객체의 프로퍼티를 접근할 때 우리는 . 대신에 []를 이용하는 경우가 많다. ?.[]의 경우도 객체 내부에 존재할지 안할지 모르는 프로퍼티에 대해 안전하게 접근할 수 있게 해준다.

let user1 = {
  firstName: "John"
};
let user2 = null;
let key = "firstName";

alert(user1?.[key]); // John
alert(user2?.[key]); // undefined
alert(user1?.[key]?.something?.not?.existing); // undefined

또 우리는 delete 와 함께 ?. 를 사용할 수 있다.

delete user?.name;

?.는 안전하게 읽기, 지우기에는 사용될 수 있지만, 쓰기에는 사용될 수 없다.

let user = null;

user?.name = "John"; // 에러
// undefined = "John"과 같은 의미를 갖는다.

요약

?.는 3가지 형태가 있다.

  1. obj?.propobj가 존재한다면, obj.prop 존재하지 않으면 undefined
  2. obj?.[prop]obj가 존재한다면, obj.prop 존재하지 않으면 undefined
  3. obj.method?.()obj.method가 존재한다면, obj.method() 존재하지 않으면 undefined

?.는 직관적이고 사용하기 쉽다.
?.는 깊숙한 곳에 있는 프로퍼티에 안전하게 접근할 수 있도록 도와준다.
?.는 꼭 없을 수도 있는 변수에만 사용해야 한다. 프로그래밍적으로 발생하는 에러를 가려버리기 때문에 잘못 사용했을 때는 되려 디버깅하기 어려워질 수 있다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글