Nullish Coalescing Operator란?

Nullish Coalescing Operator는 2020년 ES에서 도입된 JavaScript의 새로운 논리 연산자입니다. 이 글에서는 이 연산자가 어떻게 작동하는지 살펴보겠습니다.

JavaScript에는 네 가지 이상의 논리 연산자가 있습니다: AND &&, OR ||, NOT !, 그리고 Nullish Coalescing Operator ?? 입니다.

때로 Nullish Operator라고도 불리는 이 연산자는 두 피연산자 사이에서 사용됩니다:

operand1 ?? operand2

이 연산자를 이해하기 위해서는 “nullish”, “coalescing” 그리고 “short-circuiting”이 무엇을 의미하는지 알아야 합니다.

“nullish” 값이란 무엇인가?

JavaScript에서의 Nullish 값은 null과 undefined입니다. 이 값들은 falsy 값에 속하지만, 보다 구체적으로 null 값으로 분류됩니다. 모든 nullish 값은 falsy이지만, 모든 falsy 값(예: 0)이 nullish한 것은 아닙니다.

그래서, nullish 연산자는 null과 undefined 값과 관련이 있는 반면 다른 논리 연산자들은 일반적으로 truthy와 falsy 값과 관련이 있습니다.

“coalescing”이란 무엇인가?

사전적으로 “coalescing”은 “하나의 전체로 결합되는 것”을 의미합니다. 프로그래밍에 이것이 어떻게 적용될까요? 여러 값들을 결합하여 그것들로부터 하나의 값이 만들어진다는 것을 의미합니다.

프로그래밍에서의 coalescing은 특정 “값들을 결합하는 것”을 의미하지 않고, “제공된 값들로부터 어떤 값이 만들어지는가를 결정하는 것”에 더 가깝습니다.

이 글에서는 나중에 예제를 통해 이것이 어떻게 작동하는지 살펴보겠습니다.

Short-Circuiting

Short-Circuiting의 개념은 많은 프로그래밍 언어에서 발생합니다. boolean 관련 표현식을 실행할 때, 해석기가 표현식의 관련 없는 부분을 건너뛰는 경우가 발생합니다.

예를 들어, “나는 40살이고, 나는 기술 분야에 있다”와 같은 불린 표현식은 나가 40살이면서 또한 기술 분야에 있을 때만 참이 됩니다.

“나는 40살이다”를 실행한 후, 해석기는 “나는 기술 분야에 있지 않다”인 경우에 전체 표현식이 거짓이 되므로, 전체 표현식이 참인지 여부를 아직 결론지을 수 없습니다. 표현식의 두 번째 부분은 결과를 변경할 수 있기 때문에 관련이 있습니다.

하지만, “나는 40살이 아니다”라는 경우에는 short-circuiting이 발생합니다. 표현식의 첫 부분이 거짓을 반환하기 때문에, 해석기는 두 번째 표현식을 평가할 필요가 없다는 것을 알고 있습니다. 두 번째 부분은 결과를 변경하지 않으므로 해석기는 두 번째 부분을 건너뛰게 됩니다(이로써 자원-시간, 전력을 절약합니다).

이것은 nullish 연산자에도 적용됩니다.

이 글에서 Short-Circuiting에 대해 더 자세히 알아볼 수 있습니다.

Nullish Coalescing Operator

이제 이 연산자의 기본 사항을 살펴보았으니, 이 연산자가 무엇을 하는지 이해해 봅시다.

식에서 사용될 때, Nullish 연산자는 첫 번째 피연산자(왼쪽에 있는)가 null이나 undefined인지 확인합니다. 그렇지 않으면, 연산자는 그 표현식에서 이를 반환합니다. 하지만 첫 번째 피연산자가 그러한 값 중 하나인 경우, 연산자는 표현식에서 두 번째 피연산자를 반환합니다.

빠른 예를 들어 보겠습니다:

function expression1() {
  return null
}

const expression2 = 4 * 5

const result = expression1() ?? expression2

console.log(result)
// 20

여기서, expression1이라는 함수가 있고, 이 함수를 호출하면 null을 반환합니다. 그리고 expression2는 식 4 * 5에서 값을 가집니다.

result 변수에 대해, 우리는 nullish 연산자를 사용하고 expression1()expression2를 피연산자로 전달합니다.

첫 번째 피연산자(함수 호출 표현식)가 null을 반환합니다. 연산자는 첫 번째 피연산자가 null임을 확인하고, 두 번째 표현식에서 값을 반환합니다: expression2.

또 다른 예를 보겠습니다:

function expression1() {
  console.log("expression1")
  return false
}

function expression2() {
  console.log("expression2")
  return "Dillion"
}

const result = expression1() ?? expression2()

console.log(result)
// expression1
// false

여기서, expression1은 호출될 때 console.log("expression1")을 실행한 후 false를 반환하는 함수입니다. 그리고 expression2는 호출될 때 console.log("expression2")를 실행하고 “Dillion”을 반환하는 함수입니다.

nullish 연산자를 사용하여 첫 번째 피연산자로 expression1()을, 두 번째 피연산자로 expression2()를 사용하고 이 표현식에서 값을 result에 할당합니다.

이 코드를 실행하면, expression1이 실행된 것에서부터 “expression1″이 기록된 것을 볼 수 있고, result는 false로 기록됩니다. 이것은 expression1()이 nullish 연산자에서 반환된 표현식이라는 것을 의미합니다.

