Falsy 값이 6개인 이유는?

Boolean 컨텍스트에서 false로 평가되는 6개의 Falsy 값이 설계된 이유를 이해하고, 조건문에서의 활용 방법을 익힙니다

입문 15분 Falsy Boolean 컨텍스트 조건문 타입 변환

JavaScript를 처음 배울 때 가장 혼란스러운 순간 중 하나는 “왜 이 값이 false처럼 동작하지?”라는 의문이 드는 때입니다. JavaScript에는 Boolean 컨텍스트, 즉 조건문이나 논리 연산에서 false로 평가되는 특별한 값들이 존재하는데, 이를 Falsy 값이라고 합니다. 이 값들은 임의로 정해진 것이 아니라, 언어를 설계할 당시 “의미 없음” 또는 “비어 있음”을 표현하는 가장 자연스러운 방식으로 선정된 것입니다. Falsy 값을 정확히 이해하면, 장황한 비교 연산 없이도 간결하고 읽기 쉬운 조건문을 작성할 수 있게 됩니다.

🎯 핵심 특징

  • JavaScript에는 정확히 6개의 Falsy 값이 존재하며, 나머지 모든 값은 Truthy로 처리됩니다
  • false, 0, "" (빈 문자열), null, undefined, NaN이 6개의 Falsy 값입니다
  • 각 Falsy 값은 서로 다른 상황에서 “없음” 또는 “초기화되지 않음”을 나타내는 고유한 의미를 가집니다
  • 조건문에서 Falsy 값은 명시적으로 === false와 비교하지 않아도 자동으로 false처럼 동작합니다
  • Falsy 값과 헷갈리기 쉬운 [](빈 배열), {}(빈 객체)는 Truthy로 평가되어 초보자에게 함정이 됩니다

💡 실무에서의 영향

실무 코드에서는 값이 존재하는지 확인하는 패턴이 매우 자주 등장합니다. Falsy 값을 이해하지 못하면 불필요하게 길고 복잡한 조건을 작성하게 되고, 유지보수가 어려운 코드가 만들어집니다. 반대로 Falsy 값을 제대로 이해하고 있다면, API 응답에서 데이터 유무를 확인하거나, 사용자 입력이 비어 있는지 검사하거나, 함수 인자의 기본값을 설정하는 등의 작업을 훨씬 간결하게 처리할 수 있습니다. 특히 nullundefined의 차이, 그리고 0이 Falsy로 처리될 때 발생하는 미묘한 버그는 실무에서 자주 마주치는 문제이므로, 각 Falsy 값의 의미와 사용 맥락을 명확히 구분하는 것이 중요합니다. 이 주제를 숙달하면 코드의 가독성과 안전성을 동시에 높일 수 있습니다.


핵심 개념

6개의 Falsy 값

입문

JavaScript에는 “비어있다” 또는 “없다”는 뜻을 가진 특별한 값이 딱 6개 있어요. 이 6개 값만 if문에서 “아니오”처럼 동작하고, 나머지 모든 값은 “예”처럼 동작해요!

📭 우편함 비유로 생각해봐요 우편함을 열었을 때 편지가 없으면 “없다”고 말하죠? JavaScript도 비슷해요. null은 우편함 자체가 없는 것, undefined는 우편함이 있지만 아직 설치 안 된 것, 빈 문자열("")은 우편함이 있는데 속이 텅 빈 것이에요.

🔢 숫자 0은 왜 “없음”인가요? 수학에서 0은 “아무것도 없음”을 뜻해요. 사과가 0개라면 사과가 없는 거잖아요! JavaScript도 같은 논리로 숫자 0을 “없다”는 신호로 봐요. 그래서 if (사과개수)라고 쓰면, 사과가 0개일 때 자동으로 “없다”고 판단해요.

❓ NaN이 뭔가요? NaN은 “Not a Number”의 줄임말로, “숫자가 아니에요!”라는 뜻이에요. 예를 들어 “사과” ÷ 3처럼 말이 안 되는 수식을 계산하면 NaN이 나와요. 계산 결과가 말이 안 되니까 “없다”고 보는 거예요.

