변수

Created
April 9, 2024
Tags
JavaScript

ES5 문법에서 변수 선언은 var 키워드를 사용했다. 이후 2015년 ES6 문법부터는 let, const 키워드가 도입된다. var가 가지고 있던 단점들을 해결하는 더 나은 대안으로 인정받고 있다.

JavaScript에서 변수 선언 방식인 var, let, const에 대하여 알아보자.

image

var

1. 함수 스코프를 가지며, 함수 내에서 선언된 변수는 함수 전체에 접근 가능하다.

변수가 선언된 함수 내에서만 유효하며, 함수 외부에서는 접근할 수 없다는 의미이다. 함수 외부에서 선언되었다면 전역(global scope)으로 사용될 수 있다.

var name = 'Heungmin';

function getNumber() {
	var number = 7;
}

console.log(name); // Heungmin
console.log(number); // Uncaught ReferenceError: number is not defined

name은 함수 외부에서, number는 함수 내부에서 var로 선언된 변수들이다. 따라서 변수 name은 함수 외부에서 접근할 수 있지만, number는 함수 외부에서 접근하려고 하면 ReferenceError가 발생한다.다.

2. 블록 스코프를 무시하고, 변수가 호이스팅 되어 선언 전에도 사용할 수 있다.

var는 블록 스코프를 무시하고 함수 스코프를 가지기 때문에, 블록 내에서 선언된 var 변수는 블록 외부에서도 접근 가능하다. 이것이 호이스팅 개념과 관련이 있다.

function getNumber() {
  if (true) {
    var number = "I'm number 7.";
  }
  
  console.log(number); // 블록 외부에서 `var` 변수에 접근 가능
}

getNumber(); // I'm number 7.

변수 numberif 블록 내에서 선언되었다. 하지만 console.log(number);는 블록 외부에서 호출되었다. var는 함수 스코프를 가지므로 블록 스코프를 무시하고 함수 전체 즉, 블록 외부에서 접근가능하다.

그리고 이 코드가 실행될 때 호이스팅이 일어나서 함수가 실행되기 전에 변수 선언이 끌어올려진다.

function getNumber() {
  var number; // 호이스팅: 변수가 선언되고 undefined로 초기화됨
  if (true) {
    number = "I'm number 7."; // 실제 할당은 여기서 이루어짐
  }

  console.log(number); // 블록 내에서 선언한 'var' 변수를 외부에서 접근 가능
}

getNumber(); // I'm number 7.

var가 가지고 있는 특성 중 호이스팅은 할당 이전에 언제나 undefined를 반환하기 때문에 에러를 발생시키지 않지만, 프로그램의 가독성을 떨어뜨리고 어디서 에러가 발생했는지 예측하기 어렵게 만든다.

3. 중복 선언이 가능하며, 값을 재할당할 수 있다.

만약 var로 선언한 전역변수 이름과 지역변수 이름이 같아 충돌한다면?

var name = "Global"; // 전역 변수

function example() {
  var name = "Local"; // 전역 변수와 동일한 이름의 지역 변수 중복 선언
  console.log(name); // Local (지역 변수 참조하여 지역 변수 출력)
}

example();
console.log(name); // Global (전역 변수 출력)

위 코드에서는 동일한 이름의 변수 name이 전역 변수, 지역 변수로 선언되었다. 함수 내에서 name을 출력하면 함수 스코프 내에 있는 지역 변수 name이 출력된다. 이 지역 변수는 함수 내부에서만 유효하며 전역 변수와 이름이 같더라도 서로 영향을 주지 않는다. 또한, 함수 외부에서 name을 출력하면 전역 변수 name이 출력된다. 함수 내부에서 선언된 지역 변수와 전역 변수는 서로 독립적이기 때문에 함수 호출에 따라 전역 변수의 값이 변하지 않는다.

만약 A 개발자가 name이라는 변수를 이미 선언했는데, 이를 모르고 있던 B 개발자가 중복 선언해서 값을 할당한다면?

