LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLAB Coreteam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와 ToPrimitive 흐름

2026년 6월 28일·6분 읽기

오늘의 문제

다음 코드는 무엇을 출력할까요?

js
const point = {
  x: 3,
  y: 4,
  valueOf() {
    return 5;
  },
  toString() {
    return "(3, 4)";
  },
};

console.log(point + 1);
console.log(`${point}`);

정답은 이렇습니다.

text
6
(3, 4)

둘 다 객체를 문자열이나 숫자처럼 사용한 것처럼 보이지만, 내부에서 선택한 변환 경로가 다릅니다. point + 1은 기본 hint로 원시값을 만들고, 일반 객체에서는 보통 valueOf() 쪽이 먼저 의미를 가집니다. 템플릿 리터럴은 문자열 문맥이므로 toString() 쪽으로 가는 감각이 더 강합니다.

오늘의 핵심은 “JavaScript는 이상하게 변환한다”가 아닙니다. 코드 리뷰에서 더 실용적인 질문은 이겁니다.

text
이 값은 언제, 어떤 hint로, 어떤 primitive가 되는가?

---

흔한 착각: `+`는 숫자 덧셈이다

+는 가장 헷갈리는 연산자입니다. 숫자끼리 만나면 덧셈입니다.

js
1 + 2; // 3

하지만 둘 중 하나라도 문자열이 되면 연결입니다.

js
"1" + 2; // "12"
1 + "2"; // "12"

여기까지는 많이 압니다. 진짜 문제는 객체가 끼는 순간입니다.

js
[] + {}; // "[object Object]"
{
}
+[]; // 문맥에 따라 다르게 보일 수 있음
new Date(0) + 1; // 문자열 연결처럼 보이는 결과

이런 예제를 외우는 것은 별로 도움이 되지 않습니다. 대신 한 단계 앞을 봐야 합니다. 객체는 +에 바로 참여하지 않고 먼저 primitive로 변환됩니다. 그 결과가 문자열이면 연결, 아니면 숫자 쪽으로 갑니다.

---

ToPrimitive 흐름을 한 장으로 보기

객체가 ToPrimitive를 거쳐 + 연산자의 문자열 연결 또는 숫자 덧셈으로 갈라지는 흐름

객체가 원시값으로 바뀌는 흐름은 대략 이렇게 읽으면 됩니다.

연산이나 API가 객체에게 primitive를 요구한다. Symbol.toPrimitive가 있으면 가장 먼저 호출된다. 없으면 문맥에 따라 valueOf()와 toString()이 시도된다. primitive가 나오면 그 다음 연산 규칙이 적용된다. +에서는 결과 중 하나가 문자열이면 문자열 연결, 아니면 numeric 연산으로 간다.

중요한 점은 변환과 연산을 분리해서 읽는 것입니다.

js
const obj = {
  valueOf() {
    return 10;
  },
};

obj + 5;
// obj -> 10
// 10 + 5 -> 15
js
const obj = {
  toString() {
    return "10";
  },
};

obj + 5;
// obj -> "10"
// "10" + 5 -> "105"

같은 obj + 5라도 객체가 어떤 primitive를 반환하느냐에 따라 완전히 다른 코드가 됩니다.

---

`Symbol.toPrimitive`: 변환 지점을 직접 선언하기

Symbol.toPrimitive는 객체가 primitive로 바뀔 때 호출되는 well-known symbol입니다.

js
const price = {
  amount: 12000,
  [Symbol.toPrimitive](hint) {
    if (hint === "string") return "12,000원";
    return this.amount;
  },
};

Number(price); // 12000
`${price}`; // "12,000원"
price + 1000; // 13000

이 API를 실무에서 자주 직접 작성하진 않습니다. 그래도 알아야 하는 이유가 있습니다. 라이브러리나 도메인 객체가 “숫자처럼”, “문자열처럼” 동작할 때 변환 규칙이 객체 내부에 숨을 수 있기 때문입니다.

리뷰에서는 이런 질문을 하면 됩니다.

text
이 객체는 문자열 문맥과 숫자 문맥에서 같은 의미인가?
Symbol.toPrimitive/valueOf/toString이 비즈니스 규칙을 숨기고 있지는 않은가?

예를 들어 금액 객체가 문자열로는 "12,000원"을 반환하고 숫자로는 12000을 반환하는 것은 그럴듯합니다. 반대로 객체를 더했더니 내부 ID를 숫자로 바꾸는 식이라면, 읽는 사람이 연산 결과를 추적하기 어렵습니다.

---

폼 입력에서 자주 만나는 변환

프론트엔드에서는 객체 변환보다 폼 입력 변환이 더 자주 나옵니다.

js
Number(" "); // 0
Number(""); // 0
Number("3.14"); // 3.14
Number("3,000"); // NaN
Boolean({}); // true
Boolean([]); // true

