JAVASCRIPT · 심층 가이드
JavaScript 기초 문법 완전 정리
변수 선언과 조건 분기, 반복, 배열과 객체, 타입 변환까지 JavaScript 코드를 읽고 고칠 때 매일 쓰는 문법을 20개 예제로 확인합니다.
JavaScript 기초에서 발이 걸리는 곳은 문법 자체가 아니라 암묵적 변환입니다. 값을 비교하고 더하고 정렬하는 아주 평범한 연산에서, 엔진이 조용히 타입을 바꿔 놓고 결과만 돌려줍니다. '5' + 3은 문자열이 되고 '5' - 3은 숫자가 됩니다. 빈 배열도, 문자열 '0'도 조건문에서는 참입니다. 그래서 다른 언어를 먼저 배운 사람일수록 '되긴 되는데 왜 이 값이지'를 자주 겪습니다. 여기서 시간을 아끼는 방법은 규칙을 외우기보다 변환이 일어나지 않는 코드를 쓰는 것입니다.
숫자 변수 선언과 문자열 상수 선언에서 const를 기본으로 두고 재할당이 필요할 때만 let을 쓰는 습관을 잡습니다. if else 분기와 switch 또는 match 사용은 같은 판단을 다른 형태로 쓰는 짝이고, null 기본값 처리는 이 둘에 들어가기 전 값을 정돈하는 단계입니다. 뒤쪽의 타입 변환과 정렬 기초는 함께 보는 편이 좋습니다. 정렬에서 나오는 이상한 결과의 원인이 바로 앞 절에서 다룬 문자열 변환이기 때문입니다.
Array.prototype.sort는 비교 함수를 주지 않으면 원소를 문자열로 바꿔 사전순으로 정렬합니다. [10, 9, 1].sort()가 [1, 10, 9]가 되는 이유입니다. 숫자 배열은 반드시 sort((a, b) => a - b)처럼 비교 함수를 넘겨야 하고, sort는 원본 배열을 바꾼다는 점도 같이 기억해 두세요. 기본값 처리에서는 ||와 ??의 차이가 중요합니다. ||는 0이나 빈 문자열까지 기본값으로 덮어쓰므로, '값이 없을 때만'이라는 의도라면 ??를 써야 합니다.
01숫자 변수 선언
let count = 1로 값을 담고, count > 5 비교 결과에 따라 label에 'small'이 들어가 출력됩니다. 값이 바뀔 변수는 let, 한 번 정해지면 그대로인 값은 const로 나눠 선언합니다. const는 재할당만 막을 뿐 객체 내부 속성 변경까지 막지는 않습니다.
JavaScript code
// 숫자 변수 선언
let count = 1;
const label = count > 5 ? "big" : "small";
console.log(label);let과 const는 블록 범위라 if나 for 밖으로 새어 나가지 않고, 선언 전에 읽으면 TDZ 때문에 ReferenceError가 납니다.
let으로 선언해 놓고 끝까지 재할당하지 않는 변수가 쌓이면 읽는 사람이 값이 바뀔 수 있다고 오해합니다. 기본은 const로 두는 편이 낫습니다.
02문자열 상수 선언
실제로 도는 것은 문자열 선언이 아니라 i가 0, 1, 2로 증가하는 for 문이고, 매번 i + 2인 2, 3, 4를 출력합니다. 반복 횟수가 정해진 작업에 이 형태를 씁니다. let으로 선언한 i는 반복마다 새 바인딩이 생겨, 루프 안에서 만든 함수가 마지막 값만 보는 var 시절의 문제가 생기지 않습니다.
JavaScript code
// 문자열 상수 선언
for (let i = 0; i < 3; i += 1) {
console.log(i + 2);
}i += 1과 i++는 반복문 갱신식 자리에서는 결과가 같지만, 값을 대입하는 식에서는 i++가 증가 전 값을 돌려준다는 차이가 있습니다.
종료 조건에 < 대신 <=를 쓰면 배열 순회에서 한 칸을 더 돌아 undefined를 읽습니다. 오류가 나지 않아 조용히 지나갑니다.
03불리언 플래그 만들기
[...values]로 원본 [3, 1, 4, 3]을 복사한 뒤 (a, b) => a - b 비교 함수로 오름차순 정렬해 [1, 3, 3, 4]를 출력합니다. 원본을 남겨야 하는 곳에서 이렇게 사본을 정렬합니다. 비교 함수를 생략한 sort()는 요소를 문자열로 바꿔 비교하므로 10이 9보다 앞에 오게 됩니다.
JavaScript code
// 불리언 플래그 만들기
const values = [3, 1, 4, 3];
console.log([...values].sort((a, b) => a - b));(a, b) => a - b는 정수에 잘 맞지만 값이 아주 큰 수라면 뺄셈이 정밀도를 잃으므로, 크기만 비교해 부호를 돌려주는 형태가 더 안전합니다.
[...values]를 빼먹고 values.sort(...)를 부르면 원본 배열의 순서가 그 자리에서 바뀝니다. 같은 배열을 참조하던 코드까지 함께 흔들립니다.
04문자열 길이 구하기
객체 리터럴 { name: 'user4', level: 4 }를 만들고 점 표기법으로 두 속성을 읽어 한 줄에 출력합니다. 길이 계산은 없으며, 이름이 붙은 값 묶음을 다루는 코드입니다. 없는 속성을 읽으면 오류가 아니라 undefined가 나오므로 속성 이름 오타는 실행 중에도 조용히 넘어갑니다.
JavaScript code
// 문자열 길이 구하기
const user = { name: "user4", level: 4 };
console.log(user.name, user.level);속성 이름이 변수명과 같으면 { name, level }처럼 줄여 쓸 수 있고, { [key]: value } 형태로 계산된 키도 리터럴 안에서 바로 만들 수 있습니다.
user.profile.city처럼 이어 읽다가 중간이 undefined면 TypeError로 멈춥니다. 확실하지 않은 경로에는 ?.를 넣어야 합니다.
05문자열 정리하기
count가 5라서 count > 5는 거짓이 되고, 삼항 연산자가 고른 'small'이 찍힙니다. 경계값이 조건에 포함되는지 확인할 때 이런 비교를 직접 돌려 봅니다. >와 >=는 한 글자 차이로 경계에서 결과가 뒤집히므로, 값이 딱 맞아떨어지는 지점을 한 번은 눈으로 맞춰 봐야 합니다.
JavaScript code
// 문자열 정리하기
let count = 5;
const label = count > 5 ? "big" : "small";
console.log(label);이 식에서 경계값 5는 small로 갑니다. 경계 포함 여부가 헷갈린다면 조건을 count >= 6처럼 바꿔 의도를 코드에 그대로 드러내는 방법도 있습니다.
비교 대상이 문자열이면 "10" > 5는 숫자로 변환돼 참이지만 "10" > "5"는 문자 순서로 비교돼 거짓입니다. 비교 전에 타입을 맞춰야 합니다.
06숫자 합계 계산
합계를 담는 누적 변수는 없고, for 문이 세 번 돌며 i + 6의 결과 6, 7, 8을 각각 출력합니다. 합을 구하려면 루프 밖에 let sum = 0을 두고 안에서 더해 나가면 됩니다. 소수를 더할 때는 0.1 + 0.2가 정확히 0.3이 되지 않는 부동소수점 오차가 누적된다는 점을 감안해야 합니다.
JavaScript code
// 숫자 합계 계산
for (let i = 0; i < 3; i += 1) {
console.log(i + 6);
}합을 구할 때 reduce를 쓰면 누적 변수를 밖에 두지 않아도 되지만, 중간에 멈춰야 하거나 인덱스 조작이 필요하면 이 for 형태가 다루기 쉽습니다.
누적 변수를 루프 안에서 선언하면 반복마다 초기화되어 마지막 값만 남습니다. let sum = 0은 반드시 루프 바깥에 두어야 합니다.
07나머지 연산 확인
나머지 연산자 %는 쓰이지 않고, 배열 사본을 숫자 비교 함수로 정렬해 [1, 3, 4, 7]을 만듭니다. 정렬 결과를 확인하면서 원본 배열은 그대로 두고 싶을 때의 형태입니다. sort는 배열을 제자리에서 바꾸고 같은 배열을 반환하므로, 전개 없이 부르면 values까지 순서가 바뀝니다.
JavaScript code
// 나머지 연산 확인
const values = [3, 1, 4, 7];
console.log([...values].sort((a, b) => a - b));sort는 명세상 안정 정렬이라 비교 결과가 0인 요소들의 원래 순서가 유지됩니다. 여러 기준을 단계로 나눠 정렬할 때 이 성질을 활용할 수 있습니다.
%로 짝수를 가릴 때 음수를 넣으면 -3 % 2가 -1이라 === 1 검사가 빗나갑니다. 나머지 연산은 왼쪽 피연산자의 부호를 따라갑니다.
08if else 분기
if나 else는 없고 객체 하나를 만들어 user.name과 user.level을 출력하는 코드입니다. 관련된 값을 한 덩어리로 묶어 넘길 때 이렇게 씁니다. console.log에 인자를 쉼표로 나열하면 문자열 결합 없이 값 사이에 공백이 들어가고, 객체는 문자열로 뭉개지지 않고 구조 그대로 표시됩니다.
JavaScript code
// if else 분기
const user = { name: "user8", level: 8 };
console.log(user.name, user.level);console.log는 객체를 문자열로 뭉개지 않고 구조를 그대로 보여 주므로, 디버깅할 때는 문자열을 이어 붙이기보다 값을 쉼표로 나열하는 편이 정보가 많습니다.
브라우저 콘솔은 객체를 참조로 보여 주어, 로그를 찍은 뒤 값을 바꾸면 펼쳐 볼 때 바뀐 내용이 나타납니다. 시점을 고정하려면 그 순간의 사본을 찍어야 합니다.
09switch 또는 match 사용
switch 대신 삼항 연산자가 count > 5 하나로 두 갈래를 가르며, count가 9이므로 'big'이 나옵니다. 조건이 둘뿐이면 switch보다 이 표현식이 짧습니다. switch는 각 case를 ===로 비교해서 문자열 '9'와 숫자 9를 서로 다른 분기로 취급합니다.
JavaScript code
// switch 또는 match 사용
let count = 9;
const label = count > 5 ? "big" : "small";
console.log(label);switch는 break가 없으면 아래 분기로 계속 흘러가는데, 여러 값을 같은 코드로 묶고 싶을 때는 이 성질을 일부러 쓰기도 합니다.
switch(true)로 범위 조건을 흉내 내면 각 분기가 위에서부터 평가되어, 조건이 겹칠 때 먼저 쓴 쪽만 실행되고 나머지는 조용히 무시됩니다.
10null 기본값 처리
널 병합 연산자는 등장하지 않고, 0부터 2까지 도는 루프가 i + 10인 10, 11, 12를 출력합니다. 기본값을 채우는 코드는 아니지만 반복 안에서 값을 만들어 내는 흐름은 같습니다. 기본값이 필요할 때 a || b는 0과 빈 문자열까지 대체해 버리므로 a ?? b 쪽이 안전합니다.
JavaScript code
// null 기본값 처리
for (let i = 0; i < 3; i += 1) {
console.log(i + 10);
}obj.count ?? 0과 obj.count || 0은 값이 0일 때 결과가 갈립니다. 숫자나 불리언 기본값에는 ??를 쓰는 편이 사고가 적습니다.
??는 ||나 &&와 괄호 없이 섞어 쓸 수 없어 문법 오류가 납니다. 우선순위 착각을 막기 위한 규칙이므로 괄호로 묶어야 합니다.
11삼항 연산자 사용
삼항 연산자가 아니라 화살표 비교 함수 (a, b) => a - b가 정렬 기준으로 쓰였고, 결과는 [1, 3, 4, 11]입니다. 숫자 배열을 크기 순으로 늘어놓아야 할 때 이 비교 함수를 넘깁니다. 반환값이 음수면 앞, 양수면 뒤, 0이면 순서 유지라는 규칙이라 뺄셈 한 줄로 충분합니다.
JavaScript code
// 삼항 연산자 사용
const values = [3, 1, 4, 11];
console.log([...values].sort((a, b) => a - b));비교 함수는 같은 두 값에 대해 항상 같은 결과를 내야 합니다. Math.random() - 0.5로 섞는 방식은 이 규칙을 어겨 결과가 고르게 섞이지 않습니다.
비교 함수에서 참·거짓을 반환하면 각각 1과 0으로 변환되어 순서를 뒤집는 경우가 사라집니다. 음수와 양수를 모두 낼 수 있어야 정렬이 제대로 됩니다.
12for 반복문
반복문 없이 객체 user를 만들어 속성 두 개를 읽는 코드입니다. 객체를 순회해야 한다면 for...in은 키를 문자열로 주면서 프로토타입 체인까지 훑는 반면, Object.entries와 for...of 조합은 자기 속성만 키와 값 쌍으로 돌려줍니다.
JavaScript code
// for 반복문
const user = { name: "user12", level: 12 };
console.log(user.name, user.level);Object.keys는 자기 소유의 열거 가능한 문자열 키만 배열로 돌려주므로, 순회 도중 속성을 추가해도 이미 만들어진 배열은 영향을 받지 않습니다.
for...in으로 배열을 돌면 인덱스가 문자열로 나와 i + 1이 "01"이 되고, 배열에 붙여 둔 다른 속성까지 함께 순회됩니다.
13while 누적 계산
while 루프와 누적 변수는 없으며, count가 13이라 비교가 참이 되어 'big'이 출력됩니다. 조건 하나로 라벨을 붙이는 최소 코드입니다. while로 바꾼다면 조건에 쓰인 값을 루프 본문에서 반드시 갱신해야 하고, 그러지 않으면 무한 반복에 빠집니다.
JavaScript code
// while 누적 계산
let count = 13;
const label = count > 5 ? "big" : "small";
console.log(label);do...while은 조건과 무관하게 본문이 최소 한 번은 실행되므로, 먼저 값을 읽어 봐야 종료 여부를 알 수 있는 흐름에 잘 맞습니다.
무한 루프에 빠지면 브라우저 탭 전체가 멈춰 콘솔조차 반응하지 않습니다. 조건에 쓰인 값이 본문 어디에서 갱신되는지 먼저 확인해야 합니다.
14break 와 continue
break도 continue도 쓰지 않아 세 번 모두 끝까지 돌며 14, 15, 16을 출력합니다. 중간에 빠져나가려면 break, 남은 본문만 건너뛰려면 continue를 넣습니다. 두 키워드는 forEach 안에서는 쓸 수 없으므로, 조기 종료가 필요하면 for...of나 some으로 바꿔야 합니다.
JavaScript code
// break 와 continue
for (let i = 0; i < 3; i += 1) {
console.log(i + 14);
}중첩 루프에서는 바깥 루프에 outer:처럼 이름표를 붙여 두고 break outer로 안쪽에서 한 번에 빠져나올 수 있습니다.
while 안에서 continue를 쓰면 뒤에 있는 증가 코드를 건너뛰어 무한 루프가 됩니다. 값 갱신은 continue보다 앞에 두어야 합니다.
15배열 생성과 순회
배열 리터럴로 [3, 1, 4, 15]를 만들고, 전개로 뜬 사본을 정렬해 출력합니다. 하나씩 순회하는 대신 정렬 한 번으로 내용을 확인하는 방식입니다. 배열 리터럴에 구멍을 남기면([1, , 3]) 희소 배열이 되어 map이나 forEach가 그 자리를 건너뛰므로 length와 실제 요소 수가 어긋납니다.
JavaScript code
// 배열 생성과 순회
const values = [3, 1, 4, 15];
console.log([...values].sort((a, b) => a - b));Array.from({ length: 3 }, (_, i) => i)처럼 만들면 구멍 없는 배열이 나와 map이나 fill 없이도 바로 순회할 수 있습니다.
new Array(3)로 만든 배열은 길이만 3이고 요소가 비어 있어 map이 콜백을 한 번도 부르지 않습니다. fill로 먼저 채워야 합니다.
16맵 또는 객체 생성
중괄호 리터럴로 name과 level을 가진 객체를 만들어 값을 꺼냅니다. 구조가 고정된 레코드라면 이 형태가 편합니다. 키가 실행 중에 계속 늘어나거나 문자열이 아닌 키가 필요하면 Map이 낫고, 객체 키는 모두 문자열이나 심벌로 변환되며 정수처럼 생긴 키가 앞으로 당겨져 순서도 달라집니다.
JavaScript code
// 맵 또는 객체 생성
const user = { name: "user16", level: 16 };
console.log(user.name, user.level);Map은 size로 항목 수를 바로 알 수 있고 키 타입을 그대로 유지하지만, 일반 객체는 Object.keys(obj).length를 거쳐야 개수를 셀 수 있습니다.
객체를 사전처럼 쓸 때 "toString" 같은 키를 in으로 검사하면 프로토타입 때문에 항상 참이 나옵니다. Object.hasOwn을 써야 합니다.
17정렬 기초
정렬 코드는 없고, count 17을 5와 견주어 'big'을 고르는 삼항 연산자만 있습니다. 값 하나를 두 범주로 나눌 때 쓰는 표현식입니다. 삼항 연산자는 문이 아니라 식이라서 const 초기화 자리에 그대로 들어갈 수 있고, if 문은 그 자리에 놓을 수 없습니다.
JavaScript code
// 정렬 기초
let count = 17;
const label = count > 5 ? "big" : "small";
console.log(label);삼항 연산자는 중첩할 수 있지만 두 단계를 넘어가면 읽기 어려워집니다. 조건이 셋 이상이면 조건별 값을 담은 객체에서 꺼내 쓰는 편이 명확합니다.
삼항 연산자의 두 가지에서 서로 다른 타입을 돌려주면 뒤이어 쓰는 쪽이 매번 타입 검사를 해야 합니다. 반환 타입은 한쪽으로 맞추는 편이 좋습니다.
18튜플 또는 페어 사용
쌍을 만드는 코드 대신 for 문이 18, 19, 20을 차례로 출력합니다. 자바스크립트에는 튜플 타입이 없어서 [key, value]처럼 길이를 정해 둔 배열로 대신하고, const [a, b] = pair 구조 분해로 꺼냅니다. 이 배열은 여전히 변경 가능해서 요소를 바꾸는 것까지 막지는 못합니다.
JavaScript code
// 튜플 또는 페어 사용
for (let i = 0; i < 3; i += 1) {
console.log(i + 18);
}구조 분해는 기본값과 함께 쓸 수 있어 const [a, b = 0] = pair처럼 길이가 모자란 배열도 오류 없이 받아 낼 수 있습니다.
구조 분해로 이름을 붙일 때 순서를 바꿔 쓰면 타입이 같은 값들끼리 오류 없이 뒤섞입니다. 좌표나 시작·끝 쌍에서 특히 자주 나오는 실수입니다.
19타입 변환
타입 변환 함수는 쓰이지 않고, 숫자 배열 사본을 a - b 기준으로 정렬합니다. 다만 이 비교 함수 자체가 형 변환과 얽혀 있어서, 문자열 요소가 섞이면 뺄셈이 NaN을 내고 순서가 뒤죽박죽이 됩니다. 숫자로 바꿀 때 Number(x)는 parseInt와 달리 '12abc'를 NaN으로 돌려줍니다.
JavaScript code
// 타입 변환
const values = [3, 1, 4, 19];
console.log([...values].sort((a, b) => a - b));parseInt는 앞에서부터 읽을 수 있는 만큼만 숫자로 바꿔 "12px"에서 12를 얻습니다. 전체가 숫자여야 한다면 Number가 맞습니다.
==는 양쪽 타입을 맞춰 비교해서 0 == ""와 null == undefined가 모두 참이 됩니다. 의도한 규칙이 아니라면 ===를 써야 합니다.
20날짜 문자열 만들기
날짜 API는 등장하지 않으며, 객체 하나를 만들어 두 속성을 출력하는 코드입니다. 날짜를 문자열로 만들 때는 toISOString()이 항상 UTC 기준이라 현지 시간과 하루가 어긋날 수 있고, Date 생성자의 월 인자는 0부터 센다는 점이 이런 단순 객체 다루기와 크게 다릅니다.
JavaScript code
// 날짜 문자열 만들기
const user = { name: "user20", level: 20 };
console.log(user.name, user.level);new Date("2026-08-30")처럼 날짜만 준 문자열은 UTC 자정으로 해석되지만 슬래시로 쓴 문자열은 현지 시간으로 해석돼 결과가 하루 어긋날 수 있습니다.
new Date(2026, 8, 30)의 8은 9월입니다. 월 인자만 0부터 세고 일 인자는 1부터 세기 때문에 한 달씩 밀린 날짜를 만들기 쉽습니다.
정리하며
- const를 기본으로 선언하고 재할당이 실제로 필요한 변수에만 let을 씁니다
- 비교는 == 대신 ===로 해서 암묵적 타입 변환이 개입할 여지를 없앱니다
- 숫자 정렬에는 sort에 비교 함수를 반드시 넘기고 원본 변경 여부를 확인합니다
- 기본값은 0과 빈 문자열을 살려야 할 때 || 대신 ??로 처리합니다
더 깊이 들어가고 싶다면 JavaScript 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.