var name = 'Heungmin';
console.log(name); // Heungmin

var name = 'Kangin';
console.log(name); // Kangin

이렇게 var는 같은 이름으로 여러 번 선언되어도 런타임 시 에러가 발생하지 않고, 마지막 선언이 이전의 선언을 덮어쓰는 특성을 가지고 있다. 하지만 이러한 특성은 코드의 가독성과 유지보수를 어렵게 만들고, B 개발자는 의도치 않게 변경된 값을 받게 된다.

🔍

글로벌 오염 주로 변수의 스코프에 관련하여 사용되는 용어이다. 글로벌 오염은 전역 스코프에서 변수를 선언할 때 발생하는 문제를 나타낸다. 이런 문제는 특히 var를 사용할 때 발생한다. var의 함수 스코프 동작은 예상치 못한 변수 충돌이나 동작을 일으킬 수 있기 때문에 let 또는 const를 사용하여 지역 스코프를 명시적으로 지정해서 전역 스코프에서 변수를 오염시키는 문제를 최소하하는 것이 좋다.

let

1. 블록 스코프를 가진다.

변수가 선언된 블록(일반적으로 중괄호 {}로 둘러싸인 영역) 내에서만 유효하여, 블록 외부에서는 접근할 수 없다는 의미이다.

function example() {
	let x = 10; // 함수 스코프를 가지는 변수 x

	if (true) {
		let y = 20; // 블록 스코프를 가지는 변수 y
		console.log(x); // 10 (함수 스코프이므로 블록 내에서도 접근 가능)
		console.log(y); // 20 (블록 스코프 내에서 선언된 변수이므로 접근 가능)
	}

	console.log(y); // ReferenceError: y is not defined (블록 외부에서 접근 불가)
}

example();
console.log(x); // ReferenceError: x is not defined (함수 외부에서 접근 불가)

함수 스코프를 가지는 변수 x와 블록 스코프를 가지는 변수 y가 선언되었다. 변수 xexample 함수 내에서만 유효하며, 변수 y는 블록 내에서만 유효하다.

  • console.log(x);: 함수 스코프를 가지는 변수 x에 접근하여 값을 출력했다. 함수 스코프이므로 블록 내에서도 접근 가능합니다.
  • console.log(y);: 블록 스코프를 가지는 변수 y에 접근하여 값을 출력했다. 블록 내에서 선언되었기 때문에 블록 내에서만 접근 가능하다.
  • // console.log(y);: y는 블록 외부에서 접근했기 때문에 에러가 발생한다.
  • // console.log(x);: x는 함수 외부에서 접근했기 때문에 에러가 발생한다.

2. 호이스팅이 일어나지만, 초기화되기 전에는 접근할 수 없다.

let은 평가 단계에서 선언만 이루어지며, 이후 실행 단계에서 선언문을 만나면 값이 초기화된다.

console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
console.log(x); // 10

따라서 변수 x는 호이스팅에 의해 선언은 끌어올려지지만, 초기화 전에 접근하기 때문에 ReferenceError가 발생한다.

3. 중복 선언은 불가능하며, 재할당은 가능하다.

같은 스코프 내에서 let으로 선언된 변수 이름을 중복해서 선언하면 문법 에러가 발생한다. 또한, let 변수는 재할당이 가능하다.

let name = 'Heungmin';
let name = 'Kangin'; // Uncaught SyntaxError: Identifier 'name' has already been declared

let league = 'EPL';
league = 'League 1';
console.log(league); // League 1

변수 name은 같은 스코프 내에서 중복 선언되었으므로 문법 에러가 발생했다. 코드 천 줄 만 줄 짜다 보면 중복된 변수 만들 수 있을텐데 let을 사용하면 방지할 수 있겠다. 그리고 재할당이 가능하므로 League 1이라는 값이 출력된다.

const

1. 블록 스코프를 가지며, 선언과 동시에 초기화해야 한다.

