이번 문서의 목표: 이 문서를 다 읽으면 변수 선언 방식(var·let·const)의 차이, JavaScript의 기본 자료형과 typeof 결과, 암묵적 형 변환의 규칙, 비교 연산자 ==와 ===의 차이, 조건·반복문의 흐름, 함수 선언 방식을 코드만 보고 실행 결과까지 예측할 수 있게 됩니다.
JavaScript는 이 과목에서 어떤 역할인가
쉽게 말하면: HTML이 뼈대, CSS가 옷이라면 JavaScript(자바스크립트)는 페이지를 움직이게 하는 근육입니다.
01편에서 클라이언트-서버 개념을 다뤘는데, JavaScript는 브라우저(클라이언트) 안에서 직접 실행되어 사용자의 클릭·입력에 실시간으로 반응하는 유일한 언어입니다. 이번 편은 09편에서 다룰 배열·객체·DOM·이벤트의 토대가 되는 문법 자체에 집중합니다. 독학사 시험은 JavaScript 파트에서 개념 설명보다 “이 코드를 실행하면 무엇이 출력되는가” 문제 비중이 매우 높으므로, 이 문서도 코드마다 실행 결과를 직접 확인하는 방식으로 구성합니다.
변수와 식별자
쉽게 말하면: 변수는 값을 담아 두는 이름표이고, 식별자는 그 이름표에 붙일 수 있는 이름의 규칙입니다.
JavaScript에서 변수를 선언하는 키워드는 세 가지이며, 이 셋의 차이는 반드시 출제된다고 봐도 좋을 만큼 비중이 높습니다.
| 키워드 | 재선언 | 재할당 | 스코프(scope, 유효 범위) |
|---|---|---|---|
var | 가능 | 가능 | 함수 스코프(function scope) |
let | 불가능 | 가능 | 블록 스코프(block scope) |
const | 불가능 | 불가능 | 블록 스코프(block scope) |
var a = 1;
var a = 2; // 재선언 가능, 오류 없음
let b = 1;
// let b = 2; // 오류: 이미 선언된 식별자
const c = 1;
// c = 2; // 오류: 상수는 재할당 불가스코프(scope)는 그 변수를 참조할 수 있는 코드의 범위를 뜻합니다. var는 함수 단위로만 범위가 나뉘어, if나 for 같은 블록(중괄호로 감싼 부분) 안에서 선언해도 그 블록을 벗어나 함수 전체에서 접근할 수 있습니다. 반면 let·const는 블록 단위로 범위가 나뉘어, 선언된 중괄호를 벗어나면 더 이상 접근할 수 없습니다.
if (true) {
var x = 10;
let y = 20;
}
console.log(x); // 10 — var는 블록을 벗어나도 함수(또는 전역) 범위이므로 접근 가능
console.log(y); // 오류: y is not defined — let은 블록을 벗어나면 소멸| 줄 | 코드 | x의 상태 | y의 상태 |
|---|---|---|---|
| 1 | if (true) { | 미정의 | 미정의 |
| 2 | var x = 10; | 10 (함수/전역 범위에 등록) | - |
| 3 | let y = 20; | 10 | 20 (블록 범위에만 등록) |
| 4 | } (블록 종료) | 10 (유지) | 소멸(블록 밖에서 접근 불가) |
| 5 | console.log(x) | 10 출력 | - |
| 6 | console.log(y) | - | 참조 오류 발생 |
이런 차이 때문에 최신 JavaScript 코드에서는 var보다 let·const를 쓰는 것이 원칙입니다. 값이 바뀔 일이 없으면 const를, 나중에 재할당이 필요하면 let을 쓰는 관례를 기억해 두면 됩니다.
식별자(identifier, 변수·함수 등에 붙이는 이름) 규칙은 다음과 같습니다.
- 문자, 숫자,
_(밑줄),$(달러 기호)를 사용할 수 있지만 숫자로 시작할 수 없다. - 대소문자를 구분한다(
total과Total은 다른 식별자). let,const,function같은 예약어(reserved word, 언어가 이미 문법적으로 쓰고 있는 단어)는 식별자로 쓸 수 없다.
자료형(data type)
쉽게 말하면: 자료형은 “이 값이 숫자인지, 글자인지, 참/거짓인지”를 구분하는 값의 종류입니다.
JavaScript의 기본 자료형(원시 타입, primitive type)은 다음과 같습니다.
| 자료형 | 예시 값 | typeof 결과 |
|---|---|---|
| number(숫자) | 10, 3.14, -5 | "number" |
| string(문자열) | "안녕", 'hi' | "string" |
| boolean(불리언, 참거짓) | true, false | "boolean" |
| undefined(정의되지 않음) | 값을 할당하지 않은 변수 | "undefined" |
| null(값 없음) | null | "object"(언어 초기 설계의 오류로 굳어진 예외) |
| symbol(심벌) | Symbol("id") | "symbol" |
| bigint(빅정수) | 10n | "bigint" |
typeof null이 "object"를 반환하는 것은 JavaScript 초창기부터 있던 잘 알려진 결함(bug)인데, 지금까지도 호환성을 위해 고쳐지지 않고 그대로 유지되고 있습니다. 시험에서 “typeof null의 결과는?”이라는 문제가 나오면 상식적으로 "null"을 답으로 고르기 쉬운데, 정답은 "object"라는 점을 정확히 기억해야 합니다.
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object"undefined와 null도 자주 짝을 지어 출제됩니다. undefined는 변수를 선언만 하고 값을 할당하지 않았을 때 JavaScript 엔진이 자동으로 부여하는 값이고, null은 개발자가 “이 자리에는 값이 없다”는 것을 의도적으로 명시하기 위해 직접 대입하는 값입니다. 즉 하나는 “아직 정해지지 않음”이고 하나는 “일부러 비워둠”이라는 의미 차이가 있습니다.
형 변환(type conversion)
쉽게 말하면: 형 변환은 한 자료형의 값을 다른 자료형으로 바꾸는 것이며, 개발자가 직접 시키기도 하고(명시적) JavaScript가 알아서 처리하기도 합니다(암묵적).
JavaScript는 서로 다른 자료형끼리 연산할 때 자동으로 형을 맞추는 암묵적 형 변환(implicit coercion)을 수행합니다. 이 동작이 독학사 JavaScript 파트에서 출력 예측 문제로 가장 많이 나오는 지점입니다.
console.log("5" + 3); // "53"
console.log("5" - 3); // 2
console.log("5" * "2"); // 10
console.log(1 + true); // 2
console.log("10" == 10); // true
console.log("10" === 10); // false한 줄씩 왜 이런 결과가 나오는지 추적해 보겠습니다.
| 코드 | 연산자의 성격 | 변환 규칙 | 결과 |
|---|---|---|---|
"5" + 3 | +는 피연산자 중 하나라도 문자열이면 문자열 연결로 동작 | 3이 "3"으로 변환되어 이어붙기 | "53" |
"5" - 3 | -는 문자열 연결 기능이 없어 항상 숫자 연산으로 해석 | "5"가 숫자 5로 변환 | 2 |
"5" * "2" | *도 항상 숫자 연산 | 양쪽 모두 숫자로 변환 | 10 |
1 + true | +에서 boolean이 숫자로 변환됨 | true는 1, false는 0으로 취급 | 2 |
+ 연산자만 문자열 연결과 숫자 덧셈 두 가지 역할을 겸한다는 점이 이 표의 핵심입니다. 다른 산술 연산자(-, *, /)는 문자열 연결 기능이 없으므로 무조건 양쪽을 숫자로 변환하려 시도합니다.
자주 틀리는 점: "5" + 3과 "5" - 3이 서로 다른 원리로 동작한다는 것을 모르면, 둘 다 숫자 계산이 될 것이라 착각해 "5" + 3의 결과를 8로 잘못 예측하기 쉽습니다. +가 나오면 피연산자 중 문자열이 있는지부터 확인하는 습관이 필요합니다.
== 와 === 의 차이
==(동등 연산자, loose equality)는 비교 전에 암묵적 형 변환을 먼저 수행한 뒤 값을 비교하고, ===(일치 연산자, strict equality)는 형 변환 없이 자료형까지 같아야 true를 반환합니다.
console.log("10" == 10); // true — "10"이 숫자 10으로 변환된 뒤 비교
console.log("10" === 10); // false — 문자열과 숫자는 자료형이 다름
console.log(0 == false); // true — false가 0으로 변환됨
console.log(0 === false); // false — 숫자와 불리언은 자료형이 다름
console.log(null == undefined); // true — 이 둘만의 특별 규칙
console.log(null === undefined); // false — 자료형이 다름(object vs undefined)이런 예측 불가능함 때문에, 실무와 시험 모두에서 ===(일치 연산자)를 기본으로 사용하는 것을 권장합니다. ==가 굳이 필요한 경우는 매우 드뭅니다.
진리값(truthy/falsy) 판별
조건문에서 값 자체를 boolean처럼 평가할 때, JavaScript는 정해진 규칙에 따라 참(truthy)·거짓(falsy)으로 취급합니다. falsy로 취급되는 값은 다음 여섯 가지뿐이며, 이 목록에 없는 값은 모두 truthy입니다.
false, 0, "", null, undefined, NaNif ("") {
console.log("실행 안 됨");
} else {
console.log("빈 문자열은 falsy이므로 이 줄이 실행됨");
}
if ("0") {
console.log("문자열 '0'은 truthy이므로 이 줄이 실행됨");
}자주 틀리는 점: 숫자 0은 falsy이지만, 숫자처럼 보이는 문자열 "0"은 비어 있지 않은 문자열이므로 truthy입니다. “0이라는 값은 어떤 형태든 항상 falsy다”라는 진술은 틀린 설명이며, 이는 형 변환 문제와 함께 자주 짝지어 출제됩니다.
조건문(conditional statement)
let 점수 = 75;
if (점수 >= 90) {
console.log("A");
} else if (점수 >= 80) {
console.log("B");
} else if (점수 >= 70) {
console.log("C");
} else {
console.log("D");
}ifelse ifelse 구조는 위에서부터 순서대로 조건을 검사하다가 처음 참(true)이 되는 블록만 실행하고 나머지는 건너뜁니다. 점수가 75이면 점수 >= 90(거짓) → 점수 >= 80(거짓) → 점수 >= 70(참, 여기서 멈춤)이 되어 "C"가 출력됩니다. 뒤에 있는 else는 검사조차 하지 않습니다.
switch 문은 하나의 값을 여러 경우와 비교할 때 씁니다.
let 등급 = "B";
switch (등급) {
case "A":
console.log("우수");
break;
case "B":
console.log("보통");
break;
default:
console.log("확인 필요");
}
// 출력: "보통"자주 틀리는 점: case 블록 끝에 break를 빠뜨리면, 해당 case가 실행된 뒤 다음 case까지 조건 검사 없이 그대로 이어서 실행됩니다. 이를 폴스루(fall-through)라고 부릅니다. 위 예시에서 case "B": 다음 줄에 break가 없다면 "보통"을 출력한 뒤 default의 "확인 필요"까지 이어서 출력되어 버립니다.
반복문(loop)
for (let i = 1; i <= 3; i++) {
console.log(i);
}for 문은 초기식; 조건식; 증감식 세 부분으로 이루어집니다. 실행 순서를 한 단계씩 추적하면 다음과 같습니다.
| 단계 | 수행되는 부분 | i의 값 | 조건 검사(i <= 3) | 출력 |
|---|---|---|---|---|
| 1 | 초기식 let i = 1 (최초 1회만) | 1 | - | - |
| 2 | 조건식 검사 | 1 | 참 | - |
| 3 | 본문 실행 | 1 | - | 1 |
| 4 | 증감식 i++ | 2 | - | - |
| 5 | 조건식 검사 | 2 | 참 | - |
| 6 | 본문 실행 | 2 | - | 2 |
| 7 | 증감식 i++ | 3 | - | - |
| 8 | 조건식 검사 | 3 | 참 | - |
| 9 | 본문 실행 | 3 | - | 3 |
| 10 | 증감식 i++ | 4 | - | - |
| 11 | 조건식 검사 | 4 | 거짓(4 <= 3 아님) | 반복 종료 |
while은 조건식만 앞에 두고, 초기식과 증감식을 직접 작성해야 하는 반복문입니다.
let i = 0;
while (i < 3) {
console.log(i);
i++;
}
// 0, 1, 2 순서로 출력do...while은 while과 거의 같지만 조건을 나중에 검사하기 때문에, 조건이 처음부터 거짓이더라도 본문이 최소 한 번은 실행된다는 차이가 있습니다.
let n = 10;
do {
console.log(n); // 10 출력 — 조건이 거짓인데도 한 번은 실행됨
} while (n < 5);break는 반복문 자체를 즉시 빠져나가고, continue는 현재 반복만 건너뛰고 다음 반복으로 넘어갑니다. 이 둘의 차이도 출력 예측 문제에 자주 나옵니다.
for (let i = 1; i <= 5; i++) {
if (i === 3) continue;
if (i === 5) break;
console.log(i);
}
// 1, 2, 4 출력 — 3은 건너뛰고(continue), 5는 아예 반복을 멈춘다(break)함수(function) 기초
쉽게 말하면: 함수는 “이름을 붙여 재사용할 수 있는 코드 묶음”입니다.
function 더하기(a, b) {
return a + b;
}
console.log(더하기(3, 4)); // 7function 키워드로 만드는 이 방식을 함수 선언문(function declaration)이라고 합니다. a, b는 매개변수(parameter, 함수를 정의할 때 받기로 약속한 값의 자리)이고, 호출할 때 실제로 넘기는 3, 4는 인수(argument, 실제로 전달하는 값)입니다. return은 함수의 실행을 그 자리에서 끝내고 지정한 값을 호출한 자리로 돌려주는 키워드로, return이 없는 함수는 자동으로 undefined를 반환합니다.
함수를 변수에 담는 방식도 있습니다.
const 곱하기 = function (a, b) {
return a * b;
};
const 빼기 = (a, b) => a - b; // 화살표 함수(arrow function)함수 표현식(function expression)은 이름 없는(또는 이름이 있어도 외부에서 그 이름으로 호출할 수 없는) 함수를 만들어 변수에 대입하는 방식이고, 화살표 함수(arrow function)는 function 키워드 없이 => 기호로 더 짧게 쓰는 최신 문법입니다. 화살표 함수에서 본문이 표현식 하나뿐이면 return과 중괄호를 생략하고 그 값이 자동으로 반환됩니다(위 빼기 예시).
자주 틀리는 점: 함수 선언문(function 이름() {})은 호이스팅(hoisting)이라는 특성 때문에 코드에서 실제로 작성된 위치보다 앞에서 호출해도 정상 동작합니다. 반면 함수 표현식이나 화살표 함수를 const·let에 대입한 경우, 대입 코드보다 앞에서 호출하면 오류가 발생합니다.
console.log(더하기(1, 2)); // 3 — 정상 동작(호이스팅)
function 더하기(a, b) {
return a + b;
}
console.log(빼기(5, 2)); // 오류: 아직 초기화되지 않음
const 빼기 = (a, b) => a - b;핵심 정리
var는 재선언 가능·함수 스코프,let은 재선언 불가·블록 스코프,const는 재선언·재할당 모두 불가·블록 스코프다.- 기본 자료형은 number·string·boolean·undefined·null·symbol·bigint이며,
typeof null은 예외적으로"object"를 반환한다. +는 피연산자에 문자열이 있으면 연결 연산으로, 그 외 산술 연산자는 항상 숫자 변환 연산으로 동작한다.==는 형 변환 후 비교,===는 자료형까지 일치해야 참이며 실무·시험 모두===사용을 권장한다.- falsy 값은
false, 0, "", null, undefined, NaN여섯 가지뿐이고 그 외는 모두 truthy다. ifelse ifelse는 처음 참인 블록만 실행하고,switch는break가 없으면 다음 case로 흘러넘친다(fall-through).for·while은 조건을 먼저 검사하지만do...while은 본문을 최소 한 번 실행한 뒤 조건을 검사한다.break는 반복 종료,continue는 그 회차만 건너뛴다.- 함수 선언문은 호이스팅되어 선언 전에도 호출할 수 있지만, 함수 표현식·화살표 함수를 담은 변수는 대입 이전에 호출하면 오류가 난다.
마무리 복습
console.log("5" + 3);
console.log("5" - 3);"10" == 10
"10" === 10for (let i = 1; i <= 5; i++) {
if (i === 3) continue;
if (i === 5) break;
console.log(i);
}