공백 문자열이 0이 되는 이유는 숫자 변환 과정에서 공백이 trim된 뒤 빈 문자열처럼 취급되기 때문입니다. 빈 객체와 빈 배열은 Boolean 문맥에서 모두 truthy입니다. 그래서 이런 코드는 위험합니다.

js
const value = Number(input.value);

if (!value) {
  showEmptyMessage();
}

0, NaN, 빈 입력이 한꺼번에 섞입니다. 리뷰에서는 변환과 검증을 분리하는 편이 낫습니다.

js
const raw = input.value.trim();

if (raw === "") {
  showEmptyMessage();
} else {
  const value = Number(raw);
  if (Number.isNaN(value)) showInvalidNumberMessage();
}

이 코드는 길어졌지만 의도는 더 선명합니다. “비어 있음”과 “숫자로 파싱 실패”를 같은 falsey 처리에 맡기지 않습니다.

---

`+`를 리뷰할 때 보는 네 가지

1. 문자열을 만들려는 코드인가

js
const label = count + "개";

동작은 하지만 의도는 템플릿 리터럴이 더 잘 보입니다.

js
const label = `${count}개`;

단순 취향 문제가 아닙니다. 템플릿 리터럴은 “문자열을 만들고 있다”는 문맥을 코드에 남깁니다.

2. 숫자 덧셈 전에 파싱이 끝났는가

js
const total = input.value + 1000;

input.value는 문자열입니다. 여기서는 덧셈이 아니라 문자열 연결이 됩니다. 먼저 숫자 변환과 실패 처리를 분리해야 합니다.

3. 객체 변환 규칙이 숨어 있나

js
const next = money + fee;

money가 숫자인지, 금액 객체인지, BigInt인지 모르면 이 한 줄은 안전하지 않습니다. 도메인 객체라면 money.add(fee)처럼 의도를 드러내는 API가 더 나을 수 있습니다.

4. `==` 비교와 변환이 붙어 있나

Day 2에서 본 equality 문제는 변환 문제와 붙습니다.

js
if (input.value == 0) {
  // "", " ", false와 얽힐 수 있다
}

비교 전에 어떤 변환이 일어나는지 설명할 수 없다면, 비교식을 더 명시적으로 바꾸는 편이 좋습니다.

---

오늘의 정리

+를 보면 바로 덧셈으로 읽지 말고 두 단계를 나눠야 합니다.

피연산자가 primitive인지 확인한다. 객체라면 ToPrimitive 흐름을 탄다. 결과 중 하나가 문자열이면 문자열 연결이다. 그렇지 않으면 numeric 연산으로 간다. 폼 입력처럼 문자열에서 시작하는 값은 파싱/빈값/실패 처리를 분리한다.

이 흐름을 기억하면 괴상한 퀴즈를 많이 외우지 않아도 됩니다. 코드 리뷰에서 필요한 것은 “이 값이 어떤 primitive가 되는가”를 설명하는 능력입니다.

---

참고문헌

프런트엔드 레벨을 높이는 자바스크립트 퀴즈북, Chapter 01–02 타입/형변환 관련 문제와 진규의 Notion 복습 노트 MDN Web Docs, Symbol.toPrimitive와 type conversion 관련 문서 ECMAScript Language Specification, ToPrimitive / Addition operator / Abstract Operations

---

퀴즈

Quiz1 / 4
Q.다음 코드의 결과로 맞는 것은 무엇일까요?
js
"const obj = { valueOf() { return 2;

Post Q&A

오케이징에게 물어보기

자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와 ToPrimitive 흐름 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/javascript-quizbook
파일 13개, 폴더 0개
자바스크립트 퀴즈북 리마인드 Day 1: 숫자는 왜 가끔 믿을 수 없을까자바스크립트 퀴즈북 리마인드 Day 2: 같은 값인지 묻는 네 가지 방법자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와 ToPrimitive 흐름자바스크립트 퀴즈북 리마인드 Day 4: 프로퍼티 descriptor와 freeze의 경계자바스크립트 퀴즈북 리마인드 Day 5: 참조, 복사, Map/Set의 메모리 감각자바스크립트 퀴즈북 리마인드 Day 6: 스코프 체인과 호이스팅자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된 값자바스크립트 퀴즈북 리마인드 Day 8: 이벤트 루프와 Promise 타이밍자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이 정한다자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다자바스크립트 퀴즈북 리마인드 Day 11: class를 봐도 프로토타입을 읽는다자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는 target에서 끝나지 않는다자바스크립트 퀴즈북 리마인드 Day 13: 모듈은 파일 묶음이 아니라 의존성 그래프다

같은 섹션의 대표 이미지

13 posts · latest first
ESM live binding과 dynamic import, circular dependency 리뷰 포인트를 보여주는 module graph 다이어그램
Study26. 07. 09.

자바스크립트 퀴즈북 리마인드 Day 13: 모듈은 파일 묶음이.

