[JavaScript] 변수와 스코프
자바스크립트를 처음 접했을 때 신기하고 당혹스러웠던 점
변수타입은 var 하나로 올클리어가 된다는 점과,
절차적으로 변수와 함수를 선언하지 않아도 특정 범위 내에서 접근이 가능하다는 점이다.
이번 노트에는 신기하고 당혹스러웠던 두 점에 대한 원리를 적어보려 한다.
Variable
특정 변수 유형에 값을 할당하게 되면, 브라우저의 자바스크립트 엔진이 할당된 값을 분석하여 실제 값의 유형을 내부적으로 지정하는 방식으로 동작한다.
우리가 다른 언어처럼 굳이 유형을 지정하지 않아도 된다는 간편함이 있지만,
브라우저 엔진이 값의 할당에 대하여 자동으로 유형을 지정해주기 때문에 성능적으로 좋은 코드를 위하여 개발자가 직접 고려해야 할 부분이 있으므로 주의해서 다루어야 할 것이다.
현재 자바스크립트에서 표현할 수 있는 변수유형은 이전까지는 var 하나였지만, ECMA2015 이후로 let과 const 유형이 추가되어 총 3가지 방식으로 변수를 선언할 수 있다.
let과 const
var는 다음의 문제를 갖고 있었다.
- 이미 만들어진 변수 이름으로 재선언했는데 문제발생 없음.
 - Hoisting으로 인해 ReferenceError에러가 발생하지 않는다.
1var a = 'var';2var a = 'let'; // 같은 변수명으로 재선언해도 아무문제 발생하지 않는다.34b = 'const'; // 호이스팅으로 ReferenceError에러가 발생하지 않는다.5var b; 
이를 보완하기 위해 let과 const가 탄생하게 된다.
- let - 변수의 재선언이 불가능, 재할당은 가능
 - const - 변수의 재선언 불가능, 상수 혹은 참조형에 사용.
1let a = 'var'2let a = 'let' // Uncaught SyntaxError: Unexpected string3a = 'const' // 가능45b = 'var' // 불가능. ReferenceError: c is not defined6let b78const c // Uncaught SyntaxError: Missing initializer in const declaration9const c = "test" // 선언과 동시에 할당해 주어야 한다.10c = "test2" // 불가능. 
2번 예제 처럼 let과 const는 Hoisting이 안되는것 처럼 보이지만 실제로는 둘 다 Hoisting이 된다.
하지만 에러가 발생하는 이유는 TDZ 때문이다.
TDZ(Temporal Dead Zone)
javascript엔진 내부에서 변수의 생성절차는 다음의 세단계로 이루어진다.
- 선언(Declaration) - 스코프와 변수객체가 생성되고 스코프가 변수객체를 참조
 - 초기화(Initialization) - 값 설정을 위한 메모리 할당(undefined)
 - 할당(Assignment)
 
var를 이용한 변수선언은 선언과 초기화 2단계까지 자동으로 이루어져 우리가 일반적으로 알고있는 결과가 발생한다.
1 | var a | 
2 | console.log(a) // undefined | 
하지만 let과 const는 변수 선언시 1단계인 선언까지 실행하고 메모리의 할당을 기다리게 되는데 바로 이 구간
를 TDZ구간이라고 한다.
Hoisting
자바스크립트에서 변수유형은 특정 스코프를 갖고 있으며 자바스크립트 엔진은 코드를 컴파일하면서변수들이 해당 스코프 범위의 최상단으로 끌어올려지는 현상을 보이는데 이것을 호이스팅이라고 한다.
Scope
var와 let,const는 Scope에서도 차이가 난다.
scope는 변수의 접근 범위를 정의하는 의미로 크게 Global scope와 Local scope로 나뉜다. 
- Global scope
 - Local scope
- Function scope : 함수 레벨 내에서만 변수를 참조할 수 있다.
 - Block scope : 모든 코드 블록
{}내에서 선언된 변수는 코드블록 내에서만 접근할 수 있다. 
 
