Skip to content
This page has been auto-translated and may contain errors.View in English

연산자와 비교

docs.scrimba.com

쇼핑 카트를 관리하는 상황을 생각해봅시다. 가격을 더하고, 할인을 적용하고, 총액이 무료 배송 기준을 넘었는지 확인하고, 카트가 비어 있을 때 경고를 보여줄지 결정합니다. 이 모든 과정마다 연산자가 작동하고 있습니다. 연산자는 하나 또는 두 개의 값을 받아 새로운 값을 만들어내는 기호입니다. 이 장에서는 숫자를 계산하는 것부터 값을 비교하고 참·거짓 판단을 조합하는 것까지, 늘 사용하게 될 연산자들을 다룹니다.

산술 연산자와 대입 연산자

**산술 연산자**는 이미 알고 있는 계산을 그대로 수행합니다. +는 더하고, -는 빼고, *는 곱하고, /는 나눕니다. 하나 더 낯설 수 있는 연산자가 있는데, 나눗셈의 나머지를 구하는 %(나머지 연산자)입니다.

js
const total = 8 + 5; // 13
const change = 20 - 13; // 7
const leftover = 17 % 5; // 2, 5가 17에 세 번 들어가고 2가 남음
console.log(total); // 13

어떤 값을 자기 자신을 기준으로 갱신하고 싶을 때 쓸 수 있는 축약형이 있습니다. score = score + 10이라고 쓰는 대신 score += 10이라고 쓸 수 있습니다. 의미는 같습니다. 현재 값을 가져와 10을 더한 다음 그 결과를 다시 저장하는 것입니다.

js
let score = 100;
score += 10; // score는 이제 110
score -= 30; // score는 이제 80
console.log(score); // 80

숫자를 정확히 1만큼 올리거나 내리고 싶다면 ++--로 한 번에 처리할 수 있습니다. count++는 1을 더하고, count--는 1을 뺍니다.

js
let lives = 3;
lives--; // lives는 이제 2
console.log(lives); // 2

**산술 연산자**는 +, -, *, /, %입니다. 앞의 네 개는 익숙하고, %는 한 번 더 살펴볼 만합니다. 이 연산자는 나눗셈의 나머지를 반환하기 때문에 "이 값이 나누어지는가?"를 확인하거나 값을 어떤 범위 안에서 순환시키는 데 표준적으로 쓰이는 도구입니다.

js
const isEven = 10 % 2; // 0, 그래서 10은 짝수
const isOdd = 7 % 2; // 1, 그래서 7은 홀수
console.log(isEven); // 0

어떤 값을 자기 자신을 기준으로 갱신하는 일은 흔해서 축약형이 따로 있습니다. 각 산술 연산자는 =와 결합해 복합 대입 연산자를 만듭니다: +=, -=, *=, /=, %=. 즉 price *= 1.2는 "price에 1.2를 곱해서 다시 저장하라"는 뜻이며, price = price * 1.2라고 쓰는 수고를 덜어줍니다.

js
let price = 40;
price *= 1.2; // 20퍼센트를 더함, price는 이제 48
price -= 8; // price는 이제 40
console.log(price); // 40

정확히 1만큼 변화를 줄 때는 ++--가 그 자리에서 값을 증가시키거나 감소시킵니다. 카운터를 한 단계씩 진행할 때 자연스럽게 읽히며, 앞으로 계속 마주치게 될 것입니다.

js
let attempts = 0;
attempts++; // attempts는 이제 1
console.log(attempts); // 1

**산술 연산자**의 네 가지 기본 연산에는 놀랄 만한 부분이 거의 없으므로, 정말 눈여겨봐야 할 것은 %+의 중복 의미(오버로드)입니다. 나머지 연산자 %는 정수 나눗셈 후 남는 값을 반환하며, 왼쪽 피연산자의 부호를 그대로 유지합니다. -7 % 32가 아니라 -1이 되는데, 수학적인 모듈로 연산을 기대하는 사람들이 흔히 이 점에서 걸립니다. 이 연산자는 나누어짐을 확인하거나 인덱스를 범위 안에서 순환시킬 때 자주 쓰입니다.

