JavaScript: Null과 Undefined, 그리고 Optional Chaining

Heechan
HcleeDev
Published in
9 min readDec 31, 2021
Photo by Towfiqu barbhuiya on Unsplash

Swift에서는 null 이 아닌 nil 을 사용하고 있기도 하고, 뭔가 아예 없는 상태를 따로 지칭하는 말이 없다. 그래서 웹 코드를 처음 볼 때 나도 모르게 null 말고 다른 개념도 같이 사용하고 있었다는 사실에 흠칫했던 기억이 있다.

이번주는 짤막하게 JS, TS에서의 nullundefined 에 대해 알아보자.

Null, Undefined와 그 차이

nullundefined 는 이 변수가 어느 값을 가르키지 않는다는 것을 의미한다는 점에서는 비슷하다.

null 은 어떤 값이 의도적으로 비어있음을 의미하는 표현이다.

undefined아예 어떤 값이 할당되지 않은 상태를 의미한다.

이 두 가지의 차이를 간단히 코드로 알아보자.

let a
console.log(a) //출력: undefined
let b = null
console.log(b) //출력: null

a 의 경우 변수를 선언했지만 그 아무 값도 넣어주지 않았다. 어떤 값이 아예 할당이 되지 않은 경우에 undefined 가 된다.

b 의 경우는 어떤 값이 할당이 되긴 했는데, null 이라는 것을 할당된 상태다. 이 b 는 어떤 숫자를 가질 수도 있고, 어떤 문자열을 가질 수도 있고, 어떤 객체를 가질 수도 있었을 것이다. 하지만 여기서는 특수한 값인 null 을 가짐으로써, 이 b 는 현재 아무런 정보도 없다는 것을 표현할 수 있다.

function test(first, second) {
console.log(second)
}
test("first") //출력: undefined
test("first", "It's second") //출력: It's second
test("first", null) //출력: null

undefined 가 아예 아무것도 없는, 할당되지 않은 상태를 표현한다는 것은 위 예시에서도 확인해볼 수 있다. 내가 급조한 함수인 test 는 2개의 매개변수를 받고, 그 중에서 second 를 출력한다.

첫 번째 호출에서는 매개변수를 단 하나만 넣어줬다. 아마 Swift에서는 컴파일러가 거품을 물었겠지만 JavaScript에서는 “아, 그냥 second 안들어왔네?”하고 만다. testsecond 를 찾아봤지만 매개변수 목록에 아예 없으므로 undefined 로 판단한다.

두 번째 호출에서는 들어간 값이 잘 출력되고 있다.

세 번째 호출에서는 우리가 의도적으로 null 이라는 값을 넣어준 상태라고 볼 수 있겠다. 첫 번째 호출에서는 test 가 매개변수 목록을 살펴봤을 때 { first: "first" } 밖에 없어서 undefined를 낼 수 밖에 없었지만, null 은 값이기 때문에 목록에 { first: "first"; second: null; } 이렇게 되어있었을 것이다. 그 목록에서 second 를 꺼내와 출력하니 null 이 출력된 것이다.

console.log(null === undefined) //출력: false
console.log(null == undefined) //출력: true
console.log(null) //출력: null
console.log(!null) //출력: true
console.log(undefined) //출력: undefined
console.log(!undefined) //출력: true

위 예시에서도 체크해볼 수 있는 것이 두 가지 있다.

우선 nullundefined 를 비교할 때는 === 를 써야 한다. 위에서 볼 수 있듯 == 을 사용하면 긍정적 마인드를 가진 JS가 형 변환까지 해가면서 서로 같은 의미라는 것을 확인해내기 때문에, 완전히 동치하는지 여부를 확인하기 위해서는 === 을 사용해야 한다.

그래서 null 을 확인할 때는 === 를 쓰는 것이 좋다. 예를 들면 로직 내에서 어떤 값이 null 일 때만 동작해야 하는 것이 있다면 if (something === null) { ... } 이런 식으로 체크하는 것이 확실할 것이다. == 를 사용하면 undefined 일 수도 있기 때문에…

또한 nullundefinedfalsy한 값이다. 따라서 그냥 if (null) 만 해도 이 조건문은 if (false) 를 한 경우와 똑같은 효과를 보인다고 볼 수 있다. 위에서는 console.log(null) 을 했을 때 당연히 그냥 null 으로 나오지만, if 문 안에서는 Boolean 값으로 변환되어야 하므로 nullfalse 와 같은 의미로 해석된다.

위에서 볼 수 있는 것과 같이 !null , !undefined 처럼 표현할 시 Boolean이 필요한 문맥이 아니더라도 true 가 반환된다.

Optional Chaining

