본문 바로가기

Languages/JavaScript

[JavaScript] nullish 병합 연산자 '??'

728x90


??


분명 연산자가 맞는데, 이상하게 이 두개의 물음표만 보면 머릿속에 해석이 안되고 물음표가 생긴다.
nullish 병합 연산자를 잘 해석할 수 있도록 해보자.

 


 

 

 nullish 병합 연산자의 용도 

 

??를 사용하면 짧은 문법으로 여러 피연산자 중 그 값이 ‘확정되어있는’ 변수를 찾을 수 있다.

즉,  null도 아니고 undefined도 아닌 값을 찾는 데 쓰인다.

 

x = a ?? b
x = (a !== null && a !== undefined) ? a : b;

 

||, &&, ! 를 모른다면 "논리연산자 (ll, &&, !)" 게시글 참고 https://dev-ini.tistory.com/43
?를 모른다면 "if와 ?를 사용한 조건 처리" 게시글 참고 https://dev-ini.tistory.com/42

 

위에 두 코드는 같은 의미인데, 밑에 코드 처럼 길게 쓰기 싫어서 위에 같이 연산자를 사용하는 것이다.

위의 코드를 해석을 해보자면 다음과 같다.

a가 null이 아니고 undefined도 아닌 것true이면,  a가 x에 할당된다.
a가 null이 아니고 undefined도 아닌 것false이면,  b가 x에 할당된다.

 

이제 위에 해석을 보면서 밑에 코드를 보자.

x = a ?? b

 

이걸 딱 보면 a가 null이나 undefined가 아니면 a가 선택받는다는 것을 알아야한다.
만약 a가 아무거토 없는 애라서 까이면, b가 선택 받는 것!
앞에서부터 줄 서있는데 값이 들어있는 애가 선착순으로 선택받는다고 생각하면 쉽다.

 

 

이를 토대로 이제 조금 더 업그레이드 된 예시를 살펴보자.
(firstNamelastNamenickName이란 변수에 사용자 이름이나 별명을 저장하는데, 사용자가 아무런 정보도 입력하지 않는 케이스도 허용한다는 전제로 작성된 예시이다.)

let firstName = null;
let lastName = null;
let nickName = "바이올렛";

// null이나 undefined가 아닌 첫 번째 피연산자
alert(firstName ?? lastName ?? nickName ?? "익명의 사용자"); // 바이올렛

 

복잡해보이지만, 

 firstName ?? lastName ?? nickName ?? "익명의 사용자" 

이것도 결국에는

 a ?? b ?? c ?? d 

이거랑 같은 구조이다.

줄서있는 3가지 변수를 차례대로 판별한다. 

firstName은 null이다. →탈락 
lastName은 null이다. →탈락 
nickName은 "바이올렛"이다.  →선택 받음  →"바이올렛"이 출력됨  (세 변수 중 실제 값이 있는 변수의 값을 출력한다.)

 

 

세 변수가 아무도 선택을 안받으면 어떻게 될까? (세 변수가 모두 값이 없는 경우)

let firstName = null;
let lastName = null;
let nickName = null;

// null이나 undefined가 아닌 첫 번째 피연산자
alert(firstName ?? lastName ?? nickName ?? "익명의 사용자"); // 익명의 사용자
firstName은 null이다. →탈락 
lastName은 null이다. →탈락 
nickName은 "바이올렛"이다. →탈락  
"익명의 사용자"가 출력됨

 

 

이렇게 되면 결국 true만 찾아내는 감별사인 ||(OR)연산자랑 비슷하다고 생각할 수도 있다.

그런데 두 연산자 사이에는 중요한 차이점이 있다. 

||첫번째 truthy한 값을 반환하는 반면, 

??첫번째 정의된(defined) 값을 반환한다. 

즉, 판별의 기준이 다른 것이다.

||는 true를 찾는거고,
??는 값이 있는 애를 찾는 것이다.

 

null과, undefined, 숫자0을 구분지어 다뤄야할 때 이 차이점은 매우 중요한 역할을 한다.

 

let height = 0;

alert(height || 100); // 100
alert(height ?? 100); // 0

 

(height || 100) 에서 height = 0 이므로  height는 falsy한 값이다. 따라서 ||는 true를 찾으므로 100을 출력한다.

(height ?? 100) 에서 height = 0 이므로 숫자0 이라는 값이 들어있다. (=null, undefined가 아니다.) 따라서 ??는 값이 있는 것을 찾으므로 0을 출력한다.

 

따라서 높이처럼 0이 할당될 수 있는 변수를 사용해서 개발을 할 때는 ??가 적합하다.
왜냐하면 ??은 "값"을 찾을 수 있기 때문이다.

 

 


 

 

 연산자 우선순위 (자주 쓰는 것 위주로 정리함) 

 

1.   ( ),  [ ]   (괄호, 대괄호) 
2.   !,  ++,  --   (부정, 증감) 
3.   *,  /   (곱셈, 나눗셈) 
4.   +,  -   (덧셈, 뺄셈)
5.   <,  <=,  >,  =>   (관계) 

6.   ==,  !=  
7.   && (AND)    
8.   || (OR)  
9.   ?? (병합)  
10.   ?(조건)  
11.   = (할당)  

 

*참고)   a && b || c && d  (a && b) || (c && d) 와 동일하게 동작한다.

 ??는 =(할당)와 ?(조건) 보다는 먼저, 대부분의 연산자보다는 나중에 평가된다.
그렇기 때문에 복잡한 표현식 안에서 ??를 사용해 값을 하나 선택할 땐 괄호를 추가하는 것이 좋다.
그렇지 않으면 *가 ??보다 우선순위가 높기 때문에 *가 먼저 실행된다.

 

let height = null;
let width = null;

// 괄호를 추가!
let area = (height ?? 100) * (width ?? 50);

alert(area); // 5000
// 원치 않는 결과
let area = height ?? (100 * width) ?? 50;

 

 

??엔 자바스크립트 언어에서 규정한 또 다른 제약사항이 있다.

안정성 관련 이슈 때문에 ??는 &&나 ||와 함께 사용하지 못한다는 것이다.

 

아래 예시를 실행하면 문법 에러가 발생한다.

let x = 1 && 2 ?? 3; // SyntaxError: Unexpected token '??'

 

이 제약에 대해선 아직 논쟁이 많긴 하지만 사람들이 ||를 ??로 바꾸기 시작하면서 만드는 실수를 방지하고자 명세서에 제약이 추가된 상황이다.

제약을 피하려면 괄호를 사용주면 된다.

let x = (1 && 2) ?? 3; // 제대로 동작합니다.

alert(x); // 2

 

 

 

 

참고자료

https://ko.javascript.info/nullish-coalescing-operator

 

728x90