Skip to Content

9장 타입 변환과 단축 평가

9-1. 타입 변환이란?

자바스크립트의 모든 값은 타입이 있다. 값의 타입은 개발자의 의도에 따라 다른 타입으로 변환할 수 있다. 개발자가 의도적으로 값의 타입을 변환하는 것을 명시적 타입 변환(explicit coercion) 또는 타입 캐스팅(type casting)이라고 한다. 또한 자바스크립트 엔진이 문맥에 따라 값의 타입을 자동으로 변환하는 것을 암묵적 타입 변환(implicit coercion)이라고 한다.

const num = 123; const str = String(num); // 명시적 타입 변환 console.log(str); // "123" console.log(typeof str); // "string" const str2 = num + ''; // 암묵적 타입 변환 console.log(str2); // "123" console.log(typeof str2); // "string"

타입 변환은 기존 원시 값을 이용해 다른 타입의 새로운 원시 값을 생성한다. 기존 변수 값을 직접 변경하는 것이 아니라 새로운 값을 생성하는 것이다.

명시적 타입 변환만 사용하는 것이 좋을까?

암묵적 타입 변환이 가독성 측면에서 유리하다면 암묵적 타입 변환을 사용해도 무방하다. 다만, 암묵적 타입 변환은 의도치 않은 결과를 초래할 수 있으므로 주의해야 한다.

개인적 의견: 명시적 타입 변환을 선호하며, 타입스크립트를 사용하면 이런 고민을 덜 수 있다.

9-2. 암묵적 타입 변환

자바스크립트 엔진은 문맥에 따라 값의 타입을 자동으로 변환한다. 암묵적 타입 변환이 발생하면 문자열, 숫자, 불리언과 같은 원시 타입으로 변환된다.

console.log(1 + '2'); // "12" console.log(1 * '2'); // 2 console.log(!0); // true

9-2-1. 문자열 타입으로의 변환

문자열 타입으로의 변환이 필요한 문맥

  • 문자열 연결 연산자 +의 피연산자 중 하나 이상이 문자열인 경우
console.log('The answer is ' + 42); // "The answer is 42" console.log('5' + 10); // "510"

암묵적 타입 변환의 다양한 예시

자바스크립트는 코드의 문맥(상황)에 따라 다양한 값들을 자동으로 문자열로 변환한다.

// 숫자를 문자열로 변환 console.log('5' + 10); // "510" // 객체를 문자열로 변환 console.log('Value: ' + { a: 1 }); // "Value: [object Object]" // 배열을 문자열로 변환 console.log('Array: ' + [1, 2, 3]); // "Array: 1,2,3" // null, undefined를 문자열로 변환 console.log('null: ' + null); // "null: null" console.log('undefined: ' + undefined); // "undefined: undefined"

연산자 없이도 암묵적 타입 변환이 일어나는 경우

// 템플릿 리터럴 - 연산자 없이 자동 변환 console.log(`숫자: ${42}`); // "숫자: 42" console.log(`객체: ${{ a: 1 }}`); // "객체: [object Object]"

9-2-2. 숫자 타입으로의 변환

숫자 타입으로의 변환이 필요한 문맥

  • 산술 연산자(-, *, /, %, **)의 피연산자인 경우
console.log('5' - 1); // 4 console.log('5' * '2'); // 10 console.log('5' / '2'); // 2.5 console.log('5' % 2); // 1 console.log('5' ** 2); // 25
  • 비교 연산자(<, <=, >, >=, ==, !=)의 피연산자인 경우
console.log('5' < 10); // true console.log('5' == 5); // true
  • 단항 덧셈 연산자(+)의 피연산자인 경우
console.log(+true); // 1 console.log(+'123'); // 123 console.log(+null); // 0 console.log(+undefined); // NaN console.log(+{}); // NaN console.log(+[1, 2, 3]); // NaN console.log(+[]); // 0 console.log(+Symbol()); // TypeError: Cannot convert a Symbol value to a number

