본문 바로가기

변수, 데이터 타입, 연산자

let·const로 값을 저장하고 원시·객체 타입을 구분하며 산술·비교·논리 연산자로 데이터를 처리합니다.

웹 페이지를 보여 주기만 하는 데서 나아가 사용자의 행동에 반응하게 하려면 자바스크립트(JavaScript)가 필요합니다. 자바스크립트는 브라우저가 실행하는 프로그래밍 언어로, 화면에 동작과 반응을 더합니다. 프로그래밍에서 다루는 정보를 데이터라고 하며, 데이터를 저장하고 종류를 나누고 계산하는 방법을 아는 것이 자바스크립트를 배우는 출발점입니다.

5장에서는 자바스크립트의 기초를 다룹니다. 이 글에서는 데이터를 다루는 가장 기본적인 단위인 변수(variable), 데이터 타입(data type), 연산자(operator)를 살펴봅니다.

JavaScript binding·값·타입·연산 흐름 — 상태 추적

원시값 복사 후 b=4와 객체 참조 공유 후 b.n=4에서 a 관찰값이 달라진다. 동일한 a/b 고정 칸과 객체 동일성을 보존한 변경 전후가 유용하다.

값의 재할당과 객체 속성 변경은 다르다

VALUES · BINDINGS · OPERATORS

값의 재할당과 객체 속성 변경은 다르다

let은 이름에 연결된 값을 다시 바꿀 수 있다. const는 재할당을 막지만 객체 속성까지 동결하지 않는다. 대입한 값의 종류에 따라 다른 이름에 보이는 영향이 달라진다.

원시값 · 다른 변수의 재할당

let a = 3;
let b = a;
b = 4;
// a: 3, b: 4

b에 새 값을 할당해도 a에 연결된 값은 바뀌지 않는다.

객체 · 같은 참조로 속성 변경

const a = { n: 3 };
const b = a;
b.n = 4;
// a.n: 4, b.n: 4

두 이름이 같은 객체를 참조하므로 속성 변경이 양쪽에서 보인다.

원시값의 재할당과 객체 참조 공유원시값의 a는 3, 재할당한 b는 4이다. 객체 예시의 a와 b 화살표는 n이 4인 같은 객체 하나로 모인다. 두 예시 사이에는 화살표가 없다.b = 4 뒤 · 원시값a = 3b = 4재할당은 서로에게 영향 없음b.n = 4 뒤 · 공유 객체ab같은 객체n = 4
원시값의 재할당과 객체 참조 공유원시값의 a는 3, 재할당한 b는 4이다. 객체 예시의 a와 b 화살표는 n이 4인 같은 객체 하나로 모인다. 두 예시 사이에는 화살표가 없다.b = 4 뒤 · 원시값a = 3b = 4재할당 영향 없음b.n = 4 뒤 · 공유 객체ab같은 객체n = 4

+ 연산 · 덧셈 또는 문자열 연결

5 + 3 → 8, "5" + 3 → "53". 의도한 수치 계산이라면 입력을 명시적으로 변환하고 변환 결과도 확인한다.

=== 비교 · 타입 변환 없음

10 === "10" → false. Number("10") → 10과 Boolean("") → false는 별도로 값을 변환하는 연산이다.

const는 바인딩의 재할당을 막는다

위 객체 예시에서 b.n = 4는 가능하지만 b = 다른객체는 TypeError다. 원시값은 불변이며, 그림은 이름과 값의 의미 관계를 보여 주고 엔진의 실제 메모리 배치를 지정하지 않는다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
값 복사a: 3 / b: 3a=3을 b에 복사한다원시 값을 대입하면 독립된 값이 된다
b 변경a: 3 / b: 4b를 4로 바꿔도 a는 3이다b의 값 변경은 a를 수정하지 않는다
객체 별칭 비교a.n: 4 / b.n: 4같은 객체 참조의 b.n을 4로 쓰면 a.n도 4다별도 객체 사례의 공유 참조는 원시 값 복사와 다르다

JavaScript binding·값·타입·연산 흐름 — 상태 추적

원시값 복사 후 b=4와 객체 참조 공유 후 b.n=4에서 a 관찰값이 달라진다. 동일한 a/b 고정 칸과 객체 동일성을 보존한 변경 전후가 유용하다.

