[모던 자바스크립트 Deep Dive] - (2) 변수 선언 키워드 var, let, const

JIN·2024년 9월 24일
post-thumbnail

변수 선언 키워드

이전 포스팅에서 변수에 대해 정리하고, 변수 선언에 대해 알아보았습니다.

자바스크립트에서 변수를 선언하는 키워드는 var, let, const 세가지가 있는데 하나씩 어떤 특징이 있는지 정리해보겠습니다.

📢 var

ES5까지 자바스크립트에서 변수를 선언하는 유일한 방법은 var였습니다. 그러나 var 키워드로 선언된 변수는 몇 가지 특징 때문에 예기치 못한 문제를 일으킬 수 있어 현재는 사용을 권장하지 않고, 실제 사이드 프로젝트를 진행하는 동안에도 한번도 사용하지 않았던 기억이 있습니다.

특징 1) 변수 중복 선언 허용

var x = 'hi';
var x = 'hello';

consoel.log(x) // hello

위와 같이 var로 선언한 변수는 동일한 스코프 내에서 중복 선언을 허용됩니다.
맨 처음 x에 'hi'를 할당하고 다음 'hello'를 할당하면 최종적으로는 'hello'가 출력됩니다.
이렇게 중복 선언이 된다면 의도치 않게 이전에 선언한 변수의 값이 덮어써져 예상한 값이 아닌 다른 값을 만나 볼 수도 있습니다.

특징 2) 함수 레벨 스코프
var 키워드로 선언된 변수는 오직 함수의 코드 블록만을 지역 스코프로 인식합니다.
그렇기 때문에 함수 외부에서 var로 선언한 변수는 코드 블록 내에서 재선언되더라도 모두 전역 변수로 간주됩니다.

쉽게 표현하자면 각각 키워드는 본인이 활동할 수 있는 영역이 정해져 있는데, var는 모든 영역에서 활동할 수 있다고 생각할 수 있습니다.

var x = 'hi';

if (true) {
	var x = 'hello';
}

consoe.log(x); // hello

위 예시에서 if 블록 내에서 조건에 따라 x를 선언하기를 원하지만, var를 사용해 선언을 하게 되면 어디서 선언을 하더라도 전역 변수로 간주됩니다. 그래서 x에 할당된 값이 'hi'에서 'hello'로 덮어써지게 되고 의도와 다르게 변수의 값이 변경되어 예상과 다른 결과를 불러올 수 있습니다.

var i = 10;

for (var i = 0; i < 5; i++) {
	console.log(i); // 0 1 2 3 4 
}

console.log(i) // 5

위 예시에서도 for문에서 사용된 i는 전역 변수로 간주되며, for문 내부에서 i를 다시 선언하게 되면 이전에 선언된 i 변수가 덮어씌워집니다. 결과적으로 for문 외부에서도 i는 5로 남아있게 되며 이는 반복문 내에서만 사용하려던 변수가 의도치 않게 전역 변수가 되는 문제를 일으킬 수 있습니다.

이와 같이 var함수 레벨 스코프를 따르고, 중복 선언이 가능해 의도치 않게 전역 변수가 덮어씌워지거나 예상하지 못한 결과가 발생할 수 있다는 문제가 있었고, 자바스크립트는 ES6부터 새로운 키워드인 letconst를 도입하게 되었습니다.

📢 let

var 키워드의 단점을 보완하기 위해 ES6에서는 새로운 변수 선언 키워드인 letconst가 도입되었습니다. 그 중 letvar의 문제점들을 해결하고 보다 안정적인 변수 선언을 가능하게 합니다.

특징 1) 변수 중복 선언 금지
동일한 이름으로 변수를 선언해도 아무런 에러가 발생하지 않았던 var와는 달리 let은 같은 이름으로 변수를 중복선언하면 문법 에러 (SyntaxError)가 발생하게 됩니다.

let x = 'hi'

