JavaScript 기초

2026. 7. 25. 21:01·알면좋은것들
반응형

다른 언어를 먼저 배우고 JavaScript로 넘어오면 이상한 경험을 한다. 문법은 대부분 익숙한데, 분명 맞게 쓴 것 같은 코드가 예상과 다른 값을 뱉는다. 빈 배열을 조건문에 넣었더니 참으로 판정되고, 객체 하나를 복사했더니 원본까지 같이 바뀐다.

이건 실수가 아니라 JavaScript가 원래 그렇게 설계된 언어라서 생기는 일이다. 그 규칙을 모르면 매번 디버깅으로 알아내야 하고, 알고 나면 코드를 쓰기 전에 결과를 예측할 수 있다.

이 글은 그 예측 가능한 기준선을 만들기 위한 정리다. 변수 선언 키워드부터 화살표 함수까지 — 나중에 "이게 왜 이렇게 되지" 싶을 때 돌아와 찾아볼 지점들을 중심으로 묶었다.

 

📖 이 글에서 다루는 내용

  1. ECMAScript와 JavaScript — 표준과 구현체
  2. 변수 — const를 기본으로 쓰는 이유
  3. 호이스팅과 TDZ — var를 버린 진짜 이유
  4. 데이터 타입 — 값이 복사되는가, 주소가 복사되는가
  5. truthy와 falsy — 조건문의 숨은 규칙
  6. ==와 === — 타입까지 볼 것인가
  7. 반복문 — for...in과 for...of를 가르는 기준
  8. 함수 — 선언식, 표현식, 화살표 함수
  9. 매개변수와 spread 문법
  10. 핵심 요약

 


1. ECMAScript와 JavaScript — 표준과 구현체

두 단어가 섞여 쓰이는 탓에 처음에는 같은 말인 줄 알기 쉽다. 하지만 둘의 관계는 설계도와 완성품에 가깝다.

구분 설명
ECMAScript Ecma International이 정의하는 표준화된 스크립트 언어 명세(스펙)
JavaScript ECMAScript 표준을 따라 구현된 구체적인 언어. 브라우저, Node.js 등에서 실행

이 구분이 실제로 쓸모 있는 순간은 버전을 이야기할 때다. "ES6 문법"이라는 말은 "ECMAScript 2015 명세에 추가된 문법"이라는 뜻이고, 그게 내 브라우저에서 동작하느냐는 별개의 문제가 된다.

알아둘 만한 버전

  • ES5 (2009) — 안정성과 생산성이 크게 향상된 버전. 오랫동안 사실상의 기준선이었다.
  • ES6 / ECMAScript 2015 — 역사상 가장 중요한 버전으로 평가된다. let/const, 화살표 함수, 템플릿 리터럴, spread 문법 등 지금 매일 쓰는 문법이 대부분 여기서 들어왔다.

💡 ES6 이후로는 매년 나온다
ES2015 이후 명세는 연 1회 발행되는 방식으로 바뀌었다. 그래서 ES7, ES8 같은 번호보다 ES2016, ES2017처럼 연도로 부르는 게 일반적이다. 아래에서 "ES6에 추가됐다"는 표현이 자주 나오는 이유는, 그 한 번의 개정이 유독 컸기 때문이다.

 


2. 변수 — const를 기본으로 쓰는 이유

식별자 규칙

  • 문자, $, _로 시작한다
  • 대소문자를 구분한다
  • 예약어(for, if, function 등)는 쓸 수 없다

네이밍 케이스

케이스 용도 예시
camelCase 변수, 객체, 함수 userName
PascalCase 클래스, 생성자 UserProfile
SNAKE_CASE (대문자) 상수 MAX_COUNT

파이썬의 snake_case가 손에 익어 있으면 여기서 자주 미끄러진다. JavaScript에서 소문자 스네이크 케이스는 관례가 아니다.

let과 const