js
const remainder = 17 % 5; // 2
const negative = -7 % 3; // -1, 부호는 왼쪽 피연산자를 따름
console.log(negative); // -1

+ 연산자는 중복된 의미를 갖습니다. 양쪽이 모두 숫자면 더하지만, 어느 한쪽이라도 문자열이면 다른 쪽을 문자열로 변환한 뒤 이어붙입니다. 이 하나의 규칙 때문에 온갖 예상치 못한 결과가 생기므로, 더하기 전에 다루는 값이 무엇인지 먼저 확인해야 합니다.

js
const sum = 5 + 3; // 8
const glued = "5" + 3; // "53", 3이 문자열이 됨
console.log(glued); // 53

복합 대입 연산자(+=, -=, *=, /=, %=)는 어떤 값을 자기 자신을 기준으로 갱신하는 관용적인 방법이며, +와 같은 중복 의미를 그대로 물려받습니다. 즉 msg += countcount를 문자열로 변환합니다. 증가·감소 연산자 ++--는 전위형과 후위형이 있는데, 표현식이 반환하는 값이 서로 다릅니다. x++는 이전 값을 평가한 뒤 증가시키고, ++x는 먼저 증가시킨 뒤 새로운 값을 평가합니다. 표현식 안에서는 이 차이가 문제를 일으킬 수 있으므로, 차이가 중요하지 않은 상황에서는 따로 독립된 문장으로 사용하는 것이 좋습니다. 이런 방식으로 재할당할 값에는 let을 사용하세요. const로 선언한 값은 갱신할 수 없습니다.

js
let counter = 5;
const before = counter++; // before는 5, counter는 이제 6
const after = ++counter; // after는 7, counter는 이제 7
console.log(`${before} ${after}`); // 5 7
Juno산술 연산자와 대입 연산자 수학 연산자는 예상하는 그대로 동작하고, 나눗셈의 나머지를 구하는 %만 추가됩니다. 어떤 값을 자기 자신을 기준으로 갱신할 때는 축약형을 쓰세요: score = score + 10 대신 score += 10입니다. 그리고 ++--는 숫자를 1만큼 올리거나 내립니다. 처음엔 익숙해지는 데 시간이 걸렸지만, 결국 같은 계산을 더 짧게 쓴 것일 뿐입니다.
Juno산술 연산자와 대입 연산자 산술 연산자는 다섯 개고, 나머지를 구하는 %는 나누어짐을 확인할 때 제 몫을 합니다. price *= 1.2 같은 복합 대입 연산자는 값을 자기 자신을 기준으로 한 번에 갱신하고, ++--는 1씩 움직입니다. 이런 방식으로 갱신할 값에는 let을 쓰세요, const는 재할당할 수 없으니까요.
Juno산술 연산자와 대입 연산자 정신을 바짝 차려야 할 두 가지: %는 왼쪽 피연산자의 부호를 유지하므로 -7 % 3-1이고, +는 한쪽이라도 문자열이면 즉시 이어붙이기로 바뀝니다. 전위형과 후위형 ++는 서로 다른 값을 반환하니, 문제가 되지 않는 곳에서는 독립된 문장으로 써야 합니다. 나머지 산술 연산자들은 예상하는 그대로 동작합니다.

비교 연산자

**비교**는 두 값에 대해 질문을 던지고 true 또는 false로 답합니다. 이 참·거짓 답이 바로 불리언이고, 프로그램은 이 값을 이용해 이후에 어떤 결정을 내립니다.

가장 자주 쓰게 될 비교는 ===로, "이 두 값이 정확히 같은가?"를 묻습니다.

js
const answer = 42;
console.log(answer === 42); // true
console.log(answer === 7); // false

그 반대는 !==로 "이 두 값이 다른가?"를 묻습니다. 순서를 비교할 때는 <, >, <=, >=가 있으며, 수학에서와 똑같이 작동합니다. 작다, 크다, 작거나 같다, 크거나 같다는 의미입니다.