값의 재할당과 객체 속성 변경은 다르다

VALUES · BINDINGS · OPERATORS

값의 재할당과 객체 속성 변경은 다르다

let은 이름에 연결된 값을 다시 바꿀 수 있다. const는 재할당을 막지만 객체 속성까지 동결하지 않는다. 대입한 값의 종류에 따라 다른 이름에 보이는 영향이 달라진다.

원시값 · 다른 변수의 재할당

let a = 3;
let b = a;
b = 4;
// a: 3, b: 4

b에 새 값을 할당해도 a에 연결된 값은 바뀌지 않는다.

객체 · 같은 참조로 속성 변경

const a = { n: 3 };
const b = a;
b.n = 4;
// a.n: 4, b.n: 4

두 이름이 같은 객체를 참조하므로 속성 변경이 양쪽에서 보인다.

원시값의 재할당과 객체 참조 공유원시값의 a는 3, 재할당한 b는 4이다. 객체 예시의 a와 b 화살표는 n이 4인 같은 객체 하나로 모인다. 두 예시 사이에는 화살표가 없다.b = 4 뒤 · 원시값a = 3b = 4재할당은 서로에게 영향 없음b.n = 4 뒤 · 공유 객체ab같은 객체n = 4
원시값의 재할당과 객체 참조 공유원시값의 a는 3, 재할당한 b는 4이다. 객체 예시의 a와 b 화살표는 n이 4인 같은 객체 하나로 모인다. 두 예시 사이에는 화살표가 없다.b = 4 뒤 · 원시값a = 3b = 4재할당 영향 없음b.n = 4 뒤 · 공유 객체ab같은 객체n = 4

+ 연산 · 덧셈 또는 문자열 연결

5 + 3 → 8, "5" + 3 → "53". 의도한 수치 계산이라면 입력을 명시적으로 변환하고 변환 결과도 확인한다.

=== 비교 · 타입 변환 없음

10 === "10" → false. Number("10") → 10과 Boolean("") → false는 별도로 값을 변환하는 연산이다.

const는 바인딩의 재할당을 막는다

위 객체 예시에서 b.n = 4는 가능하지만 b = 다른객체는 TypeError다. 원시값은 불변이며, 그림은 이름과 값의 의미 관계를 보여 주고 엔진의 실제 메모리 배치를 지정하지 않는다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
값 복사a: 3 / b: 3a=3을 b에 복사한다원시 값을 대입하면 독립된 값이 된다
b 변경a: 3 / b: 4b를 4로 바꿔도 a는 3이다b의 값 변경은 a를 수정하지 않는다
객체 별칭 비교a.n: 4 / b.n: 4같은 객체 참조의 b.n을 4로 쓰면 a.n도 4다별도 객체 사례의 공유 참조는 원시 값 복사와 다르다

위 그림은 이 글의 핵심 두 가지를 보여 줍니다. 왼쪽은 원시값의 경우로, let b = a;로 b에 a의 값 3을 넣은 뒤 b = 4;로 다시 대입해도 a는 3 그대로입니다. 오른쪽은 객체의 경우로, const b = a;로 두 이름이 같은 객체를 가리키게 한 뒤 b.n = 4;로 속성을 바꾸면 a.n도 4가 됩니다. 두 화살표가 객체 하나로 모이는 것은 두 이름이 같은 객체를 함께 가리키기 때문입니다.

그림 아래쪽은 연산에서 자주 헷갈리는 두 가지를 정리합니다. +는 숫자끼리는 더하지만 한쪽이 문자열이면 이어 붙여 "5" + 3이 "53"이 되고, ===는 타입을 바꾸지 않고 비교하므로 10 === "10"이 false입니다. 마지막 칸의 설명처럼 const는 이름에 다른 값을 다시 대입하는 것만 막을 뿐 객체의 속성 변경까지 막지는 않으며, 이 그림은 이름과 값의 관계를 나타낼 뿐 엔진이 메모리를 실제로 어떻게 배치하는지를 뜻하지는 않습니다.


변수: 값에 이름 붙이기

이 글의 예제는 브라우저 개발자 도구의 Console 패널에서 실행할 수 있습니다. console.log()는 값을 개발자 도구에 출력할 뿐, 페이지의 HTML을 바꾸지는 않습니다. 앞 예제에서 선언한 변수가 Console에 남아 있으면 결과가 헷갈릴 수 있으므로, 예제마다 페이지를 새로고침한 뒤 실행하세요. 앞 예제에서 선언한 값을 쓰는 예제는 그 선언부터 함께 실행합니다.