ES6에서 추가된 두 키워드다. 차이는 세 축으로 정리된다.

const는 한 가지 제약이 더 있다. 선언과 동시에 초기값을 반드시 넣어야 한다.

const number = 10

number = 10        // TypeError: Assignment to constant variable
const number = 20  // SyntaxError: 이미 선언된 이름
const number       // SyntaxError: 초기값이 없음

블록 스코프

if, for, 함수 등 중괄호 {} 내부에서 선언한 변수는 블록 밖에서 접근할 수 없다.

let x = 1

if (x === 1) {
  let x = 2
  console.log(x)  // 2  ← 블록 안의 x
}

console.log(x)    // 1  ← 블록 밖의 x는 그대로

같은 이름이지만 완전히 다른 변수다. 안쪽 x는 블록이 끝나는 순간 사라진다.

✅ 기본은 const, 재할당이 필요해지면 그때 let
처음부터 let으로 시작하면 "이 값이 나중에 바뀌는가"를 코드만 보고는 알 수 없다. const로 선언해 두면 값이 바뀌지 않는다는 사실 자체가 문서가 된다.

⚠️ const 객체의 내용은 바꿀 수 있다
const가 막는 것은 변수에 다른 값을 다시 대입하는 것이지, 값 내부의 변경이 아니다. const arr = [1, 2] 뒤에 arr.push(3)은 정상 동작하고, arr = [3]만 에러가 난다. 이 구분은 4번 항목의 참조 타입과 이어진다.

 


3. 호이스팅과 TDZ — var를 버린 진짜 이유

ES6 이전에는 var 하나뿐이었다. var는 재선언과 재할당이 모두 되고, 블록이 아닌 함수 스코프를 따른다. 그리고 결정적으로 호이스팅(hoisting) 된다.

호이스팅은 변수 선언이 스코프 최상단으로 끌어올려진 것처럼 동작하는 현상이다. 문제는 var의 경우 선언이 올라가면서 undefined로 초기화까지 되어버린다는 점이다.

console.log(userName)     // undefined  ← 에러가 아니다
var userName = '홍길동'

console.log(userAge)      // ReferenceError: Cannot access 'userAge' before initialization
let userAge = 30

위쪽 코드는 에러 없이 조용히 넘어간다. 오타로 선언되지 않은 변수를 읽어도 프로그램이 멈추지 않고 undefined인 채로 계속 흐른다. 버그가 발생 지점에서 한참 떨어진 곳에서 터지는 전형적인 원인이다.

TDZ (Temporal Dead Zone)

여기서 흔히 하는 오해가 있다. "let과 const는 호이스팅이 안 된다"는 설명인데, 정확하지 않다.

let과 const도 호이스팅은 된다. 다만 선언문에 도달하기 전까지의 구간에서 접근하면 ReferenceError를 던지도록 설계되어 있고, 이 구간을 TDZ(Temporal Dead Zone, 일시적 사각지대) 라고 부른다.

➡️ "호이스팅이 안 된다"가 아니라 "끌어올려지되 접근이 막힌다" 가 정확한 이해다. 면접에서 자주 갈리는 지점이기도 하다.

이 설계 덕분에 let/const는 선언 전 접근을 에러로 즉시 알려준다. 조용히 undefined가 흘러가는 것보다 훨씬 낫다. var를 쓰지 않는 이유가 이것이다.

 


4. 데이터 타입 — 값이 복사되는가, 주소가 복사되는가

JavaScript의 타입은 크게 두 갈래로 나뉜다. 이 구분을 놓치면 "복사했는데 원본이 바뀌는" 현상을 만나게 된다.

  원시 타입 (Primitive) 참조 타입 (Reference)
종류 Number, String, Boolean, null, undefined Object, Array, Function
저장 방식 변수에 값이 직접 저장 객체의 주소가 저장
복사할 때 값이 복사된다 주소가 복사된다
가변성 불변 가변

 