변환 규칙 정리

  • true → 1
  • false, null, 빈 문자열(''), 빈 배열([]) → 0
  • undefined, 빈 객체({}), 요소가 있는 배열 → NaN
  • Symbol → TypeError 발생

9-2-3. 불리언 타입으로의 변환

자바스크립트 엔진은 불리언 타입이 아닌 값을 Truthy 값(참으로 평가되는 값) 또는 Falsy 값(거짓으로 평가되는 값)으로 구분한다. 즉, 제어문의 조건식과 같이 불리언 값으로 평가되어야 할 문맥에서 Truthy 값은 true로, Falsy 값은 false로 암묵적 타입 변환된다.

아래 값들은 false로 평가되는 Falsy 값이다.

  • false
  • 0 및 -0
  • "" (빈 문자열)
  • null
  • undefined
  • NaN
if (!false) console.log(false + ' is falsy'); if (!0) console.log(0 + ' is falsy value'); if (!NaN) console.log(NaN + ' is falsy value'); if (!'') console.log('' + ' is falsy value'); if (!null) console.log(null + ' is falsy value'); if (!undefined) console.log(undefined + ' is falsy value');

중요: Falsy 값을 제외한 모든 값은 Truthy 값이다. 특히 빈 객체({}), 빈 배열([])은 true로 평가된다.

if ({}) console.log('빈 객체는 truthy'); if ([]) console.log('빈 배열은 truthy'); if ([1, 2, 3]) console.log('요소가 있는 배열은 truthy'); // 주의: 문자열 "false"도 Truthy 값이다 if ('false') console.log('"false" 문자열은 truthy');

9-3. 명시적 타입 변환

개발자의 의도에 따라 명시적으로 타입을 변경하는 방법은 다양하다. 표준 빌트인 생성자 함수(String, Number, Boolean)를 new 연산자 없이 호출하는 방법과 빌트인 메서드를 사용하는 방법, 그리고 앞서 살펴본 암묵적 타입 변환을 이용하는 방법이 있다.

const num = 123; const str = String(num); console.log(str); // "123" console.log(typeof str); // "string" const bool = Boolean(0); console.log(bool); // false console.log(typeof bool); // "boolean"

9-3-1. 문자열 타입으로의 변환

문자열 타입으로의 변환 방법

  • String 생성자 함수 호출
  • toString 메서드 호출
  • 암묵적 타입 변환
const num = 123; const str1 = String(num); // String 생성자 함수 호출 const str2 = num.toString(); // toString 메서드 호출 const str3 = num + ''; // 암묵적 타입 변환 console.log(str1, str2, str3); // "123" "123" "123"

각 방법의 차이점

방법null/undefined 처리특징
String(value)”null”, “undefined” 반환가장 안전하고 명시적
value.toString()TypeError 발생null/undefined에 사용 불가
value + ''”null”, “undefined” 반환간결하지만 의도가 불명확할 수 있음
// null/undefined 처리 예제 console.log(String(null)); // "null" console.log(String(undefined)); // "undefined" // console.log(null.toString()); // TypeError: Cannot read property 'toString' of null // console.log(undefined.toString()); // TypeError: Cannot read property 'toString' of undefined console.log(null + ''); // "null" console.log(undefined + ''); // "undefined"

9-3-2. 숫자 타입으로의 변환

숫자 타입으로의 변환 방법

  • Number 생성자 함수 호출
  • parseInt, parseFloat 함수 호출
  • 암묵적 타입 변환
const str = '123.45'; const num1 = Number(str); // Number 생성자 함수 호출 const num2 = parseInt(str); // parseInt 함수 호출 const num3 = parseFloat(str); // parseFloat 함수 호출 const num4 = +str; // 암묵적 타입 변환 console.log(num1, num2, num3, num4); // 123.45 123 123.45 123.45

각 방법의 차이점

