JavaScript: Null과 Undefined, 그리고 Optional Chaining
Swift에서는 null
이 아닌 nil
을 사용하고 있기도 하고, 뭔가 아예 없는 상태를 따로 지칭하는 말이 없다. 그래서 웹 코드를 처음 볼 때 나도 모르게 null
말고 다른 개념도 같이 사용하고 있었다는 사실에 흠칫했던 기억이 있다.
이번주는 짤막하게 JS, TS에서의 null
과 undefined
에 대해 알아보자.
Null, Undefined와 그 차이
null
과 undefined
는 이 변수가 어느 값을 가르키지 않는다는 것을 의미한다는 점에서는 비슷하다.
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
안들어왔네?”하고 만다. test
는 second
를 찾아봤지만 매개변수 목록에 아예 없으므로 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
위 예시에서도 체크해볼 수 있는 것이 두 가지 있다.
우선 null
과 undefined
를 비교할 때는 ===
를 써야 한다. 위에서 볼 수 있듯 ==
을 사용하면 긍정적 마인드를 가진 JS가 형 변환까지 해가면서 서로 같은 의미라는 것을 확인해내기 때문에, 완전히 동치하는지 여부를 확인하기 위해서는 ===
을 사용해야 한다.
그래서 null
을 확인할 때는 ===
를 쓰는 것이 좋다. 예를 들면 로직 내에서 어떤 값이 null
일 때만 동작해야 하는 것이 있다면 if (something === null) { ... }
이런 식으로 체크하는 것이 확실할 것이다. ==
를 사용하면 undefined
일 수도 있기 때문에…
또한 null
과 undefined
는 falsy한 값이다. 따라서 그냥 if (null)
만 해도 이 조건문은 if (false)
를 한 경우와 똑같은 효과를 보인다고 볼 수 있다. 위에서는 console.log(null)
을 했을 때 당연히 그냥 null
으로 나오지만, if
문 안에서는 Boolean 값으로 변환되어야 하므로 null
은 false
와 같은 의미로 해석된다.
위에서 볼 수 있는 것과 같이 !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
이 예시를 한 번 보자.
getName
은 target
의 name
을 바로 접근하고 있고, getNameByOptional
은 target?.name
으로 Optional Chaining을 통해 접근하고 있다. 아래 실행 코드에서는 각 함수를 호출하기는 하나 매개변수는 아예 주고 있지 않다.
getName
을 실행했을 때는 target
의 값을 찾을 수 없기에 타입 에러가 발생하고, 코드가 돌아가지 않게 된다.
하지만 getNameByOptional
을 실행했을 때는, target
의 값을 찾지 못했기에 undefined
를 반환한다. 에러가 발생하지 않고 돌아가므로 좀 더 안전하게 코드를 돌린다고 생각할 수 있을 것이다.
그런데 사실 우리의 targetObject
는 name
을 가지고 있지도 않다. 하지만 위 코드 그대로라면 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할 시 오른쪽 값을 반환하도록 하는 연산자다.
위 코드에서 확인할 수 있듯 name
이 undefined
인 targetObject
나, null
인 targetWithNullObject
나 There is No Name을 출력하는 모습을 확인할 수 있다.
다만 Optional Chaining이나 Nullish coalescing의 경우 일정 버전 이상에서만 사용할 수 있는 것으로 보인다. 처음에 웹 컴파일러에서 돌리려다가 분명히 있는 기능인데 Syntax Error가 떠서 알맞은 버전의 컴파일러를 찾아다니느라 좀 헤멨다.
결론
Swift에서도 Nullable한 객체, 프로퍼티 등에 대해 ?
, !
, ??
등을 이용하는데, JavaScript에서도 비슷한게 있었다. TypeScript에서는 좀 더 활용할 여지가 많지 않을까 싶기도 하다.
이번엔 연말이기도 해서 간단한 주제로 짤막하게 적어보긴 했다. 내년엔 Web 쪽 지식이 좀 더 늘어날 수 있는 한 해가 되었으면 좋겠다.
참고한 것