본문 바로가기
JS

모던 자바스크립트 Deep Dive - let, const 키워드와 블록 레벨 스코프

by 학식러 2024. 1. 13.

 

15장 let, const 키워드와 블록 레벨 스코프

 

var 키워드로 선언한 변수의 문제점

1) 변수 중복 선언 허용

var는 중복 선언이 가능

예제

var x = 1;
var y = 1;

var x = 100;
var y; // 초기화문이 없는 변수 선언문은 무시된다.

console.log(x);  // 100
console.log(y);  // 1

 

2) 함수 레벨 스코프

var는 함수 블록만을 지역 스코프로 인정한다.

따라서 함수 외부에서 var로 선언한 변수는 모두 전역변수가 된다.

예제

var x = 1;

if(true) {
	var x = 10;
}

console.log(x);  // 10

 

예제

var i = 10;

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

console.log(i); // 5

 

함수 스코프는 전역 변수를 남발할 가능성이 높다.

(함수 스코프 이외의 스코프는 모두 전역변수 처리되기 때문)

⇒ 전역 변수 중복 선언 발생

 

3) 변수 호이스팅

var는 호이스팅이 작동한다.

예제

console.log(foo); // undefined

foo = 123;

console.log(foo); // 123

var foo;

에러는 안나지만 프로그램의 흐름에 맞지 않고 가독성도 떨어뜨린다.

 

let 키워드

1) 변수 중복 선언 금지

var는 변수 중복 선언해도 에러가 발생하지 않는다.

하지만 의도치 않은 재할당이라는 부작용이 발생한다.

그래서 let은 이름이 같은 변수를 같은 스코프 내에서 중복 선언하면 문법 에러를 발생시켜준다.

예제

var foo = 123;
var foo = 456;

let bar = 123;
let bar = 456; // SyntaxError: Identifier 'bar' has already been declared

 

2) 블록 레벨 스코프

var는 함수 스코프만을 지역 스코프로 인정한다.

하지만 let은 모든 블록(함수, if문, for문, while문, …)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.

예제

let foo = 1;
{
	let foo = 2;
	let bar = 3;
}

console.log(foo); // 1
console.log(bar); // ReferenceError: bar is not defined

 

3) 변수 호이스팅

var와는 달리 let은 호이스팅이 발생하지 않는 것처럼 동작한다.

예제

console.log(foo); // ReferenceError: foo is not defined
let foo;

 

var에서는 선언하고 그 즉시 undefined로 초기화 된다.

하지만 let은 선언단계와 초기화 단계가 분리되어 진행된다.

초기화가 실행되기 전에 변수에 접근하려고 하면 참조 에러가 발생한다.

let으로 선언한 변수는 스코프 시작지점부터 초기화 단계 시작 지점(변수 선언문)까지 변수를 참조할 수 없다. 그 구간을 일시적 사각지대(TDZ: Temporal Dead Zone)이라고 부른다.

예제

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

let foo;
console.log(foo); // undefined

foo = 1;
console.log(foo); // 1

 

let은 변수 호이스팅이 발생하지 않는 것처럼 보인다. 하지만 그렇지 않다.

예제

let foo = 1; // 전역 변수

{
	console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
	let foo = 2;
}

만약 let이 호이스팅이 발생하지 않는다면 전역 변수 4번 줄에서 전역변수foo의 값을 출력해야한다.

하지만 let도 호이스팅이 발생하기 때문에 참조에러가 발생한다.

아랫줄에 let foo = 2;가 들어감으로써 블록 내에서 foo가 선언이 되었다는 것을 의미한다.

하지만 console시점에서 초기화가 되지 않았으므로

TDZ구간에서 console을 하려고 시도하는 것은 ReferenceError가 된다.

 

만약 let foo = 2가 없었다면 에러는 발생하지 않는다.

호이스팅 할 변수가 없기 때문에 전역 변수를 참조하기 때문이다.

예제

let foo = 1; // 전역 변수

{
	console.log(foo); // 1
}

 

4) 전역 객체와 let

var 전역변수, 전역함수, 선언하지 않은 변수에 값을 할당한 암묵적 전역은 전역 객체 window의 프로퍼티가 된다. 전역객체의 프로퍼티를 참조할 때 window는 생략가능하다.

예제

var x = 1; //전역변수
y = 2; //암묵적 전역

//전역함수
function foo() {}

console.log(window.x); // 1
console.log(x); // 1

console.log(window.y); // 2
console.log(y); // 2

console.log(window.foo); // f foo() {}
console.log(foo); // f foo() {}

 

하지만 let 전역변수는 전역객체의 프로퍼티가 아니다.

⇒ window.foo와 같이 접근할 수 없다.

let전역변수는 보이지 않는 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드)내에 존재하게 된다.

-23장 실행컨텍스트에서 자세히

예제

let x=1;
console.log(window.x); // undefined
console.log(x); // 1

 

const 키워드

const는 let과 대부분 동일하므로 let과 다른점.

 

1) 선언과 초기화

반드시 선언과 동시에 초기화 해야한다.

const foo = 1;
const foo; // SyntaxError

 

let과 마찬가지로 블록레벨 스코프를 가지고, 변수 호이스팅이 발생하지 않는것처럼 동작

{
	console.log(foo); // ReferenceError
	const foo=1;
	console.log(foo); // 1
}

console.log(foo); // ReferenceError

 

2) 재할당 금지

const foo = 1;
foo = 2; // TypeError

 

3) 상수

const 변수에 원시 값을 할당한 경우 변수 값을 변경할 수 없다.

 

4) const 키워드와 객체

const 변수에 원시 값을 할당한 경우 값을 변경할 수 없다.

하지만 객체를 할당한 경우 값을 변경할 수 있다.

객체는 재할당 없이도 직접 변경 가능하기 때문이다.

예제

const person = {
	name: 'Lee'
};

person.name = 'Kim';

console.log(person); // {name: "Kim"}

const는 재할당을 금지하지만 프로퍼티의 동적 생성, 삭제, 프로퍼티 값의 변경을 통해 객체를 변경하는 것은 가능하다.

 

var vs. let vs. const

일단 const를 사용하고 재할당이 필요하다면 그 때 let으로 변경해도 늦지 않다.

댓글