방법반환 타입부분 파싱null/undefined 처리특징
Number(value)숫자X (전체만)0 / NaN엄격한 변환
parseInt(value)정수O (앞에서부터)NaN / NaN정수만 추출
parseFloat(value)소수O (앞에서부터)NaN / NaN소수 포함 추출
+value숫자X (전체만)0 / NaN간결하지만 의도 불명확
// 부분 파싱 차이 console.log(Number('123px')); // NaN (전체 변환 실패) console.log(parseInt('123px')); // 123 (숫자 부분만 추출) console.log(parseFloat('123.45px')); // 123.45 (숫자 부분만 추출) // null/undefined 처리 console.log(Number(null)); // 0 console.log(Number(undefined)); // NaN console.log(parseInt(null)); // NaN console.log(parseFloat(undefined)); // NaN // 정수 vs 소수 console.log(Number('123.45')); // 123.45 console.log(parseInt('123.45')); // 123 (정수만) console.log(parseFloat('123.45')); // 123.45

9-3-3. 불리언 타입으로의 변환

불리언 타입으로의 변환 방법

  • Boolean 생성자 함수 호출
  • 암묵적 타입 변환
const str = ''; const bool1 = Boolean(str); // Boolean 생성자 함수 호출 const bool2 = !!str; // 부정 연산자를 두 번 사용 console.log(bool1, bool2); // false false

각 방법의 차이점

방법가독성특징
Boolean(value)명시적의도가 명확함
!!value간결많이 사용되는 관용적 표현

두 방법 모두 동일하게 Falsy 값은 false로, Truthy 값은 true로 변환한다.

// 동일한 결과 console.log(Boolean(0)); // false console.log(!!0); // false console.log(Boolean('hello')); // true console.log(!!'hello'); // true // !! 연산자의 동작 원리 console.log(!'hello'); // false (첫 번째 !로 반전) console.log(!!'hello'); // true (두 번째 !로 다시 반전)

9-4. 단축 평가

단축 평가(short-circuit evaluation)란 논리 연산자 &&||가 피연산자의 타입과 상관없이 항상 불리언 값으로 평가되는 것이 아니라, 피연산자의 실제 값으로 평가되는 것을 의미한다.

console.log('Cat' || 'Dog'); // "Cat" console.log('' || 'Dog'); // "Dog" console.log('Cat' && 'Dog'); // "Dog" console.log('' && 'Dog'); // ""

위 예제에서 논리 연산자 ||&&는 피연산자의 실제 값으로 평가된다.

  • 논리 연산자 ||는 첫 번째 피연산자가 Truthy 값이면 첫 번째 피연산자의 값을 반환하고, Falsy 값이면 두 번째 피연산자의 값을 반환한다.
  • 논리 연산자 &&는 첫 번째 피연산자가 Falsy 값이면 첫 번째 피연산자의 값을 반환하고, Truthy 값이면 두 번째 피연산자의 값을 반환한다.

단축 평가를 활용하면 조건부 실행과 기본값 설정을 간결하게 표현할 수 있다.

// 조건부 실행 function printMessage(message) { message && console.log(message); } printMessage('Hello, World!'); // "Hello, World!" printMessage(''); // 아무것도 출력되지 않음 // 기본값 설정 function greet(name) { const userName = name || 'Guest'; console.log(`Hello, ${userName}!`); } greet('Alice'); // "Hello, Alice!" greet(); // "Hello, Guest!"

단축 평가 사용 시 주의사항

단축 평가는 피연산자의 실제 값을 반환하므로, 의도치 않은 결과가 발생할 수 있다.

// 숫자 0이 Falsy 값이지만 유효한 값인 경우 function getCount(count) { return count || 10; // count가 0이면 10을 반환 (의도와 다를 수 있음) } console.log(getCount(5)); // 5 console.log(getCount(0)); // 10 (0을 반환해야 하는데 10을 반환) // 이런 경우 null 병합 연산자(??)를 사용하는 것이 더 적절 function getCountSafe(count) { return count ?? 10; // count가 null이나 undefined일 때만 10을 반환 } console.log(getCountSafe(0)); // 0 (올바른 동작) console.log(getCountSafe(null)); // 10