연산자는 첫 번째 표현식이 null이나 undefined를 반환하는 경우 두 번째 표현식을 반환합니다. 하지만 이 경우, 첫 번째 표현식이 false를 반환하므로, 연산자는 첫 번째 표현식을 반환합니다.

또한 “expression2″가 기록되지 않은 것을 알 수 있습니다. 이는 expression2()가 전혀 실행되지 않았다는 것을 의미합니다. 여기서 short-circuiting이 발생합니다.

연산자는 이미 첫 번째 피연산자가 null이나 undefined가 아니라는 것을 확인했으므로, 두 번째 표현식의 값이 연산자가 반환할 것을 변경하지 않기 때문에, 두 번째 표현식에 대해 신경 쓰지 않습니다.

Nullish 연산자와 OR 연산자

Nullish와 OR 연산자는 몇 가지 유사점이 있지만, 약간 다르게 동작합니다.

OR 연산자는 첫 번째 피연산자가 truthy 값인지 확인합니다. 첫 번째 피연산자가 그렇다면, 그것을 반환하고, 그렇지 않으면 두 번째 피연산자를 반환합니다.

하지만, Nullish 연산자는 첫 번째 피연산자가 nullish 값인지 확인합니다. 첫 번째 피연산자가 그런 값이 아니라면, 그것을 반환하고, 그렇지 않으면 두 번째 피연산자를 반환합니다.

OR 예제는 다음과 같습니다:

const expression1 = ""
const expression2 = "Dillion"

const result = expression1 || expression2

console.log(result)
// "Dillion"

첫 번째 피연산자인 expression1은 falsy 값(빈 문자열)이므로, 연산자는 두 번째 피연산자를 반환합니다. 만약 expression1이 20 같은 truthy 값이었다면, 그것이 반환되었고 short-circuiting이 발생했을 것입니다.

Nullish 예제는 다음과 같습니다:

const expression1 = undefined
const expression2 = "Dillion"

const result = expression1 ?? expression2

console.log(result)
// "Dillion"

여기서 nullish 연산자를 사용하면, 첫 번째 피연산자가 undefined인 nullish 값이므로, 연산자는 두 번째 피연산자를 반환합니다. 만약 expression1이 false, 20 또는 다른 non-nullish 값이었다면, 그것이 반환되었고 short-circuiting이 발생했을 것입니다.

AND/OR와 직접적으로 nullish 연산자 사용하기

AND와 OR 연산자를 표현식에서 직접 혼합할 수 있지만, nullish 연산자에 대해서는 그렇게 할 수 없습니다. 제가 말하는 것은 다음과 같습니다:

exp1 && exp2 || exp3 && exp4

여기서, 우리는 AND와 OR을 결합합니다. 이 표현식의 순서는 다음과 같습니다:

  1. “exp1 AND exp2”
  2. “그 결과 OR exp3”
  3. “그 결과 AND exp4”

short-circuiting으로 인해 2단계나 3단계가 도달하지 않을 수도 있습니다.

하지만, 이런 조합을 nullish 연산자로 직접 할 수는 없습니다. 예를 들면:

exp1 && exp2 ?? exp3 || exp4

여기서 우리는 AND, Nullish, 그리고 OR을 혼합하고 있습니다: 이것은 구문 오류를 일으킬 것입니다. 실제 예를 보겠습니다:

function expression1() {
  return null
}

const expression2 = 20 < 10

const expression3 = "Dillion"

const result = expression1() ?? expression2 || expression3
// SyntaxError: Unexpected token '||'

우리는 expression1이라는 null을 반환하는 함수를 호출하고, expression2는 20 < 10에서 반환된 값을 가지며, expression3은 문자열 값 “Dillion”을 가집니다.

이 세 표현식을 nullish와 OR 연산자를 사용하여 사용하면, 다음과 같은 것을 기대합니다:

  1. expression1()이 null을 반환하므로, nullish 연산자는 표현식의 오른쪽인 expression2 || expression3을 반환합니다.
  2. 오른쪽에서, OR 연산자는 왼쪽인 expression2가 truthy인지 확인합니다; 그것이 falsy 값이기 때문에, 연산자는 오른쪽을 반환합니다.

하지만, 이것을 실행하면, 오류가 발생합니다: SyntaxError: Unexpected token ‘||’. 이것은 이들 연산자를 직접 사용할 수 없다는 것을 의미합니다. 이들을 결합하는 유일한 방법은 괄호를 추가하는 것입니다:

const result = (expression1() ?? expression2) || expression3

console.log(result)
// Dillion

expression1() ?? expression2를 괄호로 묶음으로써, 우리는 반환된 결과를 OR 연산자의 첫 번째 피연산자로 사용할 수 있고, 두 번째 피연산자로 expression3을 추가할 수 있습니다.

정리

nullish 연산자는 잠재적으로 null이나 undefined 값에 대한 기본 값을 선언하는 데 매우 유용합니다. 예를 들어, API에서 객체를 기대하고 있는데, 그 객체가 예상된 속성을 포함하지 않는 경우, 해당 속성은 null을 가지거나 undefined일 수 있습니다:

const obj = {}

console.log(obj.type)
// undefined

nullish 연산자를 사용하여, 우리는 기본 값을 제공할 수 있습니다:

const obj = {}

console.log(obj.type ?? "default")
// "default"

기본 값 또는 안전 검사에 이 연산자를 사용하는 다양한 방법이 있습니다.