// 원시 타입 — 값이 복사된다
let a = 10
let b = a
b = 20
console.log(a, b)      // 10 20  ← a는 그대로

// 참조 타입 — 주소가 복사된다
const obj1 = { name: 'Alice', age: 30 }
const obj2 = obj1
obj2.age = 40
console.log(obj1.age)  // 40  ← 같이 바뀐다

obj2 = obj1은 객체를 복제한 게 아니라 같은 객체를 가리키는 이름표를 하나 더 만든 것이다. 둘 중 무엇으로 접근하든 실체는 하나다.

⚠️ 얕은 복사도 완전하지 않다
const copy = { ...obj1 }로 새 객체를 만들면 1단계는 분리된다. 하지만 객체 안에 또 객체가 들어 있으면 그 안쪽은 여전히 주소만 복사된다. 중첩 구조를 완전히 떼어내려면 structuredClone(obj1)을 쓴다.

Number

정수와 실수를 구분하지 않는 하나의 숫자 타입이다. 특수한 값이 셋 있다.

  • Infinity, -Infinity
  • NaN (Not a Number) — Number(undefined), Math.sqrt(-1), 0 * Infinity 등에서 발생

⚠️ NaN === NaN은 false다
NaN은 자기 자신과도 같지 않은 유일한 값이다. 값이 NaN인지 확인하려면 비교 연산자가 아니라 Number.isNaN(value)을 쓴다.

String과 템플릿 리터럴

+ 연산자로 문자열을 이어 붙일 수 있지만, ES6부터는 템플릿 리터럴이 훨씬 읽기 좋다. 백틱으로 감싸고 ${} 안에 표현식을 넣는다.

const age = 100
const message = `홍길동은 ${age}세입니다.`

${} 안에는 변수뿐 아니라 ${age >= 20 ? '성인' : '미성년자'}처럼 표현식도 들어간다.

null과 undefined

  null undefined
의미 값이 없음을 의도적으로 표현 선언 후 값을 할당하지 않으면 자동으로 할당

💡 typeof null은 'object'다
JavaScript 초기 구현의 버그인데, 고치면 기존 웹사이트가 대거 깨지기 때문에 하위 호환을 위해 그대로 남았다. 시험과 면접의 단골 문제다.

📌 원본 노트에 원시 타입이 5개로 정리되어 있어 보충한다
현재 명세 기준 원시 타입은 7개다. 위 5개에 Symbol(ES6, 고유 식별자)과 BigInt(ES2020, 아주 큰 정수)가 추가됐다. 입문 단계에서 직접 쓸 일은 드물지만, "원시 타입이 몇 개냐"는 질문에는 7개가 정답이다.

 


5. truthy와 falsy — 조건문의 숨은 규칙

조건문과 반복문에서는 Boolean이 아닌 값도 자동으로 참/거짓으로 변환된다. 이걸 ToBoolean 형변환이라고 한다.

거짓이 되는 값은 딱 7개뿐이고, 나머지 전부가 참이다.

falsy (거짓으로 판정) 설명
false Boolean false
0, -0 숫자 0
NaN Not a Number
'' 빈 문자열
null 의도적인 빈 값
undefined 할당되지 않음

⚠️ 빈 배열 []과 빈 객체 {}는 truthy다
파이썬에서는 빈 리스트와 빈 딕셔너리가 거짓이지만, JavaScript에서는 참이다. 이 차이 때문에 if (arr)로 "배열이 비었는지" 확인하려다 항상 참이 되는 버그를 만든다. 비었는지 보려면 if (arr.length === 0)처럼 길이를 직접 확인해야 한다.
문자열 '0'과 'false'도 빈 문자열이 아니므로 truthy다.

if ([])        console.log('실행된다')     // 빈 배열 → truthy
if ({})        console.log('실행된다')     // 빈 객체 → truthy
if ('0')       console.log('실행된다')     // 비어있지 않은 문자열 → truthy
if ([].length) console.log('실행 안 된다')  // 0 → falsy

 