9-4-2. 옵셔널 체이닝 연산자

옵셔널 체이닝 연산자(?.)는 좌항 피연산자가 null 또는 undefined인 경우 undefined를 반환하고, 그렇지 않으면 우항의 프로퍼티 참조를 이어간다. 이를 통해 중첩된 객체 구조에서 안전하게 프로퍼티에 접근할 수 있다.

const user = { name: 'Alice', address: { city: 'Wonderland', }, }; console.log(user.address?.city); // "Wonderland" console.log(user.address?.zip); // undefined console.log(user.contact?.email); // undefined

논리 연산자 &&는 좌항 피연산자가 false로 평가되는 Falsy 값(false, undefined, null, 0, -0, NaN, ”)이면 좌항 피연산자를 그대로 반환한다. 하지만 0이나 ”은 유효한 값으로 사용될 때도 있다.

var str = ''; var length = str && str.length; // 문자열의 길이를 참조하지 못한다. console.log(length); // ''

하지만 옵셔널 체이닝 연산자 ?.는 좌항 피연산자가 false로 평가되는 Falsy 값(false, undefined, null, 0, -0, NaN, ”)이라도 null 또는 undefined가 아니면 우항의 프로퍼티 참조를 이어간다.

var str = ''; var length = str?.length; // 문자열의 길이를 참조한다. console.log(length); // 0

9-4-3. null 병합 연산자

null 병합 연산자(??)는 좌항 피연산자가 null 또는 undefined인 경우에만 우항 피연산자의 값을 반환하고, 그렇지 않으면 좌항 피연산자의 값을 반환한다. 이를 통해 기본값을 설정할 때 유용하게 사용할 수 있다.

const foo = null ?? 'default value'; console.log(foo); // "default value" const bar = undefined ?? 'default value'; console.log(bar); // "default value" const baz = 0 ?? 'default value'; console.log(baz); // 0 const qux = '' ?? 'default value'; console.log(qux); // ""

논리 연산자 ||와 null 병합 연산자 ??의 차이:

연산자Falsy 값 처리사용 시기
||모든 Falsy 값에 대해 우항 반환Falsy 값을 모두 대체하고 싶을 때
??null/undefined만 우항 반환0, ”, false도 유효한 값일 때
// || 연산자: 모든 Falsy 값을 대체 console.log(0 || 100); // 100 console.log('' || 'default'); // "default" console.log(false || true); // true // ?? 연산자: null/undefined만 대체 console.log(0 ?? 100); // 0 console.log('' ?? 'default'); // "" console.log(false ?? true); // false // 실제 사용 예제 function setPort(port) { // port가 0일 수도 있으므로 ?? 사용 return port ?? 8080; } console.log(setPort(3000)); // 3000 console.log(setPort(0)); // 0 (포트 0도 유효한 값) console.log(setPort(null)); // 8080

실습 문제

문제 1. 객체의 형변환 - 기초편

ToPrimitive 알고리즘 이해하기

자바스크립트는 객체를 원시 타입으로 변환할 때 ToPrimitive 알고리즘을 사용합니다. 이 알고리즘은 hint(변환할 타입의 힌트)에 따라 toString()valueOf() 메서드를 다른 순서로 호출합니다.

Hint 타입과 호출 순서:

Hint호출 순서사용되는 상황
"string"toString()valueOf()String(obj), 템플릿 리터럴
"number"valueOf()toString()Number(obj), 산술 연산자(*, -, /)
"default"valueOf()toString()+ 연산자, == 비교

다음 코드의 출력 결과를 예상해보세요:

var obj = { toString: function() { return '42'; }, valueOf: function() { return 7; } }; console.log(obj + '1'); // ? console.log(obj + 1); // ? console.log(obj * 2); // ? console.log(String(obj)); // ? console.log(Number(obj)); // ?