js
const age = 18;
console.log(age >= 18); // true
console.log(age !== 21); // true

비교는 항상 ===로 하고, ==는 쓰지 마세요. 등호 두 개짜리 버전에는 사람들을 놀라게 하는 특이한 동작이 있는 반면, 등호 세 개짜리 버전은 매번 예상한 대로 동작합니다.

**비교**는 두 값을 받아 불리언, 즉 true 또는 false를 반환합니다. 이 불리언이 바로 조건문에서 작성하게 될 결정의 원재료이므로, 비교를 정확히 다루는 것이 중요합니다.

동등 비교에는 ===를, 부등 비교에는 !==를 사용합니다. 순서 비교에는 <, >, <=, >=가 있습니다.

js
const stock = 3;
console.log(stock === 0); // false
console.log(stock > 0); // true
console.log(stock <= 5); // true

== 대신 ===를 써야 하는 이유는 이 둘이 같은 연산자가 아니기 때문입니다. ===(엄격한 동등 비교)는 값과 타입을 함께 비교해서, 둘 다 일치할 때만 true를 반환합니다. ==(느슨한 동등 비교)는 먼저 양쪽을 같은 타입으로 변환한 다음 비교하는데, 그 결과는 거의 아무도 예측하지 못하는 방향으로 나옵니다.

js
console.log(0 == ""); // true, 원하는 결과가 아닐 때가 많음
console.log("1" == 1); // true, 문자열이 숫자로 바뀜
console.log("1" === 1); // false, 타입이 다름

언제나 기본으로 ===!==를 사용하세요. 이 연산자들은 변환 없이 비교하므로 변환 규칙을 신경 쓸 필요가 없습니다. ==는 새 코드를 작성할 때가 아니라 옛 코드를 읽을 때를 위해 남겨두세요.

**비교**는 불리언을 반환하며, 연산자는 두 그룹으로 나뉩니다. 동등 비교(===, !==)와 관계 비교(<, >, <=, >=)입니다. 관계 비교는 숫자에서는 단순합니다. 정작 중요한 결정이 필요한 곳은 동등 비교인데, 자바스크립트가 두 가지 방식을 제공하기 때문입니다.

===(엄격한 동등 비교)는 변환 없이 두 피연산자가 타입과 값을 모두 공유할 때만 true를 반환합니다. ==(느슨한 동등 비교)는 추상 동등 알고리즘을 실행해서 피연산자들을 먼저 같은 타입으로 강제 변환하는데, 옛 코드를 읽으려면 알아둘 만한 실용적인 규칙들이 있습니다. 숫자와 문자열을 비교하면 문자열이 숫자로 변환됩니다("1" == 1true). 불리언을 무언가와 비교하면 불리언이 먼저 숫자로 변환됩니다(true == 1, false == 0). null == undefinedtrue지만 둘 다 다른 어떤 값과도 같지 않습니다. 그리고 NaN은 그 무엇과도 같지 않으며, 심지어 자기 자신과도 같지 않습니다.

js
console.log(0 == ""); // true, ""가 0이 됨
console.log("1" == 1); // true, "1"이 1이 됨
console.log(null == undefined); // true, 특수한 경우
console.log(NaN === NaN); // false, NaN은 결코 무엇과도 같지 않음

===!==를 사용하고, ==는 읽기 위한 지식으로만 다루세요. 여기서 얻어야 할 결론은 변환 경로를 모두 외우는 것이 아니라 그것을 피하는 것입니다. 엄격한 동등 비교는 변환이 전혀 없으므로 결과가 피연산자 그대로 읽힙니다. ==가 그나마 언급할 가치가 있는 곳은 null == undefined 검사인데, 일부 코드베이스에서는 이를 이용해 두 널 관련 값을 한 번에 걸러내기도 하지만, 이 장 뒤에 나오는 널리시 연산자가 그 역할을 더 명확하게 해냅니다.