let x = 'hello' // SyntatError: Identifier 'x' has already been declared

위 코드에서 let으로 선언된 x 변수를 동일한 이름으로 다시 선언하려고 하면, 자바스크립트는 오류를 발생시키며 중복 선언을 허용하지 않습니다.
이렇게 중복 선언을 막아주는 덕분에 유지 보수성이 좋아지고, 변수 중복으로 인해 발생할 수 있는 예기치 않은 문제를 사전에 방지해 줄 수 있습니다.

특징 2) 블록 레벨 스코프
함수 레벨 스코프인 var와 달리 let 키워드는 모든 코드 블록(함수, if문, for문, while문 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따릅니다.

// 전역변수 name
let name = 'jin'; 

{ 
	// 지역변수 name, age
	let name = 'jin'; 
    let age = 29;
}

console.log(name); // 'jin'
console.log(age); // ReferenceError: age is not defined

위 예시에서 {} 블록 안에서 선언된 name과 age는 블록 내부에서만 유효한 지역 변수입니다.
따라서 블록 내부에서는 name과 age에 접근할 수 있지만, 블록 외부에서는 age 변수에 접근할 수 없기 때문에 ReferenceError가 발생합니다.

또한, 전역 스코프에서 선언된 name 변수와 블록 내부의 name 변수는 서로 별개의 변수로 취급되어, 값이 충돌하지 않고 독립적으로 존재할 수 있습니다.

var보다는 조금 더 작은 영역을

이처럼 let은 중복 선언을 방지하고 블록 레벨 스코프를 지원하여, 코드의 예측 가능성과 안정성을 높일 수 있습니다.

🔥 스코프

스코프(Scope)는 변수에 접근할 수 있는 범위를 의미합니다. 자바스크립트에서 변수는 선언된 위치에 따라 접근할 수 있는 범위가 달라지며, 이를 스코프라고 부릅니다. 스코프는 크게 전역 스코프, 함수 레벨 스코프, 블록 레벨 스코프로 나눌 수 있습니다.

1. 전역 스코프 (Global Scope): 전역에서 선언된 변수는 코드 어디에서나 접근할 수 있습니다. 즉, 함수 내부와 외부 모두에서 참조 가능합니다.

2. 함수 레벨 스코프 (Function-Level Scope): 함수 내부에서 선언된 변수는 해당 함수 안에서만 접근 가능합니다. 함수 외부에서는 접근할 수 없습니다. var 키워드는 함수 레벨 스코프를 따릅니다.

3. 블록 레벨 스코프 (Block-Level Scope): letconst로 선언된 변수는 블록({}) 안에서만 접근할 수 있습니다. 이 범위를 벗어나면 변수는 참조할 수 없습니다.

let globalScope = "전역 스코프";

function outer() {
    let outerScope = "함수 레벨 스코프";

    function inner() {
        let innerScope = "블록 레벨 스코프";
        console.log(globalScope); // "전역 스코프" 
        console.log(outerScope); // "함수 레벨 스코프"
        console.log(innerScope); // "블록 레벨 스코프"
    }

    inner();
    console.log(globalScope); // "전역 스코프"
    console.log(outerScope); // "함수 레벨 스코프"
    console.log(innerScope); // ReferenceError: innerScope is not defined
}

outer();

console.log(globalScope); // "전역 스코프"
console.log(outerScope); // ReferenceError: outerScope is not defined
console.log(innerScope); // ReferenceError: innerScope is not defined

globalScope는 전역 스코프에 선언된 변수로서 어디서든 참조가 가능합니다.
outerScope는 함수 레벨 스코프에 선언된 변수로서 outer 함수 내에서만 참조가 가능하며, 함수 외부에서는 참조할 수 없습니다.
innerScope는 inner 함수의 블록 내부에서만 접근할 수 있는 변수입니다. 블록을 벗어나면 접근할 수 없기 때문에 외부에서 참조하려고 하면 오류가 발생합니다.

이처럼 스코프는 변수의 유효 범위를 제한하여 예기치 않은 변수의 충돌을 방지하는 역할을 합니다.

📢 const

const 키워드는 상수(constant) 를 선언할 때 사용되며, let과 유사한 점이 있지만 몇 가지 중요한 차이점을 가지고 있습니다. const로 선언된 변수는 아래와 같은 특징이 있습니다.

특징 1) 선언과 초기화
const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화를 진행해야 합니다. 그렇지 않으면 아래 age와 같이 문법 에러(SyntaxError)가 발생합니다.

