항상 javascript로 개발을 할 때는 함수의 매개변수에 기본값을 명시적으로 적어주려고 노력했다. 실무에서는 객체나 배열 등의 요소를 함수에 자주 전달하고, 그에 대해 내장 메소드나 객체 내 필드값을 이용하는 데이터 처리가 많았기 때문이다.
하지만 오늘 개발을 하다가 조금 당황스러운 상황을 마주했다. 함수의 인자로 null이 넘어갔는데 기본값 처리가 되지 않아서 로그를 찍어봤는데도 당연히 null로 나왔다. 나는 인자의 값이 undefined 또는 null 일 때 기본값 처리가 된다고 생각을 했고, 지금까지의 모든 개발은 그렇게 해왔는데 null은 기본값 처리에 포함되지 않는다니...?
이슈 내용
const func = (value = "") => {
console.log(value)
}
func(); // ""
func(undefined); // ""
func(null); // null
func("value"); // "value"
함수의 인자로 빈 값을 전달하면 undefined로 처리되며 또한 명시적으로 undefined를 전달 한 경우에도 마찬가지다. (실제 개발 시에는 이럴 이유가 전혀 없긴 하지만,,)
null의 경우는 초기값 처리가 되지 않아 null이 출력, 유효한 값을 전달할 경우에는 그 값이 그대로 출력된다.
예시 코드의 경우 간단한 함수이기 때문에 크게 문제는 없지만 객체나 배열인 값을 받고 내부 값을 참조하려고 하면 에러가 발생할 수 있으니 위험요소가 존재하기는 한다.
그럼 객체 형태의 다양한 값을 전달해서 각 초기값 처리에 대해서 살펴보자.
const changeContainerStyle = (options = { width: "auto" }) => {
console.log(options);
}
changeContainerStyle(); // 1. { width: "auto" }
changeContainerStyle(undefined); // 2. { width: "auto" }
changeContainerStyle(null); // 3. null
changeContainerStyle("value"); // 4. "value"
changeContainerStyle({}); // 5. {}
changeContainerStyle({ width: "" }); // 6. { width: "" }
changeContainerStyle({ width: "100px" }); // 7. { width: "100px" }
changeContainerStyle({ height: "100px" }); // 8. { height: "100px" }
changeContainerStyle 함수의 매개변수 options에 { width: "auto" }라는 기본값을 설정했다.
1. 인자가 없으므로 기본값 처리가 되어 { width: "auto" }가 출력된다.
2. 마찬가지로 undefined이기 때문에 기본값 처리가 된다.
3. null이 전달되었기 때문에 options는 null이 된다. 이 경우 options.width 같이 options를 참조하려고 하면 에러가 발생한다.
4. 마찬가지로 타입을 엄격하게 체크하지 않기 때문에 문자열 "value"가 options가 된다. 3번과 동일.
5. 빈 객체가 전달되고 그대로 출력된다.
6. 7. 8. 모두 동일하게 전달된 객체가 그대로 출력되고, 기본값은 무시된다.
결론은 undefined 그리고 빈 인자의 경우에만 기본값 처리가 되며
null 그리고 빈 객체 {} 를 포함한 다른 데이터 타입은 그 값이 그대로 출력된다.
해결 방안
undefined와 null을 함께 처리하기 위해서는 ?? 연산 또는 || 연산을 이용해서 예외처리를 해주면 될 것 같다.
(참고로 아래 예시에서는 기본값 처리 결과를 인자에 바로 할당했지만, 실제로는 원본 값을 수정하게 되니 아래처럼 쓰면 안된다.)
??, 널 병합 연산자
왼쪽 값이 null 또는 undefined일 때만 오른쪽 값을 반환하는 연산자.
const changeContainerStyle = (options = { width: "auto" }) => {
options = options ?? { width: "auto" }; // options가 null, undefined일 경우 기본값 처리
console.log(options);
}
changeContainerStyle(); // { width: "auto" }
changeContainerStyle(null); // { width: "auto" }
||, 논리 OR 연산자
왼쪽 값이 참 값(truthy)이면 왼쪽을, 아니라면 오른쪽 값을 반환하는 연산자.
const changeContainerStyle = (options = { width: "auto" }) => {
options = options || { width: "auto" }; // options가 true면 options, 아니면 기본값 처리
console.log(options);
}
changeContainerStyle(); // { width: "auto" }
changeContainerStyle(null); // { width: "auto" }
동일한 결과가 나온다. 그럼 아무거나 쓰면 되겠지? 싶지만 여기엔 정말 큰 차이가 있다.
첫 번째 널 병합 연산자(??)의 경우는 명확하게 "왼쪽 피연산자가 null 또는 undefined일 때만"이라고 적어뒀다.
하지만 논리 OR 연산자(||)의 경우는 "왼쪽 피연산자가 참 값(truthy)이면 왼쪽" 이라고 적혀있는데,
이는 왼쪽 값이 거짓같은 값(falsy)의 경우가 모두 포함되기 때문에 단순히 null, undefined 체크 용도로는 정확하지 않다.
Truthy와 Falsy가 뭘까?
Truthy = 자바스크립트에서 true로 평가되는 값
- true
- 비어있지 않은 문자열
- 0을 제외한 숫자
- 배열
- 객체
- 함수
Falsy = 자바스크립트에서 false로 평가되는 값
- false
- 0과 -0
- 빈 문자열
- null
- undefined
- NaN (Not a Number)
falsy한 값은 종류가 많아서 기본값을 처리할 때 논리 OR 연산자를 이용하게 되면 의도하지 않은 결과가 나올 수 있다.
false, null, undefined, NaN은 그럴 수 있다고 쳐도 0과 빈 문자열의 경우는 실수하기 쉽기 때문이다.
const changeContainerHeight = (height) => {
const value = height || 20;
console.log(value);
}
changeContainerHeight(); // 20
changeContainerHeight(null); // 20
changeContainerHeight(0); // 20
세 결과 모두 20을 출력한다.
결론적으로 논리 OR 연산자의 경우는 목적이 명확할 경우에만 사용하고, 유효하지 않은 값을 판단할 때에는 널 병합 연산자를 이용하자.
마무리
기본값 설정을 해줬던 이유가 아무래도 안정성 있는 예외처리를 위한거라 사실은 크게 사이드 이펙트가 날 일은 없다.
그래도 당연하다고 여겨왔던 부분이 당연한 게 아니게 되니까 기초의 중요성을 또 다시 느끼게 된다.
'Frontend > javascript' 카테고리의 다른 글
JavaScript 법정동 데이터 관리와 동적 선택기 모듈 구현: 동적 셀렉트 박스 구현 (1) | 2024.11.08 |
---|---|
JavaScript 법정동 데이터 관리와 동적 선택기 모듈 구현: 최신 데이터 가져오기 및 데이터 가공하기 (1) | 2024.11.07 |
jQuery 선택자를 이용해서 DOM 요소를 선택할 때 주의점 (0) | 2024.03.29 |
[javascript] 클립보드 제어하기 + 예제 (0) | 2023.12.14 |
[javascript] Array.reduce() + 예제 (0) | 2023.12.13 |