6. ==와 === — 타입까지 볼 것인가

할당 연산자

let a = 3
a += 10   // 13
a -= 3    // 10
a *= 10   // 100
a %= 7    // 2   ← 100 % 7

동등(==)과 일치(===)

==는 비교 전에 암묵적 형변환을 한다. ===는 타입까지 함께 본다.

console.log(1 == '1')     // true   ← '1'을 숫자로 바꿔서 비교
console.log(0 == false)   // true   ← false를 0으로 바꿔서 비교

console.log(1 === '1')    // false  ← 타입이 다르다
console.log(0 === false)  // false

✅ 항상 ===를 쓴다
==의 형변환 규칙은 예외가 많아 외우기 어렵고, 외워도 코드를 읽는 다음 사람이 모른다. 형변환이 필요하면 Number(x) === y처럼 의도를 눈에 보이게 쓰는 편이 낫다.

논리 연산자와 단축 평가

&&(and), ||(or), !(not)은 단축 평가(short-circuit) 로 동작한다. 결과가 확정되는 순간 나머지를 평가하지 않고, Boolean이 아니라 피연산자 값 자체를 반환한다.

const name = userInput || '익명'   // userInput이 falsy면 '익명'
isLoggedIn && showProfile()        // isLoggedIn이 참일 때만 호출

⚠️ ||는 0과 ''도 걸러낸다
const count = inputCount || 10에서 inputCount가 0이면 falsy이므로 10이 들어간다. "값이 없을 때만" 기본값을 주고 싶다면 null과 undefined만 걸러내는 ??(null 병합 연산자)를 쓴다. → inputCount ?? 10

 


7. 반복문 — for...in과 for...of를 가르는 기준

기본형

// while
let i = 0
while (i < 6) {
  console.log(i)
  i += 1
}

// for
for (let i = 0; i < 6; i++) {
  console.log(i)
}

for의 실행 순서는 ① 초기화 → ② 조건 검사 → ③ 본문 실행 → ④ 증감 → 다시 ②다. 증감이 본문 뒤에 온다는 점이 헷갈리는 지점이다.

for...in과 for...of

이름이 비슷해 섞어 쓰기 쉽지만, 대상도 다르고 꺼내오는 것도 다르다.

문법 대상 꺼내오는 것
for...in 객체 속성 이름(key)
for...of 반복 가능한 객체(배열, 문자열 등) 값(value)
// for...in — 객체의 key
const capitals = { korea: '서울', japan: '도쿄', china: '베이징' }
for (const capital in capitals) {
  console.log(capital)  // 'korea', 'japan', 'china'
}

// for...of — 배열의 값
const numbers = [0, 1, 2, 3]
for (const number of numbers) {
  console.log(number)  // 0, 1, 2, 3
}

배열에 for...in을 쓰면 안 되는 이유

배열에 for...in을 돌리면 값이 아니라 인덱스가, 그것도 문자열로 나온다.

const numbers = [10, 20, 30]

for (const i in numbers) {
  console.log(i, typeof i)      // '0' string / '1' string / '2' string
  console.log(i + 1)            // '01', '11', '21'  ← 문자열 연결
}

인덱스로 산술 연산을 하려던 코드가 조용히 문자열 연결로 바뀐다.

반복문에서 const를 쓸 수 있는가

for (const i = 0; i < 6; i++) { }   // TypeError — i를 재할당하기 때문
for (const item of items) { }       // 정상 — 매 반복마다 새로 선언된다

for...of와 for...in은 반복마다 변수를 새로 만들기 때문에 const를 쓸 수 있다. 일반 for문은 같은 변수를 계속 증가시키므로 let이어야 한다.

 


8. 함수 — 선언식, 표현식, 화살표 함수