여기서 var는 Function scope에 해당하며, let과 const는 block scope에 해당한다.
function 스코프도 block 스코프 아닌가 의문이 생길 수 있지만 반복문이나 조건문에서 차이가 발생한다.
1 | for(var i = 0; i < 10; i++) {  | 
2 |   var a = i | 
3 |   let b = i | 
4 | } | 
5 | console.log(a) // 9 | 
6 | console.log(b) // error | 
7 | |
8 | if (true) { | 
9 |   var a = "test"; | 
10 |   let b = "test"; | 
11 | } | 
12 | console.log(a) // "test" | 
13 | console.log(b) // error | 
중첩 스코프(Scope Chain)
그렇다면 스코프가 중첩되어 있는 상황에서는 어떠한 동작이 이루어질까?
특정 스코프를 지닌 변수를 활용하는 경우 해당 스코프 영역에서 변수를 우선 탐색하고,
없을 경우 렉시컬 스코핑하여 상위의 스코프 영역을 탐색하는 방식으로 진행하여 값을 가져온다.
아래의 예제를 보자.
1 | //example_1 | 
2 | var a = "hwang" | 
3 | function test() { | 
4 |   var a = "sangjin" | 
5 |   function test2() { | 
6 |     console.log(a) // "sangjin" | 
7 |   } | 
8 |   test2() | 
9 | } | 
10 | test() | 
11 | console.log(a) // "hwang" | 
12 | |
13 | //example_2 | 
14 | let b = "test" | 
15 | if (true) { | 
16 |   let b = "test2" | 
17 |   console.log(b) // "test2" | 
18 |     if(true){ | 
19 |         console.log(b) // "test2" | 
20 |     } | 
21 | } | 
22 | console.log(b) // "test" | 
위에서 example_1과 example_2는 각각 function scope, block scope에서 중첩스코프의 상황을 표현한 것이다.
둘 모두 위에서 설명한것 처럼 내부 스코프에서 외부 스코프의 변수를 탐색하여 가져오는것을 볼 수 있다.
참고로 이와 반대로 스코프 외부에서 내부 스코프의 접근은 불가능하다.
렉시컬 스코프(Lexical Scope)
렉시컬(정적) 스코프는 javascipt에서 scope가 적용되는 규칙이라고 할 수 있다.
스코프가 적용되는 방식은 동적 스코프(Dynamic scope)와 렉시컬 스코프(Lexical Scope)가 있으며, 의미는 다음과 같다.
동적 스코프는 프로그램의 런타임 도중의 실행 컨텍스트나 호출 컨텍스트에 의해 결정되고,
렉시컬 스코프에서는 소스코드가 작성된 그 문맥에서 결정된다. 현대 프로그래밍에서 대부분의 언어들은 렉시컬 스코프 규칙을 따르고 있다.
다음 예제를 보자.
1 | //example_1 | 
2 | var name = 'hwang'; | 
3 | function log() { | 
4 |   console.log(name); | 
5 | } | 
6 | |
7 | function wrapper() { | 
8 |   var name = 'sangjin'; | 
9 |   log(); | 
10 | } | 
11 | wrapper(); | 
12 | |
13 | //example_2 | 
14 | var name = 'hwang'; | 
15 | function wrapper() { | 
16 |   var name = 'sangjin'; | 
17 |    | 
18 |   function log() { | 
19 |     console.log(name); | 
20 |   } | 
21 |   log(); | 
22 | } | 
23 | wrapper(); | 
첫번째 예제에서 name의 값은 ‘hwang’
두번째 예제에서 name의 값은 ‘sangjin’으로 출력될 것이다.
이는 렉시컬 스코프가 초기 소스의 문맥을 기준으로 스코핑한다는 것을 의미한다.
첫번째 예제를 만약 동적 스코프라고 가정한다면 어떤 결과가 나올까?
아마 두번째 예제의 결과처럼 ‘sangjin’이 출력될 것이다. 
여기까지. 다음으로 함수에 대하여 이어서 작성해보자!
![]()