🗂️ 6개를 손가락으로 세어봐요 엄지: false (직접적인 거짓), 검지: 0 (숫자 없음), 중지: "" (빈 텍스트), 약지: null (아무것도 없음), 소지: undefined (아직 정해지지 않음), 그리고 마지막으로 NaN (계산 불가). 이 여섯 개 외에는 모두 “있다”고 봐요!

💡 왜 하필 이 6개인가요? 이 6개는 모두 공통점이 있어요. “의미 있는 값이 없다”는 상황을 표현해요. JavaScript를 만든 사람들이 “이런 상황에서는 자동으로 거짓으로 처리하면 코드 작성이 훨씬 편하겠다!”라고 결정한 거예요.

중급

JavaScript에서 Boolean 컨텍스트로 암묵적 타입 변환이 일어날 때 false로 평가되는 값을 Falsy 값이라고 합니다. 명세에 따르면 정확히 6개가 존재하며, 이 6개 외의 모든 값은 Truthy입니다.

6개의 Falsy 값과 각각의 의미

  • false: Boolean 타입의 거짓 리터럴
  • 0 (그리고 -0, 0n): 숫자 0 및 BigInt 0, 음의 0
  • "" (빈 문자열): 길이가 0인 문자열
  • null: 의도적으로 “값 없음”을 나타내는 참조 타입
  • undefined: 선언되었지만 값이 할당되지 않은 상태
  • NaN: 유효하지 않은 숫자 연산의 결과
const falsyValues = [false, 0, "", null, undefined, NaN];

falsyValues.forEach(val => {
  if (val) {
    console.log(val, "→ Truthy"); // 이 줄은 실행되지 않음
  } else {
    console.log(String(val), "→ Falsy"); // 모두 여기로
  }
});
// false → Falsy
// 0 → Falsy
//  → Falsy
// null → Falsy
// undefined → Falsy
// NaN → Falsy

Boolean() 함수로 직접 확인하기 Boolean() 함수를 사용하면 어떤 값이 Falsy인지 명시적으로 확인할 수 있습니다. 이중 부정 !!도 같은 결과를 반환합니다.

console.log(Boolean(false));     // false
console.log(Boolean(0));         // false
console.log(Boolean(""));        // false
console.log(Boolean(null));      // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN));       // false

// 이중 부정으로도 동일하게 확인 가능
console.log(!!null);             // false
console.log(!!"");               // false

심화

JavaScript의 Falsy 값 집합은 ECMAScript 명세의 추상 연산 ToBoolean(V)에 의해 엄밀히 정의되며, 이 연산은 Abstract Type Conversion의 핵심 알고리즘 중 하나입니다.

ECMAScript 명세의 ToBoolean 추상 연산 ECMAScript 2023 명세 7.1.2절 ToBoolean(argument)은 타입별 변환 규칙을 완전히 열거합니다. Falsy 값의 판정 기준은 다음과 같습니다:

  • Undefined: false 반환
  • Null: false 반환
  • Boolean: argument 그대로 반환 (false이면 false)
  • Number: argument가 +0, -0, 또는 NaN이면 false, 그 외 true
  • String: argument가 empty String(길이 0)이면 false, 그 외 true
  • BigInt: argument가 0n이면 false, 그 외 true
  • Object: 항상 true (빈 배열, 빈 객체 포함)

이 규칙이 중요한 것은 Symbol 타입과 Object 타입은 항상 Truthy라는 점입니다. Symbol은 고유한 식별자이므로 “없음”의 개념이 성립하지 않고, Object는 참조 타입으로 메모리 주소가 존재하는 한 항상 Truthy입니다.

-0NaN의 IEEE 754 연관성 -0(음의 0)이 Falsy인 이유는 IEEE 754 부동소수점 표준에서 +0-0이 동등하다는 수학적 개념에서 비롯됩니다. ECMAScript에서도 +0 === -0true이며, 두 값 모두 “숫자 없음”의 의미로 Falsy 처리됩니다. NaN은 IEEE 754의 특수값으로, 자기 자신과도 같지 않은 유일한 값(NaN !== NaN)이며, 유효하지 않은 연산의 결과이므로 Falsy로 정의됩니다.