어떤 값이 Nullish할 수 있는 경우에는 실제로 활용하기 위해 Optional을 벗겨줘야 하는 경우가 많다. 여기서 Nullish한 값이란 JS에서 null 이나 undefined 값을 의미한다.

예를 들면 API 요청에서 우리는 a 라는 프로퍼티가 올 줄 알았는데 실제 Dto에는 그 a 가 포함되지 않은 경우, undefined 가 될 것이고 그에 접근하려고 하다가는 크래시가 날 수도 있다.

그런 경우 편하게 코드를 작성할 수 있도록 JavaScript는 Optional Chaining 기능을 제공한다.

일단 Chaining이란 . 을 이용해 객체가 가지고 있는 데이터에 한 단계 깊게 접근하는 것을 얘기한다. hello 객체 안의 world 라는 값에 접근하고 싶다면 hello.world 로 접근할 것이다. 지금까지 매일같이 쓰는 존재지만 명칭은 Chaining 연산자로, 그리 익숙하지는 않은 이름이긴 하다.

Optional Chaining은 Nullable한 경우에도 안전하게 접근하기 위해 제공되는 연산자인데, ?. 이다. 이를 이용하면 ? 앞에 있는 객체가 null 이나 undefined 가 아닌지, 즉 유효한 참조인지 검증하지 않고도 내부 프로퍼티를 읽을 수 있도록 해준다.

안전하게 접근할 수 있다는 말은, 이렇게 접근할 때 뭔가 문제가 있어도 에러를 내뱉지 않는다는 말이다. Optional Chaining의 경우에는 유효하지 않은 참조면 에러 대신 undefined 를 반환한다.

function getName(target) {
const result = target.name
console.log(result)
}
function getNameByOptional(target) {
const result = target?.name
console.log(result)
}
const targetObject = {
num: 1,
}
getName() //출력: TypeError: Cannot read properties of undefined (reading 'name')
getNameByOptional() //출력: undefined

이 예시를 한 번 보자.

getNametargetname 을 바로 접근하고 있고, getNameByOptionaltarget?.name 으로 Optional Chaining을 통해 접근하고 있다. 아래 실행 코드에서는 각 함수를 호출하기는 하나 매개변수는 아예 주고 있지 않다.

getName 을 실행했을 때는 target의 값을 찾을 수 없기에 타입 에러가 발생하고, 코드가 돌아가지 않게 된다.

하지만 getNameByOptional 을 실행했을 때는, target 의 값을 찾지 못했기에 undefined 를 반환한다. 에러가 발생하지 않고 돌아가므로 좀 더 안전하게 코드를 돌린다고 생각할 수 있을 것이다.

그런데 사실 우리의 targetObjectname 을 가지고 있지도 않다. 하지만 위 코드 그대로라면 getNameByOptional 에다 매개변수로 targetObject 를 넣어줘도 단순한 undefined 만 되돌아올 것이 뻔하다.

function getNameByOptional(target) {
const result = target.name ?? "There is No Name!"
console.log(result)
}
const targetObject = {
num: 1,
}
const targetWithNullObject = {
num: 1,
name: null,
}
getNameByOptional(targetObject) //출력: There is No Name!
getNameByOptional(targetWithNullObject) //출력: There is No Name!

그럴 경우 이렇게 ?? 을 이용해, Nullish한 값이 나왔을 때 특정 값으로 대체할 수 있도록 만들 수 있다. 이 ?? 은 Nullish coalescing 연산자라고 한다. 왼쪽 값이 Nullish할 시 오른쪽 값을 반환하도록 하는 연산자다.

위 코드에서 확인할 수 있듯 nameundefinedtargetObject 나, nulltargetWithNullObject 나 There is No Name을 출력하는 모습을 확인할 수 있다.

다만 Optional Chaining이나 Nullish coalescing의 경우 일정 버전 이상에서만 사용할 수 있는 것으로 보인다. 처음에 웹 컴파일러에서 돌리려다가 분명히 있는 기능인데 Syntax Error가 떠서 알맞은 버전의 컴파일러를 찾아다니느라 좀 헤멨다.

결론

Swift에서도 Nullable한 객체, 프로퍼티 등에 대해 ? , ! , ?? 등을 이용하는데, JavaScript에서도 비슷한게 있었다. TypeScript에서는 좀 더 활용할 여지가 많지 않을까 싶기도 하다.

이번엔 연말이기도 해서 간단한 주제로 짤막하게 적어보긴 했다. 내년엔 Web 쪽 지식이 좀 더 늘어날 수 있는 한 해가 되었으면 좋겠다.

참고한 것

--

--

Heechan
HcleeDev

Junior iOS Developer / Front Web Developer, major in Computer Science