처음 바닐라 자바스크립트를 사용하여 코드를 쓸 때마다 함수 내에서 생성한 변수가 함수 밖에서는 사용이 안돼서 당황했던 적이 있다.
첫 번째로 이 문제의 원인이 무엇일까? 검색해봤다.
검색어는 왜 함수 내의 변수를 함수 밖에서 사용하지 못하는가
나온 결과는 지역변수, 전역변수, 변수의 유효 범위 등등 나왔고
유효 범위는 아직 생소한 단어이기에 익숙한 단어인 지역 변수와, 전역 변수를 먼저 알아보자!
함수 내부에서 선언한 변수
전역 공간에서 선언한 변수
이 개념을 알기 위해서는 실행 컨텍스트에 대한 개념을 알아야한다!
실행 컨텍스트에 대해 알아보자
실행할 코드에 제공할 환경 정보들을 모아놓은 객체
실행할 코드
는 저희가 작성한 코드라는 말이고, 환경 정보
는 코드 실행에 영향을 주는 조건이나 상태를 말합니다.
1. 전역 공간 ( Global Execution Context)
: 함수 내부에 없는 코드는 전역 컨텍스트에서 실행됩니다.
window
전역 컨텍스트를 생성합니다.this
를 전역 객체로 설정합니다.2. 함수 (Functional Execution Context)
: 함수가 호출될 때마다 해당 함수에 대한 새로운 실행 컨텍스트가 생성됩니다.
3. eval (Eval Function Execution Context)
: eval
은 보안상의 문제로 잘 사용되지 않기 때문에 다루지 않았습니다.
ES6 에서는 블록{} 에 의해서도 새로운 실행 컨텍스트가 생성된다고 합니다.
eval 를 제외하면 우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행 하는 것이라고 합니다.
이미지를 보는 것처럼
한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점입니다.
자바스크립트 엔진은 해당 컨텍스트에 관련된 코드를 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장한다고 하는데요.
무슨 정보들을 담을까요?
총 3가지를 담습니다.
- VariableEnvironment
- LexicalEnvironment
- ThisBinding
이 친구는 실행 컨텍스트를 생성할 때 이 친구에게 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment 를 만듭니다.
식별자의 정보를 수집하는 역할을 합니다.
이 친구는 어휘적/사전적 환경이라고도 불리우고, 실행 컨텍스트를 구성하는 환경 정보들을 모아 사전처럼 구성한 객체라고도 합니다.
각 식별자의 데이터를 추적하는 역할을 합니다.
variable Environment 와 Lexical Environment 의 내부에는 두가지의 어떠한 것들로 구성되어있는데, 초기에는 완전히 동일하고 이후 코드 진행에 따라 서로 달라지게 됩니다.
어떤 것이 구성되어있을까?
여기에는 현재 컨텍스트 (함수라고 생각해봅시다)에 있는 식별자 정보들이 저장된다고 합니다.
여기서 식별자는
들이 해당됩니다.
자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행합니다.
여기서 호이스팅(hoisting) 이라는 개념이 나옵니다.
변수와 함수 선언이 최상위로 끌어와지는 현상을 말합니다.
코드로 이해해보겠습니다.
function a (x){
console.log(x); // 1
var x;
console.log(x); // 1
var x = 2;
console.log(x) // 2
function b(){
console.log(x) // 2
}
b();
}
a(1);
이 코드에서 변수 정보를 수집하는 과정인 호이스팅을 처리해봅시다.
Record 는 현재 실행될 컨텍스트의 코드 내에 어떤 식별자들이 있는지에만 관심이 있기 때문에, 어떤 값이 할당될 것인지에 대한 관심은 없습니다. 따라서 변수명만 끌어올리고 할당 과정은 그 자리에 남겨둡니다.
function a (x){
var x;
var x;
var x;
function b () {console.log(x)}
x= 1; // var x = 1;
console.log(x);
// var x;
console.log(x);
x = 2; // = var x = 2;
console.log(x);
b()
}
a(1);
이런식으로 변경됩니다.
원래 있던 코드와 비교해보기 위해서 주석처리로 코드를 남겨놨는데요, 딱 선언만 위로 끌어올리고 할당 과정은 모두 원래 자리에 남겨놓는 모습을 볼 수 있습니다.
함수 표현식은 정의한 함수를 별도의 변수에 할당합니다.
함수 선언문은 함수 정의부만 존재하고 별도의 할당 명령이 없습니다.
위의 호이스팅을 보면, 변수는 선언부와 할당부로 나눠 선언부만 끌어올린다고 했죠?
코드 예제로 살펴봅시다.
console.log(sum(1,2)); // 3
console.log(multiply(3,4)); // error : multiply is not a function
function sum(a,b){
return a + b;
}
var multiply = function(a,b){
return a +b;
}
multiply 함수에는 오류가 나온 모습이죠? 왜 이런지 호이스팅을 적용한 코드로 확인해봅시다.
function sum (a,b){
return a + b;
}
var multiply;
console.log(sum(1,2));
console.log(multiply(3,4));
multiply = function(a,b){
return a +b;
}
이런식으로, sum 함수는 선언문이기때문에, 함수 자체가 호이스팅으로 최상단으로 끌어올려지고,
multiply 함수는 표현식으로 할당한 변수명만 끌어올려지는 것입니다. 할당은 console.log() 뒤에 이루어지기 때문에 당연히 함수인지 어떤 값이 있는지 모르겠죠?
var의 문제점
이런 문제를 해결하기 위해서 ES6 에서는 let
, const
가 추가되었다고 합니다.
var
키워드를 사용하면 선언한 객체의 경우 선언과 초기화가 동시에 이루어지기 때문에, 선언이 되자마자 undefined 로 값이 초기화 됨.let
키워드의 경우는 선언만 했더라도 묵시적으로 undefined
가 할당되며 초기화됨)const
키워드는 반드시 선언과 동시에 값을 할당해줘야한다.)let/const
는 TDZ 에 의해 에러를 발생합니다.TDZ 에 대해 알아가자.
: 일시적 사각지대라고 불리우는 이 Temporal Dead Zone 은 무엇일까?
변수가 스코프의 시작 지점부터 초기화가 시작되는 지점까지의 구간을 말합니다.
var
키워드를 사용한 것으로 취급된다.정리하자면 이 호이스팅때문에 상대적으로 함수 표현식을 사용하는것이 안전하고 전역공간에서 함수를 선언하거나, 동명의 함수를 중복 선언하는 경우는 없어야합니다.
또한var
키워드를 사용하는 것을 자제하고,let/const
키워드를 사용하는 것이 안전한 코드를 쓰는데 중요하다고 생각합니다.
outerEnvironmentReference 는 바로 직전 컨텍스트의 렉시컬 환경 (LexicalEnvironment) 의 정보를 참조하는 것들로 구성되어있습니다.
: 식별자에 대한 유효범위 (변수의 유효범위)
어떤 A 라는 함수 외부에서 선언한 변수는 A 내부에서도 접근이 가능하지만,
A의 내부에서 선언한 변수는 오직 A 의 내부에서만 접근할 수 있습니다.
: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
코드 상에서 어떤 변수에 접근하려고 하면 현재 컨텍스트의 렉시컬 환경을 탐색해서 발견되면 그 값을 반환하고, 발견하지 못할 경우 다시 outerEnvironmentReference에 담긴 렉시컬 환경을 탐색하는 과정을 거침
이 과정에서 전역 컨텍스트의 렉시컬 환경에서도 발견하지 못하면 undefined 를 반환하는 것입니다.
함수 내부에서 변수를 선언하고, 전역 공간에서 선언한 동일한 이름의 변수에 접근할 수 없는 것을 변수 은닉화 (variable shadowing)
이라고 한다.
단순하게 왜 함수 내부에서 사용한 변수를 전역 공간에서 쓰지 못할까 라고 궁금해서 공부했는데, 중요한 개념을 많이 알아간 듯하다.
this에 대해서는 더 깊게 공부를 해야할 필요가 있다 생각해서 여기에는 기술하지 않았고, 따로 포스팅을 해야할 것 같다..! 예전에도 this를 공부했었는데 코어 자바스크립트를 통해서 더 깊게 공부해서 오랫동안 이해할 수 있도록 하면 좋을 것 같다는 생각이 들었고, 이 주제로
이렇게 많은 내용을 습득해서 너무 좋았다.
자바스크립트에 단순히 코드를 쓴다고, 내 생각대로 코드가 실행되지 않는 점도 알았고, 자바스크립트 엔진이 어떤식으로 코드를 해석하고 실행해나가는지에 대해 배운 것 같았다.