요리할 때 설탕과 소금을 각자의 통에 담아 두듯, 프로그램도 데이터를 담아 둘 곳이 필요합니다. 이때 쓰는 것이 변수입니다. 처음에는 변수를 값을 읽고 쓰는 이름이라고 생각하면 됩니다. 정확히 말하면 변수는 이름과 값을 이어 주는 바인딩(binding)이며, 통에 담는다는 비유가 엔진의 메모리 구조를 그대로 뜻하지는 않습니다. 변수를 선언하고 값을 넣으면 그 이름으로 값을 읽을 수 있고, 새 값을 다시 넣을 수 있는지는 선언에 쓴 키워드가 정합니다. 이 교재는 let과 const를 씁니다.

let: 다시 대입할 수 있는 변수

let으로 선언한 변수에는 나중에 다른 값을 다시 넣을(재할당할) 수 있습니다. 다음 코드는 같은 변수에 값을 두 번 넣고, 넣을 때마다 console.log()로 현재 값을 출력합니다. 두 번째 대입에는 let을 다시 쓰지 않는다는 점을 보세요. 같은 범위에서 let userName을 한 번 더 쓰면 이미 선언된 이름이라는 오류가 납니다.

let userName = "김철수"; // userName이라는 변수를 선언하고 "김철수"라는 문자열을 할당했습니다.
console.log(userName);   // 결과: 김철수

userName = "이영희";    // userName 변수에 새로운 값 "이영희"를 다시 할당했습니다.
console.log(userName);   // 결과: 이영희

userName은 처음에 “김철수”였다가 나중에 “이영희”로 바뀝니다. 이처럼 let은 프로그램이 실행되는 동안 값이 바뀌어야 하는 변수에 씁니다.

const: 다시 대입하지 않는 변수

const는 이름과 값의 연결을 다시 바꿀 수 없게 선언합니다. 다른 값을 대입할 수 없으므로, 나중에 실수로 값을 덮어쓰는 일을 막고 이 값이 바뀌지 않는다는 의도를 드러냅니다. 원주율이나 고정된 메시지, 파일 경로처럼 바뀔 일이 없는 값에 씁니다.

const PI = 3.141592; // PI라는 상수를 선언하고 원주율 값을 할당했습니다.
console.log(PI);     // 결과: 3.141592

// PI = 3.14; // TypeError: const로 선언한 변수에는 다시 대입할 수 없습니다.

다만 const는 이름에 다시 대입하는 것만 막습니다. 연결된 값이 객체라면, 그 객체의 속성은 바꿀 수 있습니다.

const settings = { theme: "light" };
settings.theme = "dark";
console.log(settings.theme); // dark
// settings = { theme: "light" }; // 재할당이므로 TypeError

settings.theme에 대입하는 것은 같은 객체의 속성을 바꾸는 일이고, settings에 대입하는 것은 이름을 다른 값에 다시 연결하는 재할당입니다. 앞 그림에서 두 화살표가 같은 객체로 모인 것도, 객체의 속성을 바꾸면 그 객체를 가리키는 모든 이름에서 바뀐 값이 보이기 때문입니다.

let과 const 가운데 무엇을 쓸지는 다시 대입할 일이 있는지로 정합니다. 다시 대입하지 않는다면 const를 쓰고, 다시 대입해야 할 때만 let을 씁니다. 객체의 속성을 바꾸더라도 이름 자체에 다시 대입하지 않는다면 const를 쓸 수 있습니다.

변수 이름 짓기

변수 이름에는 몇 가지 규칙이 있습니다. 이름에는 문자, 숫자, 밑줄(_), 달러 기호($)를 쓸 수 있지만 숫자로 시작할 수는 없어서, 1name은 쓸 수 없습니다. if, for, function, let, const처럼 자바스크립트에서 특별한 뜻을 가진 예약어도 이름으로 쓸 수 없습니다.

규칙은 아니지만 지키면 좋은 관례도 있습니다. a나 b보다 userName, totalPrice처럼 무엇을 담는지 알 수 있는 이름을 쓰고, 첫 단어는 소문자로 시작해 이어지는 단어의 첫 글자만 대문자로 쓰는 카멜 표기법(camelCase)을 씁니다.