const name = 'jin';

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

특징 2) 재할당 금지
let이나 var와 달리, const로 선언된 변수는 재할당이 금지됩니다. 한 번 값을 할당한 후에는 동일한 변수에 새로운 값을 할당할 수 없습니다.

const name = 'jin';

name = 2 // TypeError: Assignment to constant variable.

특징 3) 상수
const는 상수를 표현하는 데 주로 사용됩니다. 상수는 변경되지 않는 값을 저장하는 변수로, 가독성과 유지보수성을 높이는 데 유용합니다.

BEFORE)

let price = 100;

let afterPrice = price + (price * 0.1);

console.log(afterPrice) // 110

위 코드에서 사용된 0.1이 무엇을 의미하는지 명확하지 않아 가독성이 떨어집니다. 이를 상수로 정의하면 의미를 명확히 할 수 있고, 유지보수도 더 쉬워진다는 장점이 있습니다.

AFTER)

const tax = 0.1

let price = 100;

let afterPrice = price + (price * tax);

console.log(afterPrice) // 110

이처럼, 상수는 값이 고정된 데이터를 표현할 때 사용되며, 상태 유지, 가독성, 유지보수성 측면에서 이점을 제공합니다.

특징 4) const키워드와 객체
const로 선언된 변수에 원시값(Primitive Value) 을 할당한 경우, 해당 값을 변경할 수 없습니다.
그러나 객체(Object) 를 할당한 경우에는 객체의 프로퍼티를 변경할 수 있습니다. 즉, const는 변수 자체의 재할당을 금지할 뿐, 객체의 내부 값을 변경하는 것은 허용합니다.

const person = { 
	name: 'choi'
};

person.name = 'kim';

console.log(person) // {name: 'kim'}

위 코드에서 person 객체 자체는 재할당할 수 없지만, 객체의 프로퍼티인 name을 변경하는 것은 가능합니다. const는 객체의 참조값을 고정하지만, 객체 내부의 프로퍼티는 수정할 수 있는 점을 기억해야 합니다.

📢 각 키워드별 변수 호이스팅

앞서 변수에 대해 설명하며 변수 호이스팅에 대해서 간략하게 설명드렸는데요, 각 키워드별로 호이스팅이 어떻게 이루어지는지 자세하게 정리해보겠습니다.

1) var 키워드의 변수 호이스팅

var키워드로 선언한 변수는 호이스팅에 의해 변수가 마치 선언된 위치와 관계없이 스코프의 최상단에서 선언된 것처럼 동작합니다. 즉, 변수를 선언하기 전에 사용할 수 있게 만들며, 이 경우 변수는 undefined로 초기화됩니다.

// 1. 선언 단계 - 변수 호이스팅에 의해 name 변수 선언 

// 2. 초기화 단계 - 변수 name은 undefined로 초기화 
console.log(name); // undefined

// 3. 할당 단계 - 변수 name에 값 할당 
name = 'jin';

console.log(name); // 'jin'

// 변수 선언은 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 실행
var name;

하지만 이렇게 변수 선언문 이전에 변수를 참조하는 것은 호이스팅에 의해 에러가 발생하지는 않지만, 프로그램의 흐름상 맞지 않고 가독성을 떨어뜨리고 오류를 발생시킬 여지를 남기게 됩니다.