엔진 레벨에서의 ToBoolean 최적화 V8 엔진은 ToBoolean 연산에 대해 인라인 캐싱(Inline Caching, IC)을 적용합니다. 동일한 타입의 값이 반복적으로 Boolean 컨텍스트에서 평가될 때, 타입 피드백(Type Feedback)을 기반으로 타입 체크를 생략하고 직접 분기합니다. Boolean 타입은 추가 연산 없이 즉시 반환되며, Smi(Small Integer) 타입의 0은 포인터 태그 비트를 이용해 상수 시간에 판별합니다.

Boolean 컨텍스트와 암묵적 변환

입문

JavaScript에서 if문처럼 “참이냐 거짓이냐”를 판단하는 상황을 “Boolean 컨텍스트”라고 해요. 여기서는 모든 값이 자동으로 “예” 또는 “아니오”로 바뀌어요!

🚦 신호등처럼 작동해요 신호등은 초록불이면 가고, 빨간불이면 서죠. JavaScript의 if문도 마찬가지예요. 어떤 값이 들어오든 “초록불(Truthy)“인지 “빨간불(Falsy)“인지 자동으로 판단해요. 여러분이 직접 “true야? false야?”라고 물어볼 필요가 없어요.

🎭 자동 변신 마법 값들이 Boolean 컨텍스트에 들어가면 자동으로 변신해요. 숫자 42는 “있다(Truthy)“로 변하고, 숫자 0은 “없다(Falsy)“로 변해요. “안녕”이라는 글자는 “있다”로, 빈 따옴표("")는 “없다”로 변하는 거예요.

🔄 어디서 이런 변환이 일어나나요? if문뿐만 아니라, while 반복문의 조건, 삼항 연산자(?:), 그리고 &&|| 같은 논리 연산자에서도 이런 자동 변환이 일어나요. “이 값이 있으면 이걸 해라”라는 패턴을 간단하게 쓸 수 있게 해줘요.

💡 논리 연산자의 단락 평가 &&는 앞의 값이 “없다”면 앞의 값을 돌려주고, “있다”면 뒤의 값을 돌려줘요. ||는 반대로 앞이 “없다”면 뒤의 값을 돌려줘요. 이 특성으로 “없으면 기본값 사용”처럼 편리하게 쓸 수 있어요.

중급

Boolean 컨텍스트는 JavaScript 엔진이 값을 Boolean으로 암묵적 변환(implicit coercion)하는 상황을 말합니다. 대표적인 Boolean 컨텍스트는 다음과 같습니다.

  • if (값) / while (값) / for (값) 의 조건 표현식
  • 삼항 연산자 값 ? a : b
  • 논리 연산자 &&, ||, !의 피연산자

이 컨텍스트에서 엔진은 내부적으로 ToBoolean 추상 연산을 수행하며, 그 결과에 따라 실행 흐름이 분기됩니다.

const userName = "";
const userAge = 0;
const userData = null;

// if 조건문에서 암묵적 변환
if (userName) {
  console.log("이름 있음"); // 빈 문자열이므로 실행 안 됨
}

// 논리 OR로 기본값 설정
const displayName = userName || "익명 사용자";
console.log(displayName); // "익명 사용자"

// 삼항 연산자
const ageLabel = userAge ? `${userAge}세` : "나이 미입력";
console.log(ageLabel); // "나이 미입력" (0은 Falsy)
// &&: 앞이 Falsy면 앞 값 반환, Truthy면 뒤 값 반환
console.log(null && "hello");     // null (단락 평가)
console.log("hi" && "hello");    // "hello"

// ||: 앞이 Falsy면 뒤 값 반환, Truthy면 앞 값 반환
console.log(null || "기본값");    // "기본값"
console.log("hi" || "기본값");   // "hi"

// ??  (Nullish Coalescing): null/undefined만 Falsy로 처리
console.log(0 ?? "기본값");       // 0 (0은 유효한 값으로 취급)
console.log(null ?? "기본값");    // "기본값"

