빌드노트
빈 숫자 입력을 Number로 읽으면 0이 된다
빈 number 입력의 value는 빈 문자열이고 Number로 바꾸면 0이 된다. 실제 브라우저에서 valueAsNumber와 validity를 비교해 선택 입력의 null, 실제 0, 필수 누락과 잘못된 숫자를 구분한다.
<input type="number">를 비워 뒀는데 저장 직전 값이 0이라면 Number(input.value)를 확인한다. 빈 입력의 value는 ""이고, Number("")는 0이다. 변환한 뒤에는 사용자가 입력한 0과 구분할 수 없다. 숫자로 바꾸기 전에 입력의 유효성과 빈 값 여부를 판단해야 한다.
이 결과는 ECMAScript의 문자열 숫자 변환 규칙 (새 탭에서 열림)에 따른다. 반면 HTML 표준의 valueAsNumber (새 탭에서 열림)는 숫자로 읽을 수 없으면 NaN을 반환한다. 다만 valueAsNumber로 바꾸는 것만으로 범위나 입력 간격까지 검사되지는 않는다.
같은 입력에서 세 가지를 함께 읽는다
기존 화면에서는 저장 코드가 읽는 input의 실제 id를 찾고, 아래 #amount를 그 id로 바꾼다. 개발자 도구 Console에서 이 코드를 실행하면 원래 문자열, 두 변환 결과, 실패한 입력 조건을 한 시점에 확인할 수 있다. validity 객체 전체를 출력하는 대신 개별 불리언을 복사해 현재 상태를 남긴다.
const amount = document.querySelector("#amount");
function inspectNumber() {
const v = amount.validity;
return {
value: amount.value, valueAsNumber: amount.valueAsNumber,
converted: Number(amount.value), valid: v.valid,
valueMissing: v.valueMissing, badInput: v.badInput,
stepMismatch: v.stepMismatch,
rangeUnderflow: v.rangeUnderflow, rangeOverflow: v.rangeOverflow
};
}
inspectNumber();
별도 폼에서 비교하려면 다음 내용을 새 number-check.html에 저장해 브라우저 새 탭에서 열고, 위 Console 코드를 실행한다. 외부 라이브러리는 필요 없다. 이 폼은 제출을 막고 값만 확인한다. 각 조건은 amount.value = "1.25"; inspectNumber()처럼 문자열을 대입해 반복할 수 있다.
<form onsubmit="event.preventDefault()">
<label for="amount">값 (0~10, 0.5 간격)</label>
<input id="amount" type="number" min="0" max="10" step="0.5">
</form>
이 글에서는 Windows 11의 headless Chromium 새 세션에서 loopback으로 연 합성 폼을 사용했다. 브라우저 User-Agent는 HeadlessChrome/153.0.0.0이었고 언어는 ko-KR이었다. required가 없는 상태에서 각 문자열을 value에 대입한 직후의 결과는 다음과 같았다. 아래 순서는 value → valueAsNumber → Number(value)다.
- 빈 값:
"" → NaN → 0.valid=true였고 오류 플래그는 모두false였다. - 실제 0:
"0" → 0 → 0.valid=true였다. - 허용한 소수:
"1.5" → 1.5 → 1.5.valid=true였다. - 간격에 맞지 않는 소수:
"1.25" → 1.25 → 1.25.stepMismatch=true,valid=false였다. - 최솟값 미만:
"-0.5" → -0.5 → -0.5.rangeUnderflow=true,valid=false였다. - 최댓값 초과:
"10.5" → 10.5 → 10.5.rangeOverflow=true,valid=false였다.
빈 값에서 valid=true인 것은 선택 입력이기 때문이다. amount.required = true로 바꾸고 같은 여섯 값을 다시 읽자 빈 값만 valueMissing=true, valid=false로 바뀌었다. 빈 값의 두 변환 결과는 여전히 NaN과 0이었다. 실제 0과 1.5는 계속 유효했다. required의 표준 규칙 (새 탭에서 열림)은 편집 가능한 입력의 빈 값을 거절하는 조건이며, Number의 변환 규칙을 바꾸지는 않는다.
소수가 예상과 달리 거절되면 step도 확인한다. 위 폼에서 step을 제거하자 1.5에 stepMismatch=true가 됐다. number 입력의 기본 step은 1 (새 탭에서 열림)이고, 이 폼은 min="0"을 기준으로 간격을 검사한다. 허용 간격이 0.5라면 그대로 명시한다. 간격 제한이 필요 없다면 step="any"를 선택할 수 있다. 실제로 이 설정에서 1.25는 유효해졌지만 10.5의 범위 초과는 남았다.
빈 문자열처럼 읽혀도 미입력이 아닐 수 있다
같은 선택 입력을 비운 뒤 브라우저 키 입력으로 - 하나만 넣었다. 이때도 value="", valueAsNumber=NaN, Number(value)=0이었다. 그러나 완전히 비운 상태와 달리 badInput=true, valid=false였다. 필수로 바꾸면 badInput과 valueMissing이 함께 true였고, 모두 지우자 badInput은 다시 false가 됐다.
따라서 value === ""만 먼저 보고 선택 입력을 미입력으로 처리하면, 이 브라우저에서는 작성 중인 잘못된 숫자도 미입력으로 받아 버린다. HTML 표준 (새 탭에서 열림)은 브라우저가 숫자로 바꿀 수 없는 편집 상태를 bad input으로 구분한다. 어떤 문자를 입력할 수 있는지는 브라우저의 UI에 따라 달라질 수 있다.
이 상태를 재현할 때는 입력 칸에 직접 -를 넣고 inspectNumber()를 호출한다. amount.value = "-"로 대신하면 관찰이 달랐다. 대입한 문자열이 빈 문자열로 정리돼 선택 입력의 badInput=false, valid=true가 됐다. 스크립트 대입과 사용자 편집 상태를 같은 실험으로 취급해서는 안 된다.
선택 입력은 null, 실제 0은 숫자 0으로 남긴다
여기서는 활성화되어 있고 편집 가능한 type="number" 요소를 대상으로, 선택 입력의 빈 값은 null, 유효한 입력은 숫자, 필수 누락이나 잘못된 입력은 오류라는 계약을 정했다. 기존의 Number(input.value) 자리에 다음 함수를 사용하면 된다. 유효성을 먼저 검사한 다음 빈 값을 나눈다.
function readNumber(input) {
if (!input.validity.valid) throw new RangeError("Invalid number input");
if (input.value === "") return null;
const value = input.valueAsNumber;
if (Number.isNaN(value)) throw new TypeError("Not a number");
return value;
}
앞의 Console에서 함수를 정의하고 readNumber(amount)로 호출한다. 선택 입력을 완전히 지우면 null, 0을 넣으면 숫자 0, 1.5를 넣으면 1.5였다. 1.25·-0.5·10.5와 키 입력으로 만든 - 상태는 RangeError로 거절됐다. required=true인 빈 값도 같은 오류였다. Number.isNaN은 인자를 숫자로 강제 변환하지 않고 NaN인지 판별 (새 탭에서 열림)한다. 이 함수에서는 숫자를 반환하기 전 마지막 확인에 사용한다.
호출부에서는 오류를 잡았을 때 입력 안내를 표시하고 저장 처리를 끝내야 한다. 반환한 null과 숫자 0도 별도로 다룬다. valueAsNumber || 0으로 대체하면 빈 값의 NaN을 다시 0으로 합치므로 같은 문제가 돌아온다. 여기서 null은 선택 필드의 미입력을 뜻한다. 서버 API에서 null을 기존 값 삭제나 다른 뜻으로 해석한다면 그 계약에 맞춰 전송 값을 정해야 한다.
결과가 다르면 저장 코드가 읽는 요소의 type, required, min, max, step과 개별 오류 플래그를 다시 확인한다. 문자열을 이미 Number로 바꾼 뒤 찍은 로그만으로는 빈 값이었는지 복원할 수 없다. 프레임워크가 별도 상태를 관리한다면 DOM에서 읽은 값과 저장용 상태를 변환 전후로 대조한다.
직접 확인한 범위는 한 Chromium 환경의 DOM 값·유효성·위 함수까지다. Firefox·Safari·모바일 키보드, 다른 로케일의 소수점 입력, 프레임워크 연동과 실제 저장은 확인하지 않았다. HTML 표준의 검증 경계 (새 탭에서 열림)는 클라이언트 검사를 서버가 신뢰해서는 안 된다고 명시한다. 서버에서도 필수 여부와 허용 숫자·범위·간격을 검증해야 한다.