JavaScript - let, const 키워드와 var 키워드
var 키워드 / let, const 키워드
ES5(ECMA Script 2009)까지는 var
키워드를 사용해서 변수를 선언했지만, 문제가 많아 ES6(ECMA Script 2015)에 let
과 const
가 만들어졌다고 알고 있었다. 그래서 var
키워드를 사용하지 않고 let
과 const
를 사용해왔지만, 사실 var
키워드가 정확히 어떤 문제가 있고, let
과 const
키워드가 어떻게 동작하는지 정확히 알지 못하고 있었다.
그래서 이번 스터디를 통해 배운 내용을 바탕으로 var
키워드가 어떤 문제가 있고, let
과 const
가 어떻게 동작하는지 이야기 해보려고 한다.
var 키워드의 문제점
1. 변수 중복 선언
var
키워드로 선언한 변수는 중복으로 선언이 가능하다.
var x = 10;
var x = 1000;
console.log(x); // 1000;
위의 예시처럼, 같은 스코프에서 동일한 변수 이름을 선언해도 오류가 발생하지 않는다. 개발자가 실수로 같은 이름의 변수를 중복 선언을 한다면, 원치않는 사이드 이펙트가 발생할 수도 있다.
그래서 let
과 const
키워드를 사용하면 변수 중복 선언 문제를 해결할 수 있다.
let x = 10;
const y = 20;
let x = 1000;
const y = 2000;
console.log(x); // SyntaxError: Identifier 'x' has already been declared
console.log(y); // SyntaxError: Identifier 'y' has already been declared
위의 예시처럼, let
과 const
키워드를 사용해서 변수를 선언하게 되면, 중복으로 변수를 선언했을 때, SyntaxError
문법에러가 발생하게 된다. 이 처럼, 개발자가 실수로 변수 중복 선언을 하게 되면 에러를 통해 확인이 가능하기 때문에 var
키워드 대신 let
, const
키워드를 사용해야 한다.
2. 함수 레벨 스코프와 블록 레벨 스코프
- 함수 레벨 스코프
var
키워드로 선언한 변수는 오로지 함수 코드 블록만 지역 스코프로 인정한다.
var name = 'Jay'; // 전역 스코프
if (true) { // 전역 스코프
var name = 'James';
}
function local() { // 지역 스코프
var name = 'JS';
console.log(name); // JS
}
console.log(name) // James
위의 코드를 보면, if
코드 블록 안에서 선언한 변수가 if
코드 블록의 지역 스코프로 등록되는 게 아니라, 전역 스코프에 등록이 되어 console.log(name)
을 출력했을 때, name
식별자의 값이 Jay
에서 James
로 바뀌게 된다. 이렇게 var
키워드는 함수 코드 블록만 지역 스코프로 인정하기 떄문에, 함수 코드 블록을 제외한 나머지 코드 블록들은 전부 전역 스코프에 등록된다.
- 블록 레벨 스코프
let
, const
키워드로 선언한 변수는 모든 코드 블록을 지역 스코프로 인정한다.
var name = 'Jay'; // 전역 스코프
if (true) { // 지역 스코프
var name = 'James';
console.log(name); // James
}
function local() { // 지역 스코프
var name = 'JS';
console.log(name); // JS
}
console.log(name) // Jay
위의 코드를 보면, if
코드 블록과 함수 코드 블록 모두 지역 스코프로 인정이 되기 때문에, 전역 스코프에 등록된 Jay
가 출력된다.
이 처럼, var
키워드는 함수 코드 블록만 지역 스코프로 인정하는데 반해, let
, const
키워드는 모든 코드 블록을 지역 스코프로 인정하기 때문에, 전역 변수를 남발해서 발생할 수 있는 문제점을 어느정도 해결할 수 있다.
3. 변수 호이스팅
console.log(firstName); // undefined
var firstName = 'Jay';
console.log(lastName); // ReferenceError: lastName is not defined
let lastName = 'Kim';
var
키워드로 선언하면 호이스팅으로 인해 쓰레드가 선언문에 도달하기 전에 변수를 참조할 수 있지만, 프로그램의 흐름 상 맞지 않고, 가독성도 떨어트려 개발자가 실수 할 여지를 제공한다. let
키워드를 사용하면 변수가 선언되기 전에 참조를 했을 때 ReferenceError
에러가 발생하므로, 원치않는 사이드 이펙트를 방지할 수 있다.
위의 코드를 보면, var
키워드로 선언한 변수는 호이스팅으로 인해 undefined
가 출력이 되고, let
키워드는 에러가 발생해서 호이스팅이 일어나지 않았다고 생각할 수도 있다. 하지만 호이스팅이 일어나지 않는 것 처럼 보일 뿐, let
키워드도 호이스팅이 발생한다.
아래의 예시를 자세히 살펴보자.
let name = 'Jay'; // 전역변수
{ // 코드 블록
console.log(name); // ReferenceError
let name = "james"; // 지역변수
}
1번 - 런타임 전에 자바스크립트 엔진에 의해서 전역 스코프에 name
식별자가 등록된다.
2번 - let name = 'Jay'
를 보면 name
식별자에 'Jay'
가 바로 할당되는 것 처럼 보이지만, name
식별자가 undefined
로 초기화가 되고나서 'Jay'
를 할당하게 된다.
let name;
name = 'Jay';
즉, 위의 코드처럼 두 개의 코드를 하나로 작성했기 때문에 'Jay'
가 바로 할당되는 것 처럼 보이지만, undefined
초기화가 일어난 다음에 값이 할당된다는 것을 꼭 기억해야 한다.
3번 - { }
코드 블록에 도달하게 되면, 1번에서 런타임 전에 코드를 평가한 것 처럼, 코드 블록 안에 있는 코드를 실행하기 전에 코드 블록 안에 있는 코드를 평가하게 된다. 코드 블록 안에 let name = 'james'
라는 변수 선언문이 있기 때문에, 코드 블록의 지역 스코프에 name
식별자가 등록된다. 위에서 설명했던 것 처럼, let
키워드는 블록 레벨 스코프이기 때문에 지역 스코프를 가질 수 있다.
4번 - console.log(name)
을 보고, 현재 스코프인 지역 스코프에서 먼저 name
식별자가 있는지 확인한다. 3번에서 코드 블록을 평가했을 때 지역 스코프에 name
식별자가 등록되었기 때문에 name
식별자의 값을 가져오려고 하지만, 아직 undefined
초기화가 되기 전이라서 ReferenceError
에러가 발생하게 된다.
5번 - 이 때 지역 스코프에 선언되어 있던 name
식별자의 값을 undefined
로 초기화 시켜주고, james
값을 할당해주게 된다.
{ }
코드 블록이 평가되어 지역 스코프에 식별자가 등록(3번)된 이후 부터, 식별자가 변수 선언문을 만나 undefined
초기화가 되기 전까지(5번)를 TDZ(Temporal Dead Zone), 일시적 사각지대라고 한다.
만약 let
키워드가 호이스팅이 발생하지 않았다면, console.log(name)
을 했을 때, 지역 스코프에서는 찾지 못해 전역 스코프에 있는 name
식별자의 Jay
값을 가져왔겠지만, 쓰레드가 { }
코드 블록에 도달했을 때, 코드 블록이 평가되어 지역 스코프에 name
식별자가 등록되었기 때문에 호이스팅이 발생했다고 말할 수 있다. 그래서 지역 스코프에 있는 name
식별자를 가져오려고 했지만, 아직 초기화가 되기 전인 TDZ(일시적 사각지대)에 있기 때문에 ReferenceError
가 발생하게 된다.
지금까지 내용을 바탕으로 정리를 한 번 해보자.
var
키워드를 사용하면 런타임 전에 선언 단계(스코프에 식별자 등록
)와 초기화 단계(undefined
)가 동시에 일어난다. 즉, 호이스팅으로 인해 var
키워드는 쓰레드가 선언문에 도달하기 전에 변수를 불러와도 에러가 발생하지 않고 undefined
를 가져와 코드의 흐름을 읽기 어렵게 만든다.
하지만 let
,const
키워드는 선언 단계와 초기화 단계가 분리되어 일어나기 때문에, 쓰레드가 선언문에 도달하기 전에 변수를 불러오면 초기화가 되기 전인 TDZ에 있으므로 ReferfencError
에러가 발생해 호이스팅이 발생하지 않는 것 처럼 보이고, 그로 인해 원치않는 사이드 이펙트를 방지할 수 있다. 호이스팅이 발생하지 않는 것 처럼 보이지만, 결국에 호이스팅은 발생한다는 사실을 기억해야 한다.
let 키워드와 const 키워드
let 키워드
위에서 let
키워드가 어떻게 동작하는지 살펴봤다.
- 선언 단계(
스코프에 식별자 등록
) - (TDZ)
- 초기화 단계(
undefined
) - 할당 단계(
값을 식별자에 할당
)
위의 순서대로 let
키워드를 통해 변수가 할당된다. let
키워드로 등록한 변수는 재할당이 가능하다.
let name = 'Jay';
console.log(name); // Jay
name = 'James';
console.log(name); // James
name = 100;
console.log(name); // 100
위의 코드처럼, let
키워드로 선언하고 Jay
값으로 할당된 변수 name
은 let
키워드로 선언되었기 때문에 다른 값으로 수정이 가능하다. 정확히 말하자면, let
키워드로 선언한 name
식별자는 다른 값으로 재할당이 가능하다. 재할당이라고 하면 위에 그림에 있는 할당 단계
를 계속 반복할 수 있다는 말과 같다.
반면에 const
키워드는 재할당이 불가능하다.
const 키워드
let
키워드와 다르게 const
키워드는 변하지 않는 값, 즉 상수를 선언하기 위해 사용된다. 그렇게 때문에 const
키워드로 선언된 변수는 다른 값으로 재할당을 하게 되면 에러가 발생한다. 그리고 반드시 선언과 동시에 값을 할당해줘야 한다.
- 반드시 선언과 동시에 할당
const age; // SyntaxError: Missing initializer in const declaration
const age = 100;
- 재할당 불가능
const age = 100;
age = 10; // TypeError: Assignment to constant variable.
이 처럼, const
키워드를 사용하면 재할당이 불가능하기 때문에 상태 유지와 가독성, 유지보수의 편의를 위해 많이 사용된다.
그럼 객체는 변할 수 있는 값이니까
let
키워드를 사용해서 선언하면 될까?
혹시 이런 생각을 하고 있다면, 아직 제대로 이해하지 못한 것이다.(제가 그랬습니다..)
let name = 'Jay';
name = 'James';
const age = 100;
age = 1000; // TypeError:
const person = {
name: 'Juhyeong',
age: 100,
};
person.age = 1000;
위의 코드를 통해 let
과 const
키워드에 어떤 차이가 있는지 자세히 알아보자.
그림에서는 런타임 이전에 스코프에 식별자를 등록하는 단계(선언 단계)와 초기화 단계를 생략했다.
1번 - 런타임 전에 let
키워드로 등록 된 name
식별자를 undefined
로 초기화 후 메모리에 있는 'Jay'
값을 변수에 할당해줬다.
2번 - name
식별자는 let
키워드로 선언되었기 때문에 재할당이 가능하다. 그래서 name = 'James'
를 보고 다른 메모리 공간에 James
값을 변수에 재할당해줬다.
3번 - 런타임 전에 const
키워드로 등록 된 age
식별자를 undefined
로 초기화 후 메모리에 있는 100
값을 변수에 할당해줬다. 바로 다음에 age = 1000
코드를 보면, age
변수는 const
키워드로 선언되었기 때문에 값을 재할당하지 못한다. 그래서 그림처럼 재할당할 수 없고 TypeError
에러가 발생하게 된다.
4번 - 런타임 전에 const
키워드로 등록 된 person
식별자를 undefined
로 초기화 후, 이번엔 객체이기 때문에 스택 메모리가 아닌 힙 메모리에 객체값을 저장하고, 객체값에 해당하는 힙 메모리 주소가 스택 메모리 값으로 저장된다. 결국 person
변수는 힙 메모리 주소를 값으로 가지고 있다.
5번 - const
로 선언한 person
식별자가 가리키는 힙 메모리 주소를 통해 객체의 age
프로퍼티 값을 1000
으로 수정한다. 이 때 person
변수의 스택 메모리 값이 바뀌는 게 아니기 때문에 오류가 발생하지 않는다.
만약 person = 'animal'
처럼 스택 메모리의 값을 바꾸게 된다면, 재할당이 발생하기 때문에 const
키워드로 선언된 person
변수는 재할당을 할 수 없다. 그러므로 3번처럼 TypeError
에러가 발생하게 된다.
다시 질문으로 돌아가 보자.
그럼 객체는 변할 수 있는 값이니까
let
키워드를 사용해서 선언하면 될까?
위의 질문에 대한 대답을 하면, 객체라서 let
키워드로 등록하는 게 아니라, 변수에 재할당이 필요하다면 let
키워드를 사용해서 변수를 선언하는 것이다. 만약 재할당이 필요없는 변수라면 const
키워드로 선언하면 된다. 결국에 원시타입이던 객체타입이던 상관없이, 재할당이 필요한 경우에만 let
키워드로 선언하면 된다.
그럼 객체는 값이 변할 수 있는데 왜 굳이
const
키워드를 사용할까?
왜냐하면 const
키워드로 선언하게 되면 객체의 프로퍼티가 추가/수정/삭제될 수는 있어도 재할당을 할 수 없기 때문에, 객체라는 자료형은 변하지 않는다. 그래서 해당 변수가 객체타입에서 다른 타입으로 변할 이유가 없다면 const
키워드를 사용해서 변수를 선언하면 된다.
결론적으로, 변수를 선언할 때 const
키워드를 주로 사용하고, 재할당이 필요한 경우에만 let
키워드를 사용하면 된다. var
키워드는 특별한 이유가 있는 게 아닌 이상 사용을 지양해야 한다.
Author And Source
이 문제에 관하여(JavaScript - let, const 키워드와 var 키워드), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@wngud4680/JavaScript-let-const-키워드와-var-키워드저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)