해답 보기

출력 결과:

71 8 14 "42" 7

상세 분석:

  1. obj + '1' → “71”

    • + 연산자 + 문자열 피연산자 → hint: "default" (하지만 문자열 우선)
    • toString() 호출 → ‘42’ 반환
    • '42' + '1' = “71” (문자열 연결)
  2. obj + 1 → 8

    • + 연산자 + 숫자 피연산자 → hint: "default"
    • valueOf() 호출 → 7 반환
    • 7 + 1 = 8 (산술 연산)
  3. obj * 2 → 14

    • 곱셈 연산자 → hint: "number"
    • valueOf() 호출 → 7 반환
    • 7 * 2 = 14
  4. String(obj) → “42”

    • 명시적 문자열 변환 → hint: "string"
    • toString() 호출 → ‘42’ 반환
  5. Number(obj) → 7

    • 명시적 숫자 변환 → hint: "number"
    • valueOf() 호출 → 7 반환

문제 2. 객체의 형변환 - 실전편

Symbol.toPrimitive로 완전한 제어하기

문제 1에서는 toString()valueOf()를 별도로 정의했습니다. 하지만 Symbol.toPrimitive 메서드를 사용하면 hint 값에 따라 하나의 메서드에서 모든 변환을 제어할 수 있습니다.

우선순위: Symbol.toPrimitive > toString()/valueOf()

이제 실무에서 사용할 법한 예제로 연습해봅시다. 다음 요구사항을 만족하는 User 객체를 작성하세요:

var user = { name: 'Alice', age: 25, points: 1500, [Symbol.toPrimitive](hint) { // TODO: 구현하세요 // hint === 'string': 이름 반환 // hint === 'number': 포인트 반환 // hint === 'default': "이름 (나이세)" 형식 반환 } }; // 테스트 케이스 console.log(`사용자: ${user}`); // "사용자: Alice" console.log(user + '님 환영합니다'); // "Alice (25세)님 환영합니다" console.log(user + 500); // "Alice (25세)500" console.log(user * 2); // 3000 console.log(String(user)); // "Alice" console.log(Number(user)); // 1500

해답 보기

var user = { name: 'Alice', age: 25, points: 1500, [Symbol.toPrimitive](hint) { if (hint === 'string') { return this.name; } if (hint === 'number') { return this.points; } // hint === 'default' return `${this.name} (${this.age}세)`; } };

실행 결과:

사용자: Alice Alice (25세)님 환영합니다 Alice (25세)500 3000 Alice 1500

각 케이스 분석:

  1. `사용자: ${user}` → “사용자: Alice”

    • 템플릿 리터럴 → hint: "string"
    • this.name 반환
  2. user + '님 환영합니다' → “Alice (25세)님 환영합니다”

    • + 연산자 + 문자열 → hint: "default"
    • "Alice (25세)" 반환
  3. user + 500 → “Alice (25세)500”

    • + 연산자 (default 상황) → hint: "default"
    • "Alice (25세)" + 500 = “Alice (25세)500” (문자열 연결)
  4. user * 2 → 3000

    • 곱셈 연산자 → hint: "number"
    • this.points (1500) 반환 → 1500 * 2 = 3000
  5. String(user) → “Alice”

    • 명시적 변환 → hint: "string"
  6. Number(user) → 1500

    • 명시적 변환 → hint: "number"

핵심 포인트:

  • Symbol.toPrimitive는 단일 메서드로 모든 형변환을 제어
  • hint 값에 따라 적절한 값을 반환하여 다양한 상황에 대응
  • 더 직관적이고 유지보수하기 쉬운 코드 작성 가능

학습 정리:

  • 문제 1: toString()/valueOf() 오버라이딩으로 ToPrimitive 알고리즘의 기본 동작 이해
  • 문제 2: Symbol.toPrimitive로 hint를 직접 받아 더 세밀한 제어 구현

참고 자료:

Last updated on