기본 구조

function name(param1, param2) {
  return value
}

return이 없으면 undefined를 반환한다. 값을 돌려주는 걸 잊고 console.log만 찍어둔 함수가 undefined를 뱉는 이유가 이것이다.

세 가지 형태

  선언식 (declaration) 표현식 (expression)
형태 function add() {...} const sub = function () {...}
호이스팅 영향을 받는다 (선언 전 호출 가능) 변수 선언 규칙을 따른다
// 선언식
function add(num1, num2) {
  return num1 + num2
}

// 표현식 — 익명 함수를 변수에 할당
const sub = function (num1, num2) {
  return num1 - num2
}

✅ 표현식이 권장되는 이유
선언식은 호이스팅 때문에 정의보다 위에서 호출해도 동작한다. 편해 보이지만, 코드를 위에서 아래로 읽어 내려가는 흐름이 깨지고 "이 함수가 어디서 정의됐지"를 매번 찾게 된다. 표현식은 선언 전 호출 시 에러가 나므로 흐름이 강제된다.

화살표 함수 (ES6)

function 키워드를 줄여 쓰는 문법이다. 단계적으로 줄어든다.

const arrow1 = function (name) { return `hello, ${name}` }

// 1. function 키워드를 지우고 => 를 넣는다
const arrow2 = (name) => { return `hello, ${name}` }

// 2. 매개변수가 하나면 () 생략 가능 (생략하지 않는 쪽을 권장)
const arrow3 = name => { return `hello, ${name}` }

// 3. 본문이 한 줄이면 {} 와 return 생략 가능
const arrow4 = name => `hello, ${name}`

⚠️ 객체를 바로 반환할 때는 소괄호로 감싼다
() => { key: 'value' }는 {}를 함수 블록으로 해석해 undefined를 반환한다. 객체 리터럴임을 알리려면 괄호가 필요하다.

const returnObject = () => ({ key: 'value' })

화살표 함수에는 축약 말고 더 중요한 차이가 하나 있다. 자신만의 this를 갖지 않는다는 점인데, 이건 이벤트 핸들러에서 곧바로 문제가 되므로 다음 글의 Event 편에서 자세히 다룬다.


9. 매개변수와 spread 문법

기본값 매개변수

const greeting = function (name = 'Anonymous') {
  return `Hi ${name}`
}
greeting()  // 'Hi Anonymous'

매개변수 개수와 인자 개수가 달라도 에러가 아니다

다른 언어에서 넘어오면 가장 놀라는 부분이다. JavaScript는 개수를 검사하지 않는다.

// 매개변수 > 인자 → 빠진 자리는 undefined
const threeArgs = function (p1, p2, p3) { return [p1, p2, p3] }
threeArgs(1)      // [1, undefined, undefined]

// 매개변수 < 인자 → 초과분은 그냥 무시된다
const twoArgs = function (p1, p2) { return [p1, p2] }
twoArgs(1, 2, 3)  // [1, 2]

에러가 안 나기 때문에 인자를 하나 빠뜨린 호출이 런타임 한참 뒤에 undefined 관련 에러로 나타난다. 함수를 호출할 때 인자 개수를 눈으로 확인하는 습관이 필요한 이유다.

spread 문법 — 같은 ..., 반대 방향

...은 쓰이는 위치에 따라 역할이 정반대다. 이게 헷갈림의 원인이다.

위치 이름 동작
함수 호출 시 spread (전개) 배열을 개별 인자로 펼친다
함수 정의 시 rest parameter (나머지 매개변수) 남은 인자들을 배열로 모은다
// 호출 시 — 펼치기
function myFunc(x, y, z) { return x + y + z }
const numbers = [1, 2, 3]
console.log(myFunc(...numbers))  // 6

