반응형
변수 선언이란?
변수(Variable)는 데이터를 저장하는 메모리 공간에 이름을 붙인 것입니다. JavaScript에서는 var
, let
, const
세 가지 키워드로 변수를 선언할 수 있습니다.
🔍 기본 문법
var variableName = value;
let variableName = value;
const variableName = value;
🕰️ 역사적 배경
- ES5 (2009):
var
만 존재 - ES6 (2015):
let
과const
추가 - 현재:
let
과const
사용 권장
var 키워드
📖 var의 기본 특징
// var로 변수 선언
var message = "안녕하세요";
var count = 10;
var isActive = true;
console.log(message); // "안녕하세요"
console.log(count); // 10
console.log(isActive); // true
🔄 var의 주요 특징
1. 재선언 가능
var name = "김철수";
console.log(name); // "김철수"
var name = "이영희"; // 재선언 가능
console.log(name); // "이영희"
2. 재할당 가능
var score = 80;
console.log(score); // 80
score = 95; // 재할당 가능
console.log(score); // 95
3. 함수 스코프
function example() {
if (true) {
var x = 1;
}
console.log(x); // 1 (접근 가능)
}
example();
// console.log(x); // ReferenceError: x is not defined
4. 호이스팅
console.log(hoistedVar); // undefined (에러 아님)
var hoistedVar = "값";
console.log(hoistedVar); // "값"
// 위 코드는 실제로 이렇게 동작함:
// var hoistedVar;
// console.log(hoistedVar); // undefined
// hoistedVar = "값";
// console.log(hoistedVar); // "값"
⚠️ var의 문제점
1. 예상치 못한 동작
// 클로저 문제
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3 출력
}, 100);
}
// 예상: 0, 1, 2
// 실제: 3, 3, 3
2. 전역 객체 오염
var globalVar = "전역 변수";
console.log(window.globalVar); // "전역 변수" (브라우저에서)
let 키워드
📝 let의 기본 특징
ES6에서 도입된 let
은 var
의 문제점을 해결합니다.
// let으로 변수 선언
let username = "사용자";
let age = 25;
let isLoggedIn = false;
console.log(username); // "사용자"
console.log(age); // 25
console.log(isLoggedIn); // false
🔧 let의 주요 특징
1. 재선언 불가능
let city = "서울";
// let city = "부산"; // SyntaxError: Identifier 'city' has already been declared
2. 재할당 가능
let temperature = 20;
console.log(temperature); // 20
temperature = 30; // 재할당 가능
console.log(temperature); // 30
3. 블록 스코프
function example() {
if (true) {
let y = 1;
console.log(y); // 1
}
// console.log(y); // ReferenceError: y is not defined
}
example();
4. 개선된 호이스팅
// console.log(letVar); // ReferenceError: Cannot access 'letVar' before initialization
let letVar = "값";
console.log(letVar); // "값"
✅ let의 장점
1. 클로저 문제 해결
// let을 사용한 해결
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2 출력
}, 100);
}
2. 전역 객체 오염 방지
let globalLet = "전역 변수";
console.log(window.globalLet); // undefined (브라우저에서)
const 키워드
📌 const의 기본 특징
const
는 상수(constant)를 선언할 때 사용하며, 재할당이 불가능합니다.
// const로 상수 선언
const PI = 3.14159;
const APP_NAME = "MyApp";
const MAX_USERS = 100;
console.log(PI); // 3.14159
console.log(APP_NAME); // "MyApp"
console.log(MAX_USERS); // 100
🔒 const의 주요 특징
1. 선언 시 초기화 필수
// const name; // SyntaxError: Missing initializer in const declaration
const name = "김철수"; // 올바른 선언
2. 재선언 불가능
const brand = "Apple";
// const brand = "Samsung"; // SyntaxError: Identifier 'brand' has already been declared
3. 재할당 불가능
const version = "1.0.0";
// version = "2.0.0"; // TypeError: Assignment to constant variable.
4. 블록 스코프
if (true) {
const secret = "비밀";
console.log(secret); // "비밀"
}
// console.log(secret); // ReferenceError: secret is not defined
🔍 const와 객체/배열
객체의 속성은 변경 가능
const person = {
name: "김철수",
age: 30
};
// 객체 자체는 재할당 불가
// person = {}; // TypeError
// 하지만 속성은 변경 가능
person.name = "이영희";
person.age = 25;
console.log(person); // { name: "이영희", age: 25 }
배열의 요소는 변경 가능
const fruits = ["사과", "바나나"];
// 배열 자체는 재할당 불가
// fruits = []; // TypeError
// 하지만 요소 변경은 가능
fruits.push("오렌지");
fruits[0] = "딸기";
console.log(fruits); // ["딸기", "바나나", "오렌지"]
완전한 불변성을 위한 Object.freeze()
const config = Object.freeze({
apiUrl: "https://api.example.com",
timeout: 5000
});
// config.apiUrl = "다른 URL"; // 무시됨 (strict mode에서는 에러)
console.log(config.apiUrl); // "https://api.example.com"
스코프(Scope) 비교
🔍 스코프란?
스코프는 변수에 접근할 수 있는 범위를 의미합니다.
📊 스코프 비교표
특징 | var | let | const |
---|---|---|---|
스코프 | 함수 스코프 | 블록 스코프 | 블록 스코프 |
호이스팅 | undefined로 초기화 | TDZ | TDZ |
재선언 | ✅ 가능 | ❌ 불가능 | ❌ 불가능 |
재할당 | ✅ 가능 | ✅ 가능 | ❌ 불가능 |
🔬 스코프 실험
function scopeTest() {
// 함수 스코프
var varVariable = "var 변수";
if (true) {
// 블록 스코프
var varInBlock = "블록 안의 var";
let letInBlock = "블록 안의 let";
const constInBlock = "블록 안의 const";
console.log(varInBlock); // "블록 안의 var"
console.log(letInBlock); // "블록 안의 let"
console.log(constInBlock); // "블록 안의 const"
}
console.log(varVariable); // "var 변수"
console.log(varInBlock); // "블록 안의 var" (접근 가능)
// console.log(letInBlock); // ReferenceError
// console.log(constInBlock); // ReferenceError
}
scopeTest();
🔄 반복문에서의 동작
// var 사용 시
for (var i = 0; i < 3; i++) {
// 각 반복마다 같은 i 변수 사용
}
console.log(i); // 3 (접근 가능)
// let 사용 시
for (let j = 0; j < 3; j++) {
// 각 반복마다 새로운 j 변수 생성
}
// console.log(j); // ReferenceError
호이스팅(Hoisting) 이해하기
🔄 호이스팅이란?
호이스팅은 변수와 함수 선언이 컴파일 타임에 스코프의 최상단으로 끌어올려지는 JavaScript의 특성입니다.
📋 var 호이스팅
// 실제 코드
console.log(hoistedVar); // undefined
var hoistedVar = "값";
// 실제 동작 방식
var hoistedVar; // undefined로 초기화
console.log(hoistedVar); // undefined
hoistedVar = "값";
🚨 let/const 호이스팅
// TDZ (Temporal Dead Zone)
console.log(hoistedLet); // ReferenceError
let hoistedLet = "값";
console.log(hoistedConst); // ReferenceError
const hoistedConst = "값";
🕰️ TDZ(Temporal Dead Zone) 설명
// TDZ 시작
// console.log(temp); // ReferenceError
let temp = "값"; // TDZ 끝
console.log(temp); // "값"
재선언과 재할당
📊 재선언/재할당 비교표
// 재선언 테스트
var name1 = "김철수";
var name1 = "이영희"; // ✅ 가능
let name2 = "김철수";
// let name2 = "이영희"; // ❌ SyntaxError
const name3 = "김철수";
// const name3 = "이영희"; // ❌ SyntaxError
// 재할당 테스트
var age1 = 20;
age1 = 30; // ✅ 가능
let age2 = 20;
age2 = 30; // ✅ 가능
const age3 = 20;
// age3 = 30; // ❌ TypeError
🔍 같은 이름 변수 선언
// 다른 스코프에서는 가능
function example() {
let x = 1;
if (true) {
let x = 2; // 다른 스코프이므로 가능
console.log(x); // 2
}
console.log(x); // 1
}
실무 사용 가이드라인
🎯 언제 어떤 키워드를 사용할까?
1. const 우선 사용
// 변경되지 않는 값들
const API_URL = "https://api.example.com";
const MAX_RETRY_COUNT = 3;
const DEFAULT_SETTINGS = {
theme: "dark",
language: "ko"
};
2. 재할당이 필요하면 let
// 반복문의 카운터
for (let i = 0; i < items.length; i++) {
// ...
}
// 조건에 따라 변경되는 값
let message = "";
if (isSuccess) {
message = "성공!";
} else {
message = "실패!";
}
3. var는 피하기
// ❌ var 사용 지양
var legacyVariable = "구식 방법";
// ✅ let 또는 const 사용
const modernConstant = "현대적 방법";
let modernVariable = "현대적 방법";
📋 명명 규칙
// 상수는 대문자와 언더스코어
const MAX_FILE_SIZE = 1024 * 1024;
const API_BASE_URL = "https://api.example.com";
// 일반 변수는 camelCase
let userName = "김철수";
let isLoggedIn = false;
const userProfile = {
name: "김철수",
email: "user@example.com"
};
// 클래스는 PascalCase
const UserManager = class {
constructor(name) {
this.name = name;
}
};
🔒 불변성과 코드 안정성
// 설정 객체는 const + Object.freeze
const CONFIG = Object.freeze({
database: {
host: "localhost",
port: 5432,
name: "myapp"
},
cache: {
ttl: 3600,
maxSize: 100
}
});
// 배열 불변성 유지
const initialTodos = Object.freeze([
{ id: 1, text: "할 일 1", done: false },
{ id: 2, text: "할 일 2", done: true }
]);
// 새 배열 생성으로 변경
const updatedTodos = initialTodos.map(todo =>
todo.id === 1 ? { ...todo, done: true } : todo
);
완전한 예제
🎉 실제 프로젝트 예제
// 애플리케이션 설정 (const)
const APP_CONFIG = {
name: "Todo App",
version: "1.0.0",
api: {
baseUrl: "https://api.todoapp.com",
timeout: 5000
},
ui: {
theme: "light",
language: "ko"
}
};
// 상태 관리 클래스
class TodoManager {
constructor() {
// private 변수들 (const로 초기화)
const STORAGE_KEY = "todos";
const MAX_TODOS = 100;
// 인스턴스 변수들 (let 사용)
let todos = this.loadTodos();
let currentFilter = "all";
// 메소드에서 클로저로 접근
this.getTodos = () => todos;
this.getFilter = () => currentFilter;
// 공개 메소드들
this.addTodo = (text) => {
if (todos.length >= MAX_TODOS) {
throw new Error(`최대 ${MAX_TODOS}개까지만 추가 가능합니다.`);
}
const newTodo = {
id: Date.now(),
text: text,
completed: false,
createdAt: new Date()
};
todos = [...todos, newTodo];
this.saveTodos();
return newTodo;
};
this.deleteTodo = (id) => {
const originalLength = todos.length;
todos = todos.filter(todo => todo.id !== id);
if (todos.length === originalLength) {
throw new Error("삭제할 할 일을 찾을 수 없습니다.");
}
this.saveTodos();
};
this.toggleTodo = (id) => {
todos = todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
);
this.saveTodos();
};
this.setFilter = (filter) => {
const validFilters = ["all", "active", "completed"];
if (!validFilters.includes(filter)) {
throw new Error(`유효하지 않은 필터: ${filter}`);
}
currentFilter = filter;
};
this.getFilteredTodos = () => {
switch (currentFilter) {
case "active":
return todos.filter(todo => !todo.completed);
case "completed":
return todos.filter(todo => todo.completed);
default:
return todos;
}
};
// private 메소드들
this.loadTodos = () => {
try {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
} catch (error) {
console.error("할 일 목록 로드 실패:", error);
return [];
}
};
this.saveTodos = () => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
} catch (error) {
console.error("할 일 목록 저장 실패:", error);
}
};
}
}
// 사용 예제
const todoManager = new TodoManager();
// 이벤트 핸들러들 (let 사용)
let addButton = document.getElementById("addBtn");
let todoInput = document.getElementById("todoInput");
// 상수로 DOM 요소 참조
const todoList = document.getElementById("todoList");
const filterButtons = document.querySelectorAll(".filter-btn");
// 할 일 추가 함수
function addTodo() {
const text = todoInput.value.trim();
if (!text) {
alert("할 일을 입력해주세요.");
return;
}
try {
todoManager.addTodo(text);
todoInput.value = "";
renderTodos();
} catch (error) {
alert(error.message);
}
}
// 할 일 렌더링 함수
function renderTodos() {
const todos = todoManager.getFilteredTodos();
// 기존 목록 초기화
todoList.innerHTML = "";
// 할 일이 없을 때
if (todos.length === 0) {
const emptyMessage = document.createElement("li");
emptyMessage.textContent = "할 일이 없습니다.";
emptyMessage.className = "empty-message";
todoList.appendChild(emptyMessage);
return;
}
// 각 할 일 렌더링
todos.forEach(todo => {
const li = document.createElement("li");
li.className = `todo-item ${todo.completed ? "completed" : ""}`;
li.innerHTML = `
<span class="todo-text">${todo.text}</span>
<div class="todo-actions">
<button onclick="toggleTodo(${todo.id})" class="toggle-btn">
${todo.completed ? "취소" : "완료"}
</button>
<button onclick="deleteTodo(${todo.id})" class="delete-btn">
삭제
</button>
</div>
`;
todoList.appendChild(li);
});
}
// 전역 함수들 (window 객체 오염 방지를 위해 네임스페이스 사용 고려)
function toggleTodo(id) {
todoManager.toggleTodo(id);
renderTodos();
}
function deleteTodo(id) {
if (confirm("정말 삭제하시겠습니까?")) {
todoManager.deleteTodo(id);
renderTodos();
}
}
// 이벤트 리스너 등록
addButton.addEventListener("click", addTodo);
todoInput.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
addTodo();
}
});
// 필터 버튼 이벤트
filterButtons.forEach(button => {
button.addEventListener("click", () => {
// 활성 버튼 변경
filterButtons.forEach(btn => btn.classList.remove("active"));
button.classList.add("active");
// 필터 적용
const filter = button.dataset.filter;
todoManager.setFilter(filter);
renderTodos();
});
});
// 초기 렌더링
document.addEventListener("DOMContentLoaded", () => {
renderTodos();
});
// 통계 정보 표시
function updateStats() {
const allTodos = todoManager.getTodos();
const completedCount = allTodos.filter(todo => todo.completed).length;
const totalCount = allTodos.length;
const statsElement = document.getElementById("stats");
if (statsElement) {
statsElement.textContent =
`전체: ${totalCount}, 완료: ${completedCount}, 미완료: ${totalCount - completedCount}`;
}
}
// 정기적으로 통계 업데이트
setInterval(updateStats, 1000);
마무리
JavaScript의 변수 선언 키워드 var
, let
, const
는 각각 고유한 특성을 가지고 있습니다. 현대적인 JavaScript 개발에서는 const를 우선 사용하고, 재할당이 필요한 경우에만 let을 사용하는 것이 권장됩니다.
✅ 핵심 정리
- const: 재할당 불가, 블록 스코프, 초기화 필수
- let: 재할당 가능, 블록 스코프, 재선언 불가
- var: 함수 스코프, 호이스팅 이슈, 사용 지양
🚀 모범 사례
- const 우선 사용 → 코드 안정성 향상
- let으로 필요시 재할당 → 명확한 의도 표현
- var 피하기 → 예상치 못한 동작 방지
- 명시적 초기화 → 가독성과 디버깅 용이성
변수 선언은 JavaScript의 기본 중의 기본입니다. 이 개념을 확실히 이해하고 넘어가면 더 복잡한 JavaScript 기능들을 학습할 때 큰 도움이 될 것입니다!
변수 선언에 대한 설명이 명확했나요? 더 궁금한 점이나 어려운 부분이 있다면 댓글로 남겨주세요! 💬
'JavaScript > 기초 문법과 자료형' 카테고리의 다른 글
JavaScript 연산자 완전 정리 - +, -, ==, ===, &&, || 마스터하기 (1) | 2025.05.16 |
---|---|
JavaScript 자료형 완전 가이드 - 문자열, 숫자, 불린, 배열, 객체 마스터하기 (3) | 2025.05.16 |