자바스크립트 기초 #2 변수와 타입

#1 시작과 셋업 에서 환경을 만들었으니 이제 언어 자체를 들어갑니다. 이번 글은 — 변수를 어떻게 선언하고, 자바스크립트가 어떤 타입들을 가지며, 그 사이를 오갈 때 무엇을 조심해야 하는지.

변수 선언 — letconst #

자바스크립트에서 변수를 만들 때 쓰는 키워드는 **let**과 const 두 가지입니다.

let과 const
let count = 0;        // 나중에 바꿀 수 있는 값
count = count + 1;    // OK

const name = '커티스'; // 한 번 정해지면 못 바꿈
name = '다른 이름';    // ✗ TypeError

규칙은 단순합니다 — 기본은 const, 재할당이 필요할 때만 let. 처음에는 헷갈릴 수 있지만, 익숙해지면 코드의 의도가 더 분명해져요. “이 값은 안 바뀐다"가 한눈에 보이는 것입니다.

var는 어디로 갔나요? #

옛날 자바스크립트에는 var 라는 키워드가 있었습니다. 지금도 동작하지만 새 코드에는 거의 쓰지 않습니다. var 에는 다음과 같은 함정이 있습니다.

  • 함수 단위 스코프 — 블록(if, for)을 무시함
  • 호이스팅 시 undefined로 초기화 — 선언 전에 접근해도 에러가 안 남
  • 같은 이름으로 중복 선언 가능 — 사고 위험

let/const는 이 모든 함정을 깔끔하게 정리한 후속입니다. 이 시리즈에서는 var를 거의 쓰지 않습니다.

8가지 기본 타입 #

자바스크립트의 모든 값은 다음 8가지 타입 중 하나입니다.

타입예시분류
string'hello', "world", `tpl`원시
number42, 3.14, -1원시
booleantrue, false원시
nullnull (의도적 비어 있음)원시
undefinedundefined (값이 아직 없음)원시
bigint9007199254740993n원시
symbolSymbol('id')원시
object{}, [], 함수, 클래스 인스턴스참조

앞 7개는 원시 타입(primitive), 마지막 object참조 타입(reference). 이 분류가 자바스크립트의 모든 동작을 설명하는 출발점입니다.

타입 확인 — typeof #

typeof 사용
typeof 'hello';       // 'string'
typeof 42;             // 'number'
typeof true;           // 'boolean'
typeof undefined;      // 'undefined'
typeof null;           // 'object'  ← 자바스크립트의 유명한 버그
typeof {};             // 'object'
typeof [];             // 'object'  ← 배열도 객체
typeof function(){};   // 'function' ← 사실 object지만 특별 취급

typeof null === 'object'는 언어 초기의 버그가 그대로 굳어진 것입니다. 호환성 때문에 못 고치고 있습니다. null을 검사할 때는 value === null로 직접 비교하는 게 안전합니다.

nullundefined의 차이 #

둘 다 “값이 없음"을 표현하지만 의도가 살짝 다릅니다.

  • undefined — 값이 아직 정해지지 않음. 자바스크립트가 자동으로 채워줌.
  • null — 값이 의도적으로 비어 있음. 개발자가 명시적으로 적음.
undefined가 자동으로 들어가는 경우
let x;                         // 선언만 하고 값 없음 → undefined
console.log(x);                 // undefined

const obj = { name: '커티스' };
console.log(obj.age);           // undefined (없는 속성)

function f(arg) {
  return arg;
}
f();                            // undefined (인자 없음)
null은 명시적으로 둠
let user = null;                // 아직 로그인 안 함
// ... 나중에
user = { id: 'u1', name: '커티스' };

실무에서는 함수 반환값이나 API 응답에서 두 가지 모두 마주칩니다. “있을 수도 있고 없을 수도 있는 값” 이라는 의미는 둘이 같다고 봐도 무방합니다. ==로 비교하면 둘 다 잡힙니다(다음 절).

원시 vs 참조 — 자바스크립트의 가장 중요한 차이 #

여기가 입문자가 가장 자주 막히는 부분입니다.

원시 타입은 값이 통째로 복사됩니다.

원시 — 값 복사
let a = 10;
let b = a;       // a의 값(10)을 b에 복사
b = 20;
console.log(a);  // 10 (영향 없음)
console.log(b);  // 20

참조 타입은 “어디를 가리키는지"가 복사됩니다.

객체 — 참조 복사
const obj1 = { count: 10 };
const obj2 = obj1;          // 같은 객체를 가리키게 됨
obj2.count = 20;
console.log(obj1.count);    // 20 ← obj1도 같이 바뀜
console.log(obj2.count);    // 20