// 정의 시 — 모으기
const restFunc = function (param1, param2, ...restParams) {
  return [param1, param2, restParams]
}
restFunc(1, 2, 3, 4, 5)  // [1, 2, [3, 4, 5]]
restFunc(1, 2)           // [1, 2, []]  ← 남은 게 없으면 빈 배열

💡 복사와 병합에도 쓴다

const copy = [...arr]                  // 배열 복사
const merged = { ...obj1, ...obj2 }    // 객체 병합 (뒤가 우선)

단, 4번 항목에서 짚은 대로 얕은 복사다. 중첩된 객체까지 분리되지는 않는다.

세미콜론

JavaScript에는 ASI(Automatic Semicolon Insertion) 규칙이 있어 세미콜론을 생략해도 동작한다. 팀 컨벤션을 따르면 되고, 이 정리에서는 생략하는 스타일을 썼다.

 


✅ 핵심 요약

주제 꼭 기억할 것
변수 선언 기본은 const, 재할당이 필요할 때만 let. var는 쓰지 않는다
호이스팅 var는 undefined로 초기화되어 조용히 넘어가고, let/const는 TDZ에서 에러를 낸다
const의 범위 재대입만 막는다. 객체 내부 값은 바꿀 수 있다
원시 vs 참조 원시는 값이 복사되고, 참조는 주소가 복사된다. 복사한 객체를 고치면 원본도 바뀐다
falsy 7개 false, 0, -0, NaN, '', null, undefined. []와 {}는 truthy
비교 ===만 쓴다. ==는 형변환 후 비교라 예측이 어렵다
반복문 객체는 for...in(key), 배열은 for...of(value). 배열에 for...in은 문자열 인덱스가 나온다
함수 형태 표현식을 기본으로. 선언식은 호이스팅으로 코드 흐름을 흐린다
... 문법 호출 위치면 펼치기(spread), 정의 위치면 모으기(rest)
인자 개수 많거나 적어도 에러가 나지 않는다. 빠진 자리는 undefined

 

🔗 참고 자료

  • MDN — JavaScript 안내서
  • MDN — Truthy / Falsy
  • MDN — 호이스팅
  • MDN — Spread 문법
  • TC39 — ECMAScript 명세
반응형

'알면좋은것들' 카테고리의 다른 글

AJAX  (3) 2026.07.27
DOM&Event  (0) 2026.07.26
HTML & CSS 기초  (3) 2026.07.25
Git  (3) 2026.07.15
CLI  (3) 2026.07.13
'알면좋은것들' 카테고리의 다른 글
  • AJAX
  • DOM&Event
  • HTML & CSS 기초
  • Git
AglioOglio
AglioOglio
데이터분석, 딥러닝 공부하고 있습니다.
  • AglioOglio
    It것저것
    AglioOglio
  • 전체
    오늘
    어제
    • 분류 전체보기 (188)
      • Stanford CS224N Lectures (10)
        • 강의정리 (8)
        • assignment (2)
      • 논문리뷰 (13)
        • NLP (11)
        • CV (1)
      • 구현 (4)
      • DL (36)
        • CV (4)
        • NLP (11)
        • 딥러닝 기초 (3)
        • 파이토치 기초 (5)
        • LLM & Agent (8)
      • 알고리즘 (62)
        • BOJ (29)
        • SWEA (10)
        • Programmers (3)
        • 이론 (20)
      • ML (2)
      • EffectivePython_125 (21)
      • 알면좋은것들 (28)
      • DB & SQL (3)
      • AI (4)
      • 데이터분석&통계 (3)
        • 시계열분석(Time Series Analysis) (1)
      • DE (Data Engineering) (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    코딩테스트
    SWEA
    Transformer
    django
    백준
    자연어처리
    파이썬
    논문리뷰
    effective python
    알고리즘
    JS
    NLP
    Vue
    attention
    Effective
    Baekjoon
    그래프
    자료구조
    CS224N
    Python
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.3
AglioOglio
JavaScript 기초
상단으로

티스토리툴바