2) let과 const 키워드의 변수 호이스팅

letconst 키워드는 호이스팅이 발생하지 않는 것처럼 동작합니다. 사실 자바스크립트에서 모든 선언(변수, 함수, 클래스)은 호이스팅되지만, letconst로 선언된 변수는 선언 이후부터 사용할 수 있기에 발생하지 않는 것처럼 느껴지는 것입니다.

console.log(name); // ReferenceError: name is not defined
console.log(age); // ReferenceError: age is not defined

let name;
const age;

위 코드에서 처럼 letconst로 선언된 변수들은 선언문 이전에 참조하려고 하면 참조 에러(ReferenceError) 가 발생합니다. 이는 letconst일시적 사각지대(Temporal Dead Zone, TDZ) 를 가지기 때문입니다.

🚨일시적 사각지대 (TDZ)
letconst로 선언된 변수의 변수 선언이 스코프의 선두로 끌어올려지는 것은 var와 같지만, letconst는 초기화 단계가 선언문에 도달한 이후에야 실행됩니다.
따라서 초기화가 이루어지기 전에는 참조할 수 없으며 변수 선언문까지는 변수를 참조할 수 없습니다.

이 선언부터 초기화까지의 구간을 일시적 사각지대(TDZ) 라고 부르며, 이 구간에서 변수를 참조하려 하면 참조 에러가 발생합니다.

let name = 'jin'; // 전역변수

{
	console.log(name); // ReferenceError: Cannot access 'name' before initialization
    let name = 'peter'; // 지역변수
}

위 코드에서 전역 변수 name을 출력하려고 했을 때 참조에러가 발생하는 이유는, 블록 내부에서 let으로 새로운 name 변수를 선언했기 때문입니다. 호이스팅은 발생하지만 초기화가 이루어지지 않아 일시적 사각지대에 놓여 있어 에러가 발생하는 것입니다.

선언 단계: 변수는 선언되었으나 초기화되지 않았고, 일시적 사각지대에 있음
일시적 사각지대 (TDZ): 초기화 전까지 변수를 참조할 수 없는 구간
초기화 단계: 변수 선언문에 도달했을 때 변수가 초기화
참조 가능 시점: 변수 선언문 이후부터 변수를 참조할 수 있음

결론적으로, 자바스크립트는 모든 선언문(var, let, const, function, class 등)을 호이스팅하지만, letconst일시적 사각지대(TDZ) 에 의해 선언 이후에만 참조가 가능하므로, 호이스팅이 발생하지 않는 것처럼 보이는 것입니다.

⭐️ 마무리

딥다이브 두번째 정리로 자바스크립트에서 변수 선언을 하는 키워드들에 대해 정리해 보았습니다. 마지막으로 각 키워드별로 특징들을 간단하게 정리하며 끝내도록 하겠습니다!

var

  • 중복선언이 가능
  • 선언 후 초기화 진행
  • 마지막으로 할당된 값이 저장되어 기존 변수가 재할당될 수 있다.
  • 함수 스코프를 따르며, 의도치 않은 값 변경이 발생 가능하다.

let

  • 중복선언이 불가능
  • 이미 선언된 변수를 재선언하면 문법 에러 (SyntaxError)가 발생하지만, 재할당은 가능하다.
  • 블록 스코프를 따르며, 선언된 블록 내에서만 변수가 유효하다.

const

  • 중복선언이 불가능
  • 선언과 동시에 초기화가 필요하다.
  • 원시값의 경우 재할당이 불가능하지만, 객체나 배열과 같은 참조 타입의 경우 내부 값은 변경 가능하다.
  • 블록 스코프를 따르며, 선언된 블록 내에서만 변수가 유효하다.

출처: 모던 자바스크립트 Deep Dive 15장 let,const 키워드와 블록 레벨 스코프 (208p ~ 218p)


profile
MAXIMUM EFFORT 🙃

0개의 댓글