PracticeEveryday
JavaScript 본문
var 키워드
- var 키워드는 JavaScript ES5까지 변수를 선언할 수 있는 키워드로 사용되었다.
var 키워드의 특징
1. 변수의 중복 선언이 가능하다.
var name = 'kem';
var name = 'lee';
console.log(name) // lee
- 이러한 코드는 변수 선언부 2줄이 가까이 붙어있으니 name이 두번 선언 되어있구나를 알 수 있지만
첫 번째 name 선언과 두 번째 name 선언 사이에 500줄의 코드가 있다면 문제가 심각해 질 수 있다.
이런 변수의 중복 선언 허용은 의도하지 않은 변수의 변경이 일어날 가능성이 충분하기 때문이다.
2. 호이스팅 대상이다.
- 호이스팅은 쉽게 이야기해서 스코프 안에 있는 선언들을 모두 스코프의 최상단으로 끌어 올리는 것을 의미한다.
호이스팅은 JavaScript 인터프리터가 코드를 해석할 때 변수 및 함수의 선언 처리, 실제 코드 실행의 두 단계로
나누어서 추리하기 때문에 발생하는 현상이다.
console.log(name); // undefined
var name = "kim";
위의 코드는 호이스팅으로 인해 아래와 같은 방식으로 작동한다.
var name
console.log(name); // undefined
var name = "kim";
- 아래와 같은 코드는 JavaScript 인터프리터 내부적으로 코드를 이런 ' 방식 '으로 처리 한다는 것이지 실제 코드
라인에서 변경되거나 하는 것은 아니다.
함수 레벨 스코프
(function () {
var local = 1;
})();
// console.log(local);
// Uncaught ReferenceError: local is not defined
for (var i = 0; i < 10; i++) {}
console.log(i); // 10
- JavaScript는 함수 스코프 레벨을 사용하기에 for문 내부에서 선언된 변수 i도 외부에서 참조 가능하다!!
var 키워드 생략 가능
- 변수를 선언할 때 var 키워드를 붙혀도 되고 안 붙혀도 된다.
자유의 상징 JavaScript 답다.
var globalVariable = 'global!';
if (globalVariable === 'global!') {
globlVariable = 'global?' // 오타 냄
}
console.log(globalVariable) // global!
console.log(globlVariable) // global?
- 실수로 globalVariable 변수를 globlVariable 변수로 오타를 냈다.....
let 과 const의 특징
- var 키워드의 경우 전역 변수를 남발하기가 쉽고 또 로컬 변수라고 해도 변수의 스코프가 너무 넓기 때문에
변수의 선언부와 호출부가 너무 떨어져 있거나 값이 의도하지 않게 바뀌는 경우를 추적하기가 힘들다.
그래서 2015년에 발표한 ES6에는 새로운 변수 선언 키워드 let과 const가 추가되었다.
- let 키워드는 var과 마찬가지로 변수를 선언할 때 사용하는 키워드이고 const 키워드는 상수를 선언할 때 사용하는
키워드 이다. 즉 const 키워드는 리터럴 값의 재할당이 불가능 하다.
const value = "kim";
value = "lee"; //TypeError: Assignment to constant variable.
var 키워드와 let const의 차이점을 알아보자
var의 특징
변수의 중복 선언이 가능하다.
호이스팅 당한다.
블록 레벨 스코프가 아닌 함수 레벨 스코프를 사용한다.
var 키워드는 생략이 가능하다.
const let 키워드들의 특징
1. 변수의 중복 선언이 불가능 하다.
const value = "kim";
value = "lee"; //TypeError: Assignment to constant variable.
var name = "kim";
var name = "lee"; // 아무 일도 일어나지 않았다...
let name = "kim";
let name = "lee";
// Uncaught SyntaxError: Identifier 'name' has already been declared
const name2 = "kim";
const name2 = "lee"; // Uncaught SyntaxError: Identifier 'name' has already been declared
- 이 두 키워드는 var 키워드와 다르게 한 번 키워드를 사용해서 선언한 변수는 재 선언이 불가능하다.
2. 호이스팅 당한다.?
- 이 부분이 관련 내용을 작성한 분이 포스팅을 한 이유다. let이나 const 키워드가 호이스팅 되지 않는 줄 알았지만
위에서 설명했든 호이스팅은 JavaScript 인터프리터가 코드를 해석하는 과정에서 발생하는 일이기에 let이나
const 라고 한 들 피해갈 수 있을 리가 없었다.
이 방법을 어떻게 해결했을까?
// 변수 선언 키워드에 따라 다른 에러가 발생한다.
console.log(name);
var name = "kim";
//console.log(aaa); aaa is not defined
//console.log(name2);
//ReferenceError: Cannot access 'name2' before initialization
let name2 = "lee";
- 첫 번째는 호이스팅 당한 var 키워드를 사용하여 선언한 변수를 호출한 모습니다.
당연히 참조 에러가 나지 않고 undefined가 출력 된다.
- 두 번째는 아예 선언한 적이 없는 변수를 호출하는 모습니다. aaa는 없는 변수라고한다. // aaa is not defined
- 세 번째는 let 키워드를 사용하여 변수를 선언부 이전에 호출한 모습니다. 두 번째와 마찬가지로 에러가 발생했다.
// Cannot access 'name2' before initialization
V8 엔진을 보자
- 이 두개의 에러는 전혀 다른 에러로 V8 엔진 내부에서 사용하는 MESSAGE_TEMPLATE에도 엄밀히 구분되어 있고
실제 호출 되는 케이스도 다르다.
T(NotDefined, "% is not defined")
T(AccessedUninitializedVariable, "Cannot access '%' before initialization")
- 내부적으로 var 키워드로 선언된 JS 객체와 let const로 선언된 JS 객체를 분기로 갈라놓은 코드가 많았다.
var, let, const 중 어떤 키워드를 사용하여 값을 선언하여도 호이스팅은 항상 이루어진다는 것을 알 수 있었다.
V8 엔진 내부의 호이스팅 플래그인 should_hoist 값을 JavaScript 객체에 할당할 때 변수 선언 키워드에 대한
구분을 하지 않고 무조건 true를 할당 한다.
- 그렇다면 var 키워드와 let const 키워드의 차이는 어디서 오는 것일까?
변수를 선언할 때 즉 V8이 변수 객체를 생성할 때는 전부 동일하게 처리하지만 변수를 위해 메모리 공간을 확보하는
초기화 단계에서 이 키워드들을 다르게 처리한다.
static InitializationFlag DefaultInitializationFlag(VariableMode mode) {
DCHECK(IsDeclaredVariableMode(mode));
return mode == VariableMode::kVar ? kCreatedInitialized
: kNeedsInitialization;
}
- 그러나 이후 진행 로직을 보면 DefaultInitializationFlag라는 함수를 통해 V8 엔진 내부에서 사용되는 VariableKind라는
타입을 반환하는데, 이 때 var 키워드를 사용하여 선언한 변수는 kCreatedInitialized 값을, 그 외의 let const 키워드는
kNeedsInitializtion 키워드를 반환하고 있다.
=> 즉 let const 키워드로 선언한 리터럴 값은 호이스팅은 되나 특별한 이유로 인해 "초기화가 필요한 상태 "로
관리되고 있다.
초기화가 필요한 상태?
- JavaScript 인터프리터 내부에서 변수는 총 3단계에 걸쳐 생성된다.
1. 선언 ( Declaration ) : 스코프와 변수 객체가 생성되고 스코프가 변수 객체를 참조한다.
2. 초기화 ( Initialization ) : 변수 객체가 가질 값을 위해 메모리에 공간을 할당한다. 이 때 초기화 되는 값은
undefined이다.
3. 할당 ( Assignment ) : 변수 객체에 값을 할당한다.
- var 키워드를 사용하면 선언한 객체의 경우 선언과 초기화가 동시에 이루어진다. 선언이 되지마자 undefined
값으로 초기화 된다는 것이다!!
// v8/src/parsing/parser.cc
// Var 모드로 변수 선언 시
auto var = scope->DeclareParameter(name, VariableMode::kVar, is_optional,
is_rest, ast_value_factory(), beg_pos);
var->AllocateTo(VariableLocation::PARAMETER, 0);
- V8 엔진의 코드를 보녀 kVar 모드로 변수 객체를 생성한 후 바로 AllocateTo 메소드를 통해 메모리에 공간을
할당하는 모습을 볼 수 있다. 그러나 let이나 const 키워드로 생성한 변수 객체는 다르다
// v8/src/parsing/parser.cc
// kLet 모드로 변수 선언 시
VariableProxy* proxy =
DeclareBoundVariable(variable_name, VariableMode::kLet, class_token_pos);
proxy->var()->set_initializer_position(end_pos);
// Const 모드로 변수 선언 시
VariableProxy* proxy =
DeclareBoundVariable(local_name, VariableMode::kConst, pos);
proxy->var()->set_initializer_position(position());
- kLet 모드나 kConst 모드로 생성한 변수 객체들은 AllocateTo 메소드가 바로 호출되지 않고
대신 소스 코드 상에서 해당 코드의 위치를 의미하는 position 값만 지정해주는 것을 볼 수 있다.
- 바로 이 타이밍에 let 이나 const 키워드로 생성된 변수들이 TDZ( Temporal Dead Zone ) 구간에 들어가는 것이다.
=> 즉 TDZ 구간에 들어가는 변수 객체는 선언은 되어 있지만 아직 초기화 되지 않아 변수에 담길 값을 위한
공간이 메모리에 할당되지 않은 상태
라고 할 수 있다. 이 때 해당 변수에 접근을 시도하면 얄짤 없이 Cannot access ' % ' before Initialization 에러가 뜬다!
블록 레벨 스코프
- 함수 레벨 스코프를 사용하는 var 키워드와 다르게 let과 const는 블록 레벨 스코프를 사용한다.
var 키워드는 블록 레벨 스코프를 사용하지 않기 때문에 블록 내부에서 선언한 변수 또한 전역 변수로 취급된다.
var globalVariable = "I am global";
if (globalVariable === "I am global") {
var globalVariable = "am I local?";
}
console.log(globalVariable); // am I local?
그러니 let과 const 키워드의 경우 블록 내부에서 선언한 변수는 지역 변수로 취급한다.
let globalVariable = 'I am global';
if (globalVariable === 'I am global') {
let globalVariable = 'am I local?';
let localVariable = 'I am local';
}
console.log(globalVariable); // I am global
console.log(localVariable); // Uncaught ReferenceError: localVariable is not defined
- 위 경우 블록 내부에서 선언된 localVariable은 지역 변수로 취급되어 전역 스코프에서는 참조가 불가능 하다.
- 참고로 let과 const는 호이스팅도 블록 단위로 발생한다.
let name = 'Global Evan';
if (name === 'Global Evan') {
console.log(name); // Uncaught ReferenceError: Cannot access 'name' before initialization
let name = 'Local Evan';
}
- 이 경우 if문 내부에서도 전역 변수로 선언한 name 변수 값인 Global Evan이 출력 될 것이라 생각 할 수 있지만
if문 블록 내부에서도 호이스팅이 발생하여 지역 변수인 name 선언부가 블록 최상단으로 끌어올려졌기 때문에
참조 에러가 발생한다!!
let const 키워드는 생략 불가능 하다.
name = 'kim'
// 상기 코드는
var name = 'kim'
// 과 같다
- 변수 선언 키워드를 사용하지 않으면 var 키워드를 사용한 것으로 취급하기 때문에 무조건 써줘야 한다.
const 키워드의 특징
1. 상수를 선언할 때 사용한다.
- 위에서 말했은 const는 상수를 선언할 때 사용하는 키워드이다.
상수는 어떤 불변의 값을 의미한다. 즉, 한 번 const 키워드를 사용하여 선언한 값은 두번 다시 변경할 수 없다는
뜻이다.
const maxCount = 30;
maxCount = 40; // Uncaught TypeError: Assignment to constant variable.
2. 참조 타입을 할당할 때에는 다른 점이 있다.
const obj = { name: "Evan" };
obj = { name: "John" }; // Uncaught TypeError: Assignment to constant variable.
- 위의 경우에는 obj 변수가 바라보는 값 자체를 변경하려고 했기에 에러가 발생한다.
- 그러나 객체 내부의 프로퍼티들은 const 키워드의 영향을 받지 않는다.
- name 프로퍼티 ( 변수 )는 가변성을 띄기 때문이다!!
3. 반드시 선언과 동시에 초기화 해주어야 한다.
- let 키워드의 경우 명시적으로 선언만 했더라도 인터프리터가 해당 코드 라인을 해석함과 동시에
묵시적으로는 undefined가 할당되며 초기화 된다.
let hi;
console.log(hi); // undefined
- 그러나 const의 경우 반드시 선언과 동시에 값을 할당해 주어야 한다!!
const hi2
console.log(hi2)
// SyntaxError: Missing initializer in const declaration
※ 만약 let 키워드를 사용해야한다면 절대 전역 스코프에서는 사용하지말고 가능하면 블록 스코프를 작게 만들고 그 내부에서 사용하는 것을 추천한다.
※ 그리고 const 키워드의 경우 값을 재할당하려고 하면 바로 에러를 뿜뿜 해주기 때문에 개발자가 의도하지 않게 변수의 값이 재할당되는 슬픈 상황을 방지할 수 있다.
JavaScript의 let과 const, 그리고 TDZ
이번 포스팅에서는 JavaScript ES6에서 추가되었던 과 키워드에 대해서 자세히 포스팅하려고 한다. 부끄럽지만 지금까지 필자는 과 는 호이스팅이 되지 않는다고 생각하고 있었다. 하지만 얼마 전
evan-moon.github.io
'JavaScript' 카테고리의 다른 글
JavaScript (0) | 2022.06.03 |
---|---|
JavaScript (0) | 2022.06.02 |
JavaScript (0) | 2022.05.19 |
JavaScript (0) | 2022.05.18 |
JavaScript (0) | 2022.05.18 |