심화

Boolean 컨텍스트에서의 암묵적 타입 변환은 ECMAScript 명세의 Abstract Operations 체계와 실행 컨텍스트(Execution Context) 평가 알고리즘에 의해 제어됩니다.

명세에서의 암묵적 Boolean 변환 메커니즘 ECMAScript 2023, 14.6절 The if Statement의 Runtime Semantics에 따르면, IfStatement: if (Expression) Statement 평가 시 다음 단계가 수행됩니다:

  1. exprRef = Evaluate(Expression) - 표현식 평가
  2. exprValue = GetValue(exprRef) - 참조에서 값 추출
  3. ToBoolean(exprValue) - Boolean 변환 수행
  4. 변환 결과가 true이면 consequent 문, false이면 alternate 문 실행

이 과정은 while, for 문의 IterationStatement 평가에서도 동일한 패턴을 따릅니다.

논리 연산자의 명세 기반 동작 (Short-circuit Evaluation) ECMAScript 13.13절 Binary Logical Operators의 Runtime Semantics:

  • && (Logical AND): 좌항을 평가한 후 ToBoolean이 false이면 좌항 값 반환 (우항 미평가). true이면 우항 평가 후 반환.
  • || (Logical OR): 좌항을 평가한 후 ToBoolean이 true이면 좌항 값 반환 (우항 미평가). false이면 우항 평가 후 반환.

주목할 점은 논리 연산자가 Boolean을 반환하지 않고 피연산자 값 자체를 반환한다는 것입니다. 이는 JavaScript의 유연성을 높이는 설계로, 단락 평가(Short-circuit Evaluation)를 통한 조건부 실행과 기본값 패턴을 가능하게 합니다.

Nullish Coalescing(??)과의 차별화 ES2020에서 도입된 ?? 연산자(13.13.2절)는 ToBoolean이 아닌 IsNullish(V) 연산을 사용합니다. IsNullish는 nullundefined에 대해서만 true를 반환하며, 0, "", false는 유효한 값으로 취급합니다. 이로써 “0이나 빈 문자열도 유효한 값”인 경우의 기본값 패턴을 안전하게 처리할 수 있습니다. 이는 || 기반 기본값 패턴의 고질적인 0/"" 버그를 해결합니다.

Falsy 함정과 안전한 패턴

입문

Falsy 값을 배웠지만, 함정이 있어요! 빈 배열([])이나 빈 객체({})는 비어있어 보이지만 사실 “있다”고 판단해요. 이 함정을 알아야 실수를 안 해요!

🪤 가장 흔한 함정: 빈 배열 []는 비어있는 배열이지만 JavaScript는 이걸 “있다(Truthy)“고 봐요. 왜냐하면 배열 자체는 존재하거든요! 빈 책꽂이도 책꽂이 자체는 있는 거잖아요. 그래서 if ([])는 “있다”로 판단해요.

🎭 “0”이라는 문자열은 Truthy예요! 숫자 0은 Falsy지만, 문자열 "0" (따옴표 안의 0)은 Truthy예요. “빈 게 아니라 글자가 있으니까요!” 이게 헷갈리는 이유는 화면에 보이는 게 둘 다 0처럼 보이기 때문이에요. 하지만 하나는 숫자고, 하나는 글자예요.

⚠️ 0을 사용할 때 주의! “사과 개수”가 0개일 때를 생각해보세요. if (사과개수)로 쓰면 0개일 때 “없다”로 처리해버려요! 0개인 것도 입력된 값이니 구분해야 할 때는 정확하게 if (사과개수 !== undefined)처럼 명시해야 해요.

🛡️ 안전하게 확인하는 방법 정확히 “null이나 undefined만 걸러내고 싶다”면 ?? 연산자를 써요. 0이나 빈 문자열도 유효한 값으로 인정하면서 null/undefined일 때만 기본값을 쓸 수 있어요. “없다”의 의미를 정확히 구분하는 게 핵심이에요!

중급