Juno비교 비교는 두 값에 대해 예/아니오 질문을 던지고 truefalse로 답합니다. 동등 비교에는 ===를, "다르다"에는 !==를, 순서 비교에는 <, >, <=, >=를 씁니다. ===를 지키고 ==는 건너뛰세요. 등호 두 개짜리는 놀라운 함정이 있지만, 세 개짜리는 얌전하게 동작합니다.
Juno비교 모든 비교는 불리언을 건네주고, 그것이 곧 다음에 작성할 결정의 원동력이 됩니다. ===는 값과 타입을 함께 확인하고, ==는 양쪽을 먼저 변환해 0 == ""true가 되는 것 같은 이상한 결과를 만듭니다. 기본으로 ===!==를 써서 변환을 신경 쓸 일이 없게 하고, ==는 옛 코드를 읽을 때만 남겨두세요.
Juno비교 엄격한 동등 비교는 강제 변환 없이 타입과 값을 비교하므로 결과가 피연산자 그대로 읽힙니다. 느슨한 ==는 뒤에서 몰래 변환 알고리즘을 돌리는데, 그래서 "1" == 1true가 되고 NaN은 아무것과도 같지 않게 됩니다. ===!==를 쓰고, 강제 변환 규칙은 옛 코드를 읽기 위한 지식으로만 남겨두세요.

논리 연산자와 참·거짓 판별

한 번에 여러 비교를 결합해야 할 때가 자주 있습니다. **논리 연산자**가 바로 그 역할을 합니다: &&(그리고), ||(또는), !(부정)입니다.

&&는 양쪽이 모두 true일 때만 true입니다. ||는 양쪽 중 하나라도 true이면 true입니다. !는 불리언을 반대로 뒤집습니다.

js
const age = 20;
const hasTicket = true;
console.log(age >= 18 && hasTicket); // true, 둘 다 true
console.log(age < 13 || hasTicket); // true, 한쪽만 true면 충분
console.log(!hasTicket); // false, 뒤집힘

이와 관련된 개념으로 **참(truthy)과 거짓(falsy)**이 있습니다. 참·거짓을 판별하는 상황에서 자바스크립트는 불리언이 아닌 일부 값도 false처럼 취급합니다. 거짓으로 취급되는 값은 false, 0, ""(빈 문자열), null, undefined, NaN입니다. 그 외의 모든 값은 참으로 취급됩니다.

js
const name = "";
console.log(!name); // true, 빈 문자열은 거짓으로 취급됨

**논리 연산자**는 불리언 값을 결합합니다. &&(그리고)는 두 피연산자가 모두 true일 때만 true이고, ||(또는)는 적어도 하나가 true이면 true이며, !(부정)는 단일 불리언을 반전시킵니다.

js
const loggedIn = true;
const isAdmin = false;
console.log(loggedIn && isAdmin); // false, 둘 다 true여야 함
console.log(loggedIn || isAdmin); // true, 하나면 충분
console.log(!isAdmin); // true

이 연산자들은 진짜 불리언에서 멈추지 않는데, 자바스크립트가 모든 값을 참(truthy) 또는 **거짓(falsy)**으로 분류하기 때문입니다. 거짓 값 집합은 짧아서 외워둘 만합니다: false, 0, "", null, undefined, NaN. 그 외의 모든 값은 참이며, "0", "false", 비어 있지 않은 모든 문자열도 여기 포함됩니다. 이 덕분에 !username 같은 표현으로 "사용자 이름이 입력되지 않았다"는 의미를 나타낼 수 있는 것입니다.

js
const username = "";
console.log(!username); // true, ""는 거짓으로 취급됨
console.log(Boolean("hello")); // true, 비어 있지 않은 문자열은 참으로 취급됨

**논리 연산자**는 &&, ||, !이며, 핵심은 &&||가 불리언을 반환하지 않는다는 점입니다. 이들은 **단축 평가(short-circuit)**를 합니다. 왼쪽 피연산자를 먼저 평가하고, 필요할 때만 오른쪽을 평가한 뒤, 강제 변환된 truefalse가 아니라 실제 피연산자 중 하나를 그대로 반환합니다.

