스코프
변수는 자신이 선언된 위치에 의해 자신이 유효한 범위, 즉 다른 코드가 변수 자신을 참조할 수 있는 범위가 결정된다.
변수 뿐만 아니라 모든 식별자가 그렇다.
모든 식별자 (변수이름, 함수이름, 클래스이름) 은 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위가 결정된다. (식별자-위치-다른코드가 참조)
이를 스코프라고 한다. 즉, 스코프는 식별자가 유효한 범위를 말한다.
글로벌 변수와 지역변수 내에서 어떤 변수를 참조해야할것인지를 결정하는 것을 식별자 결정이라한다.
따라서 스코프란 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있다.
자바스크립트 엔진은 코드를 실행할때 코드의 문맥 (context) 을 고려한다.
코드가 어디서 실행되며, 주변에 어떤 코드가 있는지에 따라 다른 결과를 만들어낸다.
코드가 어디서 실행되며 주변에 어떤 코드가 있는지를 렉시컬환경이라고 부른다.
즉, 코드의 문맥은 렉시컬 환경으로 이뤄진다.
이를 구현한 것이 실행 컨텍스트이며, 모든 코드는 실행컨택스트에서 평가되고 실행된다.
스코프는 실행컨택스트와 깊은 관련이 있다.
만약 스코프라는 개념이 없다면 같은 이름을 갖는 변수는 충돌을 일으키므로, 프로그램 전체에서 하나밖에 사용할 수 없다.
컴퓨터에서도 폴더로 구분짓고 각 폴더마다 같은 파일이름이 있어도 관계없다. 스코프도 마찬가지다.
프로그래밍 언어에서는 스코프 (유효범위)를 통해 식별자인 변수 이름의 충돌을 방지하여 같은 이름의 변수를 사용할 수 있게 해준다.
스코프내에서 식별자는 유일해야하지만, 다른 스코프에는 같은 이름의 식별자를 사용할 수 았다. 즉 스코프는 네임스페이스다.
===
전역스코프 => outer 스코프 => inner 스코프
이렇게 모든 스코프는 하나의 계층적구조로 연결되며, 모든 지역스코프의 최상위 스코프는 지역스코프다.
이렇게 스코프가 계층적으로 연결된 것을 스코프체인이라고 한다.
변수를 참조할때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여, 상위 스코프 방향으로 이동하며
선언된변수를 검색한다.
자바스크립트 엔진은 코드를 실행하기에 앞서 위 그림과 유사한 자료 구조인 렉시컬환경을 실제로 생성한다.
변수 선언이 실행되면 변수 식별자가 렉시컬환경에 키로 등록되고, 변수할당이 일어나면 이 자료구조의 변수 식별자에 해당하는 값을 변경한다.
변수의 검색도 이 자료구조상에서 이뤄진다.
렉시컬 환경은 자신의 외부렉시컬 환경에 대한 참조를 통해 상위 렉시컬 환경과 연결된다.
이것이 바로 스코프체인이다.
렉시컬 환경의 "외부렉시컬 환경에 대한 참조"에 저장할 참조값, 즉 상위 스코프에 대한
참조는 함수정의가 평가되는 시점에 함수가 정의된 환경에 의해 결정된다
이것이 바로 렉시컬스코프다
함수는 자신의 내부 슬롯 [[ENVIROMENT]] 에 자신이 정의된 환경, 상위 스코프의 참조를
저장한다.
CONST X =1;
function outer() {
const x = 10;
const inner = function() {console.log(x)}
return inner;
}
const innerFunc = outer();
innerFunc();
outr 함수를 호출하면 outer 함수는 중첩함수 inner 를 반환하고 생명주기를 마감한다.
즉, outer 함수의 실행이 종료되면 outer 함수의 실행컨택스트는 실행컨택스트 스택에서
제거된다.
이때 outer 함수의 지역변수 x 와 변수 값 10을 저장하고 있던
outer 함수의 실행컨택스트가 제거되었으므로, outer 함수의 변수 x 또한 생명주기를 마감한다.
따라서 outer 함수의 지역변수 x 는 더는 유효하지 않게 되어 x 변수에 접근 할 수 있는
방법이 달리 없어보인다.
그러나 위 코드의 실행결과는 outer 함수의 지역변수값인 10이다.
이미 생명주기가 종료도히어 실행 컨택스트에서 제거된 outer 함수의 지역변수 x가
다시 부활한듯이 동작하고 있다.
이처럼
"외부 함수보다 중첩함수가 더 오래 유지되는 경우, 중첩합수는 이미 생명주기가 종료한
외부함수의 변수를 참조할 수 있다. 이러한 중첩함수를 클로저라고 한다."
outer 함수의 실행이 종료되면 inner 함수를 반환하면서 outer 함수의 생명주기가 종료된다.
즉, outer 함수의 실행컨택스트가 실행컨택스트 스택에서 제거된다.
이때 outer 함수의 실행컨택스트은 실행컨택스트 스택에서 제거되지만,
outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다.
outer 함수의 렉시컬 환경은 inner 함수의 [[Enviroment]] 내부 슬롯에 의해 참조되고 있고
inner 함수는 전역변수 innerFunct 에 의해 참조되고 있기때문에
가비지 컬랙션의 대상이 되지 않기 때문이다. 가비지 컬랙터는 누군가가 참조하고 있는
메모리공간을 함부로 해제하지 않는다.
클로저의 활용
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
상태를 안전하게 은닉하고 특정 함수에게만 상태변경을 허용하기 위해 사용한다.
let num = 0;
const increase = function() {
return ++num
}
console.log(increase());
console.log(increase());
console.log(increase());
const increase = function() {
let num = 0;
return ++num;
}
console.log(increase());
increase 가 호출될때마다 지역변수 num 은 다시 선언되고 0으로 초기화되기때문에
출력은 언제나 1이다.
const increase = (function() {
let num = 0;
return function() {return ++num}
};}());
여기서 num변수는 외부에서 직접 접근할 수 없는 은닉된 private 변수이므로
전역변수를 사용했을 때와 같이 의도되지 않은 변경을 걱정할 필요도 없기때문에
더 안정적인 프로그래밍이 가능하다.
이처럼 클로저는 상태가 의도치않게 변경되지 않도록 안전하게 은닉하고
특정함수에게만 상태변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.