데이터 타입

변수에 넣는 값은 숫자일 수도, 글자일 수도, 참과 거짓일 수도 있습니다. 이렇게 값이 가지는 종류를 데이터 타입이라고 하며, 타입에 따라 값을 다루는 방식이 달라집니다.

원시 타입

원시 타입(primitive type)의 값은 그 값 자체를 바꿀 수 없는 불변 값입니다. let 변수에 새 문자열을 대입할 수 있는 것과 헷갈리지 마세요. 그때도 원래의 문자열이 바뀌는 것이 아니라, 이름이 다른 문자열에 다시 연결되는 것입니다. 원시 타입은 일곱 가지입니다.

Number는 숫자로, 정수와 소수를 모두 포함합니다.

let age = 30;         // 정수
let pi = 3.14;        // 실수
let temperature = -5; // 음수

String은 문자열로, 글자 데이터를 작은따옴표('), 큰따옴표("), 백틱(`)으로 감싸 씁니다. 백틱으로 감싼 템플릿 리터럴(template literal)에서는 ${} 안에 변수나 식을 넣어 문자열 안에 값을 끼워 넣을 수 있습니다. 다음 예제는 앞의 age를 쓰므로 함께 실행합니다.

let name = "홍길동";
let greeting = '안녕하세요!';
let message = `내 나이는 ${age}살 입니다.`; // 백틱은 템플릿 리터럴을 허용하여 변수를 문자열 안에 쉽게 포함할 수 있습니다.
console.log(message); // 결과: 내 나이는 30살 입니다.

Boolean은 불리언으로, 참인 true와 거짓인 false 두 값만 있습니다. 조건에 따라 다른 일을 할 때 씁니다.

let isAdult = true;
let hasLicense = false;

true와 false는 따옴표 없이 씁니다. 따옴표로 감싼 "true"는 불리언이 아니라 네 글자짜리 문자열이므로, 참과 거짓을 나타낼 값에는 따옴표를 붙이지 않습니다.

Undefined는 변수를 선언하고 값을 넣지 않았을 때 자동으로 들어가는 undefined 값으로, 아직 값이 정해지지 않았다는 뜻입니다.

let unknownVar;
console.log(unknownVar); // 결과: undefined

Null은 값이 없다는 것을 일부러 나타낼 때 쓰는 null 값입니다. undefined가 아직 정해지지 않은 상태라면, null은 개발자가 비워 두기로 정한 상태입니다.

let selectedItem = null; // 선택된 아이템이 없음을 명시적으로 나타냅니다.

BigInt는 123n처럼 정수 뒤에 n을 붙여 쓰며, 아주 큰 정수를 정확하게 다룹니다. Number와는 다른 타입이라 typeof 123n은 "bigint"이고, 1n + 1처럼 Number와 섞어 계산하면 오류가 납니다. Symbol은 다른 어떤 값과도 같지 않은 고유한 값을 만들며, 주로 객체의 속성 이름이 겹치지 않게 할 때 씁니다. 두 타입은 이런 것이 있다는 정도만 알아 두면 됩니다.

객체 타입

원시 타입이 아닌 값은 모두 객체(object)입니다. 객체는 여러 데이터를 하나로 묶어 나타내며, 배열과 함수도 객체의 한 종류입니다. 객체는 다음 글들에서 자세히 다룹니다.

Object는 키(key)와 값(value)의 쌍을 모은 것입니다.

let person = {
    name: "김민수",
    age: 25,
    isStudent: true
};
console.log(person.name); // 결과: 김민수

Array는 여러 값을 순서대로 저장하는 특별한 객체입니다. 순서를 나타내는 번호(인덱스)는 0부터 시작합니다.

let colors = ["red", "green", "blue"];
console.log(colors[0]); // 결과: red (배열은 0부터 시작합니다.)

Function은 특정 작업을 하는 코드 묶음입니다. 자바스크립트에서는 함수도 값이라서 변수에 넣거나 다른 함수에 넘길 수 있습니다.

function greet(name) {
    return `안녕하세요, ${name}님!`;
}
console.log(greet("이유진")); // 결과: 안녕하세요, 이유진님!

타입 확인: typeof

값의 타입이 궁금할 때는 typeof 연산자를 씁니다. typeof는 타입의 이름을 문자열로 돌려주므로, console.log()로 출력하면 따옴표 없이 number처럼 보입니다.

console.log(typeof 123);           // 결과: number
console.log(typeof "hello");       // 결과: string
console.log(typeof true);          // 결과: boolean
console.log(typeof undefined);     // 결과: undefined
console.log(typeof null);          // 결과: object (초기 구현 때문에 남은 예외입니다.)
console.log(typeof {});            // 결과: object
console.log(typeof []);            // 결과: object
console.log(typeof function(){});  // 결과: function (함수도 객체이지만 typeof는 function을 돌려줍니다.)

typeof null이 object인 것은 자바스크립트 초기 구현에서 생긴 예외가 호환성 때문에 지금까지 남은 것으로, null이 객체라는 뜻은 아닙니다. 배열도 object로 나오므로, 배열인지 확인하려면 Array.isArray()를 씁니다.


연산자

데이터를 저장하는 것만큼 중요한 것이 그 데이터로 계산하고 비교하는 일입니다. 이때 쓰는 것이 연산자이며, 연산자는 하나 이상의 값으로 연산해 새 값을 만듭니다.

산술 연산자

산술 연산자(arithmetic operator)는 수학 계산을 합니다. +는 덧셈, -는 뺄셈, *는 곱셈, /는 나눗셈, %는 나눈 나머지, **는 거듭제곱이며, 10 % 3은 1, 2 ** 3은 8입니다.

let num1 = 10;
let num2 = 3;

console.log(num1 + num2); // 결과: 13
console.log(num1 - num2); // 결과: 7
console.log(num1 * num2); // 결과: 30
console.log(num1 / num2); // 결과: 3.3333333333333335
console.log(num1 % num2); // 결과: 1
console.log(2 ** 3); // 결과: 8

10 / 3이 3.3333333333333335처럼 끝자리가 5로 나오는 것은 Number가 이진 부동소수점으로 계산하기 때문입니다. 같은 이유로 0.1 + 0.2는 0.3이 아니라 0.30000000000000004가 됩니다. 화면에 보여 줄 소수와 내부에서 계산한 값은 다를 수 있으므로, 금액처럼 정확해야 하는 계산은 원 단위의 정수로 다룹니다.

+는 숫자뿐 아니라 문자열에도 쓰이며, 이때는 두 문자열을 이어 붙입니다. 한쪽이 문자열이면 다른 쪽의 숫자도 문자열로 바뀌어 이어집니다.

let firstName = "길동";
let lastName = "홍";
let fullName = lastName + firstName; // 문자열 연결
console.log(fullName); // 결과: 홍길동

console.log("숫자" + 123); // 결과: "숫자123" (숫자가 문자열로 변환되어 연결됩니다.)

더 깊이: 정수도 정확하지 않은 범위가 있다

Number는 IEEE 754의 64비트 배정밀도(double precision) 부동소수점 형식으로 값을 저장합니다. 이 형식은 가수(mantissa)를 담는 데 52비트만 쓰므로, 정수를 정확히 나타낼 수 있는 범위가 정해져 있습니다. 그 한계가 Number.MAX_SAFE_INTEGER, 곧 2의 53제곱에서 1을 뺀 9007199254740991입니다. 여기서 안전하다(safe)는 말은 정수를 정확히 나타내고 서로 올바르게 비교할 수 있다는 뜻입니다.

이 범위를 넘으면 소수가 아닌 정수 계산도 틀어집니다. Number.MAX_SAFE_INTEGER + 1과 Number.MAX_SAFE_INTEGER + 2는 둘 다 9007199254740992가 되어, 둘을 ===로 비교하면 true가 나옵니다. 값이 범위 안에 있는지는 Number.isSafeInteger()로 확인할 수 있습니다.

실무에서 이 문제를 만나는 흔한 곳은 서버가 보내는 큰 정수 ID입니다. JSON의 숫자는 JSON.parse()를 거치면 Number가 되므로, 범위를 넘는 ID는 읽는 순간 다른 값으로 바뀔 수 있습니다. 그래서 큰 ID는 서버와 약속해 문자열로 주고받고, 큰 정수로 계산해야 할 때는 이 글에서 본 BigInt를 씁니다. 자세한 내용은 MDN의 MAX_SAFE_INTEGER 설명에 있습니다.

할당 연산자

할당 연산자(assignment operator)는 변수에 값을 넣습니다. =는 값을 넣고, +=, -=, *=, /=, %=는 변수의 현재 값으로 계산한 결과를 다시 넣습니다. 예를 들어 x += 5;는 x = x + 5;와 같습니다.

let score = 100;
score += 50; // score는 150이 됩니다.
console.log(score); // 결과: 150

score /= 3;  // score는 50이 됩니다.
console.log(score); // 결과: 50

비교 연산자

비교 연산자(comparison operator)는 두 값을 비교해 true나 false를 돌려주며, 주로 조건문에서 씁니다. ===는 값과 타입이 모두 같은지, !==는 값이나 타입이 다른지 비교합니다. ==와 !=는 타입이 다르면 정해진 규칙으로 타입을 바꾼 뒤 비교합니다. >, <, >=, <=는 크기를 비교합니다.

console.log(10 == "10"); // 결과: true (문자열을 숫자로 바꿔 비교)
console.log(10 === "10"); // 결과: false (값은 같지만 타입이 다름)

console.log(5 != "5");   // 결과: false (타입을 바꾸면 같으므로 false)
console.log(5 !== "5");  // 결과: true (타입이 다르므로 true)

console.log(7 > 5);      // 결과: true
console.log(3 < 3);      // 결과: false
console.log(8 >= 8);     // 결과: true

==는 타입을 무시하는 것이 아니라, 두 값의 타입에 따라 정해진 변환 규칙을 적용합니다. 이 규칙이 복잡해서 "" == 0이 true가 되는 것처럼 예상하기 어려운 결과가 나오기도 합니다. ===는 타입을 바꾸지 않고 비교하므로 결과를 예상하기 쉽지만, 몇 가지 예외가 있습니다. 객체는 속성이 똑같아 보여도 같은 객체여야 true이고, NaN === NaN은 false이며, +0 === -0은 true입니다. 예상하지 못한 변환을 피하려면 ===와 !==를 기본으로 씁니다.

논리 연산자

논리 연산자(logical operator)는 여러 조건을 연결합니다. 피연산자가 모두 불리언일 때, &&(AND)는 둘 다 true일 때만 true, ||(OR)는 하나라도 true이면 true, !(NOT)는 참과 거짓을 뒤집습니다.

let isRaining = true;
let hasUmbrella = false;

console.log(isRaining && hasUmbrella); // 결과: false (둘 다 true여야 true)
console.log(isRaining || hasUmbrella); // 결과: true (둘 중 하나라도 true면 true)
console.log(!isRaining);               // 결과: false (isRaining의 반대)

불리언이 아닌 값에 쓰면 &&와 ||의 결과가 불리언이 아닐 수 있습니다. 왼쪽부터 평가하면서, &&는 처음 만난 거짓 같은 값을, 없으면 마지막 값을 돌려주고, ||는 처음 만난 참 같은 값을, 없으면 마지막 값을 돌려줍니다. 결과가 정해지면 오른쪽 식은 실행하지 않는데, 이를 단락 평가(short-circuit evaluation)라고 합니다.

console.log("" || "손님");  // 손님
console.log(0 || 10);        // 10: 0도 거짓처럼 취급
console.log("회원" && 3);   // 3
console.log(Boolean("0"));  // true: 빈 문자열이 아님

조건에서 거짓처럼 취급되는 값(falsy)은 false, 0, -0, 0n, 빈 문자열 "", null, undefined, NaN이고, 나머지는 모두 참처럼 취급됩니다(truthy). 그래서 문자열 "0"과 빈 배열 []도 참입니다. 수량 0이 올바른 입력인 상황에서 수량 || 기본수량으로 기본값을 정하면, 0이 거짓이라 기본값으로 덮어써지므로 주의합니다.

증감 연산자

증감 연산자는 숫자를 1씩 늘리거나(++) 줄입니다(--). 연산자를 변수 앞에 붙이는 전위 연산(++a)은 먼저 값을 바꾼 뒤 바뀐 값을 쓰고, 뒤에 붙이는 후위 연산(a++)은 원래 값을 먼저 쓴 뒤 값을 바꿉니다.

let count = 5;
let result1 = ++count; // count를 먼저 6으로 만들고, result1에 6을 할당
console.log(count);   // 결과: 6
console.log(result1); // 결과: 6

let num = 10;
let result2 = num++;  // result2에 먼저 10을 할당하고, num을 11로 만듭니다.
console.log(num);     // 결과: 11
console.log(result2); // 결과: 10

정리

  • 변수는 이름과 값을 잇는 바인딩이며, 다시 대입할 일이 없으면 const를, 있으면 let을 씁니다.
  • const는 재할당만 막으므로, 연결된 객체의 속성은 바꿀 수 있고 그 변화는 같은 객체를 가리키는 모든 이름에서 보입니다.
  • 원시 타입은 Number, String, Boolean, Undefined, Null, BigInt, Symbol의 일곱 가지이고, 나머지는 모두 객체입니다.
  • typeof는 타입 이름을 문자열로 돌려주며, typeof null은 예외적으로 object입니다.
  • +는 한쪽이 문자열이면 이어 붙이고, Number의 소수 계산에는 이진 부동소수점의 오차가 있습니다.
  • ===는 타입을 바꾸지 않고 비교하므로 기본으로 쓰고, &&와 ||는 단락 평가를 하며 불리언이 아닌 값을 돌려줄 수 있습니다.

연습 문제

1. 수량과 배송비

입력받은 수량 "3"을 Number()로 바꾼 뒤 단가 4,000원을 곱합니다. 10,000원 이상이면 배송비가 없다는 규칙일 때, 총액과 비교 결과는 무엇일까요? 변환하기 전의 문자열과 변환한 숫자에 각각 + 1을 하면 어떻게 될까요?

해설 보기
const quantityText = "3";
const quantity = Number(quantityText);
const totalPrice = quantity * 4000;
console.log(totalPrice);             // 12000
console.log(totalPrice >= 10000);    // true
console.log(quantityText + 1);       // 31: 문자열 연결
console.log(quantity + 1);           // 4: 숫자 덧셈

Number("3")은 3이고, 3 × 4,000 = 12,000이 10,000 이상이므로 비교 결과는 true입니다. 변환하기 전의 "3" + 1은 문자열을 이어 붙여 "31"이 되고, 변환한 3 + 1은 숫자를 더해 4가 됩니다.

Number()가 모든 입력을 올바른 수량으로 바꿔 주는 것은 아닙니다. Number("세 개")는 NaN이므로, 실제 입력을 처리할 때는 변환한 결과가 숫자인지 따로 확인합니다.

2. 같은 객체, 다른 객체

다음 코드의 세 출력은 무엇일까요?

const a = { n: 3 };
const b = a;
const c = { n: 3 };
b.n = 4;
console.log(a.n);
console.log(a === b);
console.log(a === c);
해설 보기

순서대로 4, true, false입니다. b는 a와 같은 객체를 가리키므로 b.n을 바꾸면 a.n도 4가 되고, 같은 객체이므로 a === b는 true입니다.

c는 처음에 a와 속성이 똑같았지만 따로 만든 다른 객체이므로, a === c는 false입니다. 객체의 ===는 속성이 같은지가 아니라 같은 객체인지를 비교합니다.

3. 0이 사라지는 기본값

사용자가 수량을 입력하지 않으면 1로 정하려고 const qty = input || 1;이라고 썼습니다. input이 0, "", undefined, 5일 때 qty는 각각 무엇이 될까요? 0을 그대로 지키려면 어떻게 바꿔야 할까요?

해설 보기

0과 "", undefined는 모두 거짓 같은 값이라 ||가 오른쪽의 1을 돌려주고, 5는 참 같은 값이라 5가 됩니다. 그래서 사용자가 일부러 0을 입력해도 1로 바뀌어 버립니다.

0을 지키려면 null이나 undefined일 때만 기본값을 쓰는 ?? 연산자를 씁니다. const qty = input ?? 1;이면 0은 0, ""는 "", undefined는 1, 5는 5가 됩니다.

규칙은 MDN의 엄격한 비교, 논리 AND, 논리 OR 문서에서 확인할 수 있습니다.

다음 글 조건문과 반복문에서는 비교와 논리 연산의 결과로 실행할 코드를 고르고, 같은 일을 여러 번 반복하는 방법을 다룹니다.