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.name
은user
오브젝트에 대해 작동하겠지만, 뒤의 다른 프로퍼티에 대해서는 작동하지 않는다. 뒤의 프로퍼티까지 작동하게 하고 싶다면 뒤의.
을?.
으로 바꾸어줘야 할 것이다.
?.
는 반드시 존재하지 않아도 괜찮은 값에만 사용해야 한다.
이를테면, 위의 예제 코드들에서 user
오브젝트는 반드시 존재해야 했지만, address
는 존재하지 않아도 됐다. 그렇다면 우리는 user.address?.street
과 같이 작성하는 것이 바람직하다.
user?.address?.street
은 바람직하지 않다. 반드시 존재해야 하는 user
오브젝트를 우리가 실수로 넣지 않거나 해도 올바른 에러메세지를 볼 수 없기 때문이다. 이런 일이 일어났을 때 디버깅하는 일은 매우 어렵다.
만일 user
라는 변수가 선언되어있지도 않다면?
// ReferenceError: user is not defined
user?.address;
변수는 let/const/var
등을 이용하여 선언되어 있어야 한다.
?.
는 존재하지 않는 값일 때, 바로 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가지 형태가 있다.
obj?.prop
는 obj
가 존재한다면, obj.prop
존재하지 않으면 undefined
obj?.[prop]
는 obj
가 존재한다면, obj.prop
존재하지 않으면 undefined
obj.method?.()
는 obj.method
가 존재한다면, obj.method()
존재하지 않으면 undefined
?.
는 직관적이고 사용하기 쉽다.
?.
는 깊숙한 곳에 있는 프로퍼티에 안전하게 접근할 수 있도록 도와준다.
?.
는 꼭 없을 수도 있는 변수에만 사용해야 한다. 프로그래밍적으로 발생하는 에러를 가려버리기 때문에 잘못 사용했을 때는 되려 디버깅하기 어려워질 수 있다.