constlet과 마찬가지로 블록 스코프를 가지며, 호이스팅이 일어나지 않는 것처럼 동작한다. 그리고 선언과 동시에 초기화하지 않으면 문법 에러가 발생한다.

const x; // SyntaxError: Missing initializer in const declaration

2. 재할당이 불가능하다. 즉, 선언 후에 값이 변경될 수 없다.

const는 재할당이 금지된다. const로 선언된 변수에 원시 값을 할당한 경우 값을 변경할 수 없다. 이러한 특성을 이용해 상수를 선언하는 키워드로 사용하여 가독성과 유지보수성을 보장한다.

// 3.14의 의미를 알기 어렵기 때문에 가독성이 좋지 않음
if (round > 3.14) {
	...
}

// 값의 의미를 명확히 기록하여 가독성이 향상됨
const PI = 3.14;
if (round > PI) {
	...
}

// 재할당하면 타입 에러가 발생
PI = 3.14159; // TypeError: Assignment to constant variable.

3. 객체 내부의 속성이나 배열의 요소는 변경 가능하다.

const는 객체와 배열에도 사용할 수 있다. 물론 이때도 재할당은 금지된다. 이는 const로 선언된 변수의 타입이 객체나 배열인 경우, 객체나 배열에 대한 참조를 변경하지 못한다는 것을 의미한다. 하지만 이때 중요한 점, 객체의 프로퍼티나 배열의 요소는 보호되지 않는다. 즉, 재할당은 불가능하지만 할당된 객체나 배열의 내용(프로퍼티, 요소)은 변경할 수 있다.

const person = {
  name: "John",
  age: 30
};

console.log(person.name); // John

// 객체 내 속성 변경 가능
person.name = "Jane";
console.log(person.name); // Jane

// 객체 전체 재할당은 불가능
// person = { name: "Bob", age: 25 }; // TypeError: Assignment to constant variable.

const numbers = [1, 2, 3, 4];
console.log(numbers); // [1, 2, 3, 4]

// 배열 요소 변경 가능
numbers[0] = 5;
console.log(numbers); // [5, 2, 3, 4]

// 배열 전체 재할당은 불가능
// numbers = [1, 2, 3, 4, 5]; // TypeError: Assignment to constant variable.

객체 person을 선언하고 초기화했다. const는 객체 내부 속의 속성을 변경하는 것을 허용하는 특성에 따라 객체 내 속성 name을 변경했다. 하지만, const는 객체 전체를 다른 객체로 재할당하는 것은 허용하지 않기 때문에 에러가 발생했다.

배열도 마찬가지다. 배열 number를 선언하고 초기화한 후에 배열 내 요소를 변경했다. 하지만, 배열 전체를 재할당하면 에러가 발생했다.

이러한 특성은 const를 사용할 때 객체나 배열의 내부를 자유롭게 변경할 수 있으면서도, 변수가 다른 객체나 배열을 가리키지 못하도록 보장하는 역할을 한다.

정리

  1. 변수 선언에는 기본적으로 const를 사용한다. 변경이 발생하지 않는(재할당이 필요 없는 상수) 원시 값과 객체, 배열에는 const를 사용한다.
  2. 재할당이 필요한 경우에 한정해 let을 사용한다. 이때 반드시 재할당이 필요한지 생각해 볼 필요가 있다.
  3. ES6 이상 버전을 사용한다면 var는 사용하지 않는 것이 좋다.
    1. var의 함수 스코프 동작은 변수 충돌을 일으키기 때문에 전역 변수를 오염시키는 문제가 있다. 이로 인해 의도하지 않은 값으로 변경될 수 있다.
    2. var가 가지고 있는 특징 중 호이스팅은 할당 이전에 언제나 undefined를 반환하기 때문에 에러를 발생시키지 않지만, 프로그램의 가독성을 떨어뜨리고 어디서 에러가 발생했는지 예측하기 어렵게 만든다.
  4. 이러한 특징들을 고려하여 변수를 선택하면 코드의 가독성과 유지보수성을 향상시킬 수 있다.