obj1obj2같은 객체를 가리키는 두 이름입니다. 한쪽으로 객체를 바꾸면 다른 쪽으로 봐도 바뀐 게 보입니다. 이 차이가 자바스크립트로 일하면서 만나는 수많은 “왜 이게 같이 바뀌지?” 문제의 근원입니다.

객체를 진짜 복사하고 싶을 때 #

얕은 복사 — spread 연산자
const original = { name: '커티스', age: 30 };
const copy = { ...original };

copy.age = 31;
console.log(original.age);  // 30 (영향 없음)
console.log(copy.age);      // 31

{...original}는 객체의 1단계 속성들만 복사하는 얕은 복사(shallow copy). 중첩 객체가 있으면 그건 여전히 참조가 공유됩니다. 깊은 복사는 다음 시리즈에서 다룹니다.

형 변환 — 함정 #

자바스크립트는 타입을 자동으로 바꾸려는 경향이 있어서, 의도와 다른 결과가 나오기 쉬워요.

의도적 변환 #

명시적 변환
Number('42');      // 42
Number('hello');   // NaN (Not a Number)
String(42);        // '42'
Boolean(0);        // false
Boolean(1);        // true
Boolean('');       // false
Boolean('false');  // true ← 'false' 문자열은 비어있지 않음

Boolean 변환에 들어가는 falsy 값 7가지를 외워두면 좋습니다.

falsy 값 — 모두 false로 변환
Boolean(false);       // false
Boolean(0);           // false
Boolean(-0);          // false
Boolean(0n);          // false (bigint 0)
Boolean('');          // false
Boolean(null);        // false
Boolean(undefined);   // false
Boolean(NaN);         // false

이 7가지 외에는 모두 truthy. 빈 객체 {}와 빈 배열 []도 truthy 라는 게 헷갈리기 쉬워요.

truthy인 게 의외인 값
Boolean({});         // true
Boolean([]);         // true
Boolean('0');        // true ← 문자열 '0'은 비어있지 않음
Boolean('false');    // true

자동 변환의 함정 #

연산자가 자동으로 타입을 바꾸는 경우들이 있습니다.

자동 변환의 의외
'5' + 3;       // '53'  ← 문자열 연결로 동작
'5' - 3;       // 2     ← 숫자 연산
'5' * '2';     // 10    ← 둘 다 숫자로 변환
[] + [];        // ''
[] + {};        // '[object Object]'

+가 가장 헷갈려요. 한쪽이 문자열이면 연결, 그 외에는 합산입니다. 헷갈리면 Number()String()으로 명시적 변환을 거치는 게 안전합니다.

===== — 항상 ===를 쓰세요 #

비교 연산자가 두 가지 있습니다.

느슨한 비교 vs 엄격한 비교
// 느슨한 비교 — 자동 변환 후 비교
'5' == 5;          // true ← 문자열을 숫자로 변환해서 비교
0 == false;        // true
null == undefined; // true

// 엄격한 비교 — 타입까지 같아야 함
'5' === 5;         // false
0 === false;       // false
null === undefined;// false

거의 항상 ===를 쓰세요. 자동 변환은 직관과 어긋나는 경우가 많아 사고를 만듭니다. nullundefined를 한 번에 검사하고 싶을 때만 value == null 패턴을 쓰는 정도입니다(이건 null || undefined와 동치).

letconst의 스코프 #

마지막으로, let/const가 만드는 블록 스코프 의미.

블록 스코프
if (true) {
  const message = '안녕';
  console.log(message);  // 안녕
}
console.log(message);    // ✗ ReferenceError — 블록 밖에선 안 보임

{} 안에서 선언된 변수는 그 블록 밖에서 보이지 않습니다. for 루프, if 분기, 함수 본문 모두 같습니다. 이 덕분에 변수의 영향 범위가 좁아지고 추적이 쉬워집니다.

마무리 #

이번 글에서 정리한 내용:

  • 변수 선언은 기본 const, 재할당이 필요할 때만 let. var는 새 코드에 안 씀
  • 8가지 타입 — string/number/boolean/null/undefined/bigint/symbol/object
  • typeof null === 'object' 함정 — value === null로 직접 비교
  • 원시는 값 복사, 객체는 참조 복사. {...obj}로 얕은 복사
  • falsy 7가지: false, 0, -0, 0n, ‘’, null, undefined, NaN
  • == 대신 항상 === 사용
  • let/const는 블록 스코프

다음 글(#3 제어 흐름)에서는 if/while/for/switch 같은 분기와 반복, 그리고 모던 문법(for...of, for...in, switch의 fallthrough 함정)을 다룹니다.

X