ESM live binding, default/named export, circular dependency, dynamic import, CJS와 ESM 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 09.SEOJing
DOM 이벤트가 capture 단계로 내려가고 bubble 단계로 올라가는 경로 다이어그램
Study26. 07. 08.

자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는.

DOM 이벤트의 capture/bubble, delegation, default action, React synthetic event 경계를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 08.SEOJing
인스턴스에서 프로토타입과 Object.prototype으로 이어지는 프로퍼티 조회 경로 다이어그램
Study26. 07. 07.

자바스크립트 퀴즈북 리마인드 Day 11: class를 봐도.

프로토타입 조회, class 문법, 인스턴스 필드와 prototype 메서드 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 07.SEOJing
입력, 순수 계산, 출력, 부수 효과 경계를 나누어 함수 설계를 보여주는 다이어그램
Study26. 07. 06.

자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다.

고차 함수, 순수 함수, 부수 효과, async 함수의 Promise 반환을 코드 리뷰에서 어떻게 읽을지 정리합니다.

26. 07. 06.SEOJing
메서드 호출, 분리된 함수, 이벤트 콜백, bind와 arrow function의 this 차이를 보여주는 다이어그램
Study26. 07. 05.

자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이.

this 바인딩을 정의 위치가 아니라 호출 형태, 메서드 분리, arrow function, bind 기준으로 읽는 방법을 코드 리뷰 관점에서 정리합니다.

26. 07. 05.SEOJing
콜 스택이 비워진 뒤 마이크로태스크 큐가 먼저 실행되고 다음 태스크로 넘어가는 이벤트 루프 다이어그램
Study26. 07. 04.

자바스크립트 퀴즈북 리마인드 Day 8: 이벤트 루프와.

콜 스택, 태스크 큐, 마이크로태스크 큐를 기준으로 Promise와 setTimeout 실행 순서를 코드 리뷰 관점에서 정리합니다.

26. 07. 04.SEOJing
클로저가 생성 위치의 바인딩을 기억해 나중 실행에서 오래된 값을 읽을 수 있음을 보여주는 다이어그램
Study26. 06. 29.

자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된.

클로저가 값 복사가 아니라 렉시컬 환경 참조라는 점을 stale closure, 루프 콜백, React 이벤트 리뷰 관점에서 정리합니다.

26. 06. 29.SEOJing
스코프 체인과 호이스팅 관계를 단순화한 다이어그램
Study26. 06. 28.

자바스크립트 퀴즈북 리마인드 Day 6: 스코프 체인과 호이스팅.

스코프 체인, 렉시컬 환경, var/let/const 호이스팅 차이를 콜백·상태 버그 리뷰 관점에서 다시 정리합니다.

26. 06. 28.SEOJing
JavaScript 객체 참조와 Map, Set, WeakMap의 참조 구조를 비교한 다이어그램
Study26. 06. 27.

자바스크립트 퀴즈북 리마인드 Day 5: 참조, 복사,.

객체 참조와 얕은 복사, Object와 Map/Set의 차이, WeakMap/WeakSet이 필요한 메모리 상황을 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 27.SEOJing
JavaScript 프로퍼티 descriptor가 data descriptor와 accessor descriptor로 갈라지는 구조 다이어그램
Study26. 06. 26.

자바스크립트 퀴즈북 리마인드 Day 4: 프로퍼티.

객체 프로퍼티를 key/value가 아니라 descriptor로 읽는 법, getter/setter와 defineProperty, preventExtensions/seal/freeze의 경계를 코드 리뷰 관점에서 정리합니다.

26. 06. 26.SEOJing
객체가 ToPrimitive를 거쳐 + 연산자의 문자열 연결 또는 숫자 덧셈으로 갈라지는 흐름 다이어그램
Study26. 06. 25.

자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와.

객체가 원시값으로 바뀌는 ToPrimitive 흐름, + 연산자의 문자열 연결과 숫자 덧셈 분기, Symbol.toPrimitive가 코드 리뷰에서 왜 중요한지 정리합니다.

26. 06. 25.SEOJing
==, ===, Object.is, SameValueZero의 차이를 정리한 JavaScript equality 다이어그램
Study26. 06. 24.

자바스크립트 퀴즈북 리마인드 Day 2: 같은 값인지 묻는 네.

==, ===, Object.is, SameValueZero가 각각 어떤 비교 알고리즘을 쓰는지 정리하고, includes와 indexOf, Map/Set 키 비교에서 생기는 프론트엔드 리뷰 포인트를 잡습니다.

26. 06. 24.SEOJing
JavaScript Number, safe integer, NaN, -0, BigInt를 한 장으로 정리한 다이어그램
Study26. 06. 23.

자바스크립트 퀴즈북 리마인드 Day 1: 숫자는 왜 가끔 믿을.

자바스크립트의 Number가 왜 정수처럼 보여도 부동소수점 모델 위에서 움직이는지, NaN과 -0, safe integer, BigInt를 프론트엔드 코드 리뷰 관점에서 다시 정리합니다.

26. 06. 23.SEOJing