a || ba가 참이면 a를, 그렇지 않으면 b를 반환합니다. a && ba가 거짓이면 a를, 그렇지 않으면 b를 반환합니다. 이 동작의 기반이 바로 거짓 값 집합입니다: false, 0, "", null, undefined, NaN이 여섯 가지 거짓 값이고, 그 외의 모든 값은 참입니다.

js
const name = "";
const display = name || "게스트"; // "게스트", ""는 거짓이므로
const label = "소피아" && "활성"; // "활성", 왼쪽이 참이므로 오른쪽을 반환
console.log(display); // 게스트

||로 기본값을 지정하는 방식에는 위험한 함정이 있는데, 그래서 ??(널 병합 연산자)가 존재합니다. ||는 왼쪽이 어떤 거짓 값이든 대체값을 반환하므로, 0 || 1010이고 "" || "n/a""n/a"가 됩니다. 0이나 ""가 실제로 유지하고 싶었던 유효한 입력값이었더라도 말입니다. ??는 왼쪽이 null이거나 undefined일 때만 대체값을 반환하므로 0이나 ""도 정상적인 값으로 취급합니다.

js
const count = 0;
console.log(count || 5); // 5, 하지만 0은 실제 값이었음
console.log(count ?? 5); // 0, ??는 null이나 undefined일 때만 대체함

조건에 따라 두 값 중 하나를 선택해야 할 때는 완전한 비교와 삼항 연산자(조건 ? 참일때 : 거짓일때)를 조합한 한 줄짜리 표현이 있습니다. 예를 들면 age >= 18 ? "adult" : "minor"입니다. 이는 미리 살짝 맛보는 것일 뿐이며, 조건문 장에서 이것과 if 문을 자세히 다룹니다.

Juno논리 연산자와 참·거짓 판별&&는 "둘 다", ||는 "둘 중 하나", !는 불리언을 뒤집습니다. 여기에 더해 자바스크립트는 일부 값을 거짓처럼 취급합니다: false, 0, "", null, undefined, NaN이 거짓 값이고, 그 외는 모두 참입니다. 이 덕분에 !name이 "이름이 입력되지 않았다"는 의미가 될 수 있는 것입니다.
Juno논리 연산자와 참·거짓 판별&&는 양쪽이 모두 참이어야 하고, ||는 하나만 참이면 되고, !는 뒤집습니다. 모든 값은 참이거나 거짓인데, 거짓 값 집합은 외우기 쉬울 만큼 작습니다: false, 0, "", null, undefined, NaN. "0""false"는 문자열이라 둘 다 참으로 취급된다는 점을 조심하세요.
Juno논리 연산자와 참·거짓 판별&&||는 단축 평가를 하며 불리언이 아니라 피연산자 자체를 반환합니다. 그래서 name || "Guest"가 기본값 설정으로 동작하는 것입니다. 이 방식에는 함정이 있는데, ||는 모든 거짓 값을 대체하므로 0""를 유지하고 싶은 입력값이라면 ??를 쓰세요. 여섯 가지 거짓 값이 전부이니, 그 집합만 알면 나머지는 자연스럽게 따라옵니다.

연산자, 그다음은 무엇일까요

연산자는 이 언어의 동사와 같습니다. 저장해둔 값을 받아 결과와 결정으로 바꿔줍니다. 산술 연산자는 새로운 숫자를 만들고, 비교 연산자는 불리언을 만들고, 논리 연산자는 그 불리언들을 조합해 프로그램이 행동할 예/아니오 답을 만들어냅니다.

이 불리언 값들은 그 자체로는 큰 역할을 하지 못합니다. 다음 단계는 이 값을 이용해 코드의 흐름을 조종하는 것입니다. 검사가 통과하면 한쪽 분기를 실행하고 실패하면 다른 쪽을 실행하는 방식인데, 이것이 바로 조건문 장에서 다루는 내용입니다. 여기서 등장한 값들이 조금 헷갈렸다면, 데이터 타입 장에서 숫자, 문자열, 불리언을 제대로 다룹니다.