Falsy 값의 가장 큰 함정은 직관과 다른 Truthy 값들입니다. 특히 빈 배열([])과 빈 객체({})는 “비어있다”는 느낌을 주지만 Truthy로 평가됩니다. ECMAScript 명세에서 Object 타입은 항상 Truthy이기 때문입니다.

자주 겪는 Falsy 관련 버그 패턴

  1. 0이 유효한 값임에도 Falsy로 처리되어 기본값으로 덮어쓰이는 문제
  2. 빈 배열을 Falsy로 착각하여 if (!data) 조건이 통과되지 않는 문제
  3. "0" 문자열이 Truthy임을 모르고 잘못된 조건 분기
// 빈 배열과 빈 객체는 Truthy!
console.log(Boolean([]));  // true (함정!)
console.log(Boolean({}));  // true (함정!)

// "0" 문자열은 Truthy
console.log(Boolean("0")); // true (함정!)
console.log(Boolean("false")); // true (함정!)

// 0이 유효한 값인데 기본값으로 덮어쓰이는 버그
function setVolume(volume) {
  // 버그: volume이 0일 때 50으로 덮어씀
  const vol = volume || 50;
  return vol;
}
console.log(setVolume(0));  // 50 (버그! 0이 의도였는데)
console.log(setVolume(80)); // 80 (정상)
// 수정: ?? 연산자로 null/undefined만 처리
function setVolumeSafe(volume) {
  const vol = volume ?? 50;
  return vol;
}
console.log(setVolumeSafe(0));         // 0 (정상)
console.log(setVolumeSafe(undefined)); // 50 (정상)

// 배열이 비어있는지 확인할 때는 length 사용
const items = [];
if (!items.length) {
  console.log("아이템이 없습니다"); // 올바른 패턴
}

// 명시적 비교로 의도 명확히 하기
const count = 0;
if (count !== null && count !== undefined) {
  console.log("count가 설정됨:", count); // 0도 유효한 값으로 처리
}

심화

Falsy 관련 버그의 근원은 JavaScript의 타입 시스템이 값의 “없음(absence)“을 다중 표현(null, undefined, 0, "", NaN)으로 나타내는 데서 비롯됩니다. 이를 이해하고 방어적 코드를 작성하는 것이 실무에서 중요합니다.

Object 타입의 항상 Truthy 원칙과 설계 이유 ECMAScript 명세 7.1.2절에서 Object 타입은 인수와 무관하게 항상 true를 반환합니다. 이는 객체가 힙(Heap) 메모리에 할당된 참조 타입이기 때문입니다. 빈 배열([])과 빈 객체({})도 메모리 주소(참조, Reference)가 존재하므로 “없음”의 개념이 적용되지 않습니다. 내용의 비어있음과 참조의 존재를 구분하는 것이 핵심입니다.

[]의 내용이 비어있는지 확인하려면 [].length === 0, 객체의 프로퍼티 유무는 Object.keys({}).length === 0으로 명시적으로 검사해야 합니다.

0null/undefined의 의미론적 분리 - 실무 설계 원칙 실무에서 자주 발생하는 버그 패턴은 “0”이 유효한 입력값임에도 Falsy 처리로 기본값에 덮어쓰이는 것입니다. ES2020의 Nullish Coalescing(??)과 Optional Chaining(?.)은 이 문제를 명세 수준에서 해결합니다.

  • || 기반 패턴: 0, "", false를 “없음”으로 간주 (Falsy 전체 처리)
  • ?? 기반 패턴: null, undefined만 “없음”으로 간주 (IsNullish 연산)

실무 권장 패턴: API 응답이나 함수 인자에서 기본값 처리 시 ??를 우선 사용하고, 의도적으로 Falsy 전체를 처리할 때만 ||를 사용합니다.

TypeScript에서의 타입 안전 강화 TypeScript의 Strict Null Checks(strictNullChecks: true) 옵션은 nullundefined를 별도의 타입으로 분리하여, Falsy 관련 런타임 오류를 컴파일 타임에 탐지합니다. Control Flow Analysis를 통해 if (value) 이후 코드 블록에서 value의 타입을 null | undefined가 제거된 narrowed 타입으로 추론하여 타입 안전성을 보장합니다.