[JavaScript] 변수의 유효범위와 클로저
자바스크립트는 함수지향언어이다.
--> 함수를 동적으로 생성할 수 있고, 생성한 함수를 다른 함수에 인수로 넘길 수 있으며, 생성된 곳이 아닌 곳에서 함수를 호출할 수도 있다.
(( 변수 선언을 let, const로 했을 때를 가정함))
1. 코드블록
코드블록 {...} 안에서 선언한 변수는 블록 안에서만 사용할 수 있다.
이런 블록의 특징은 특정 작업을 수행하는 코드를 한데 묶어두는 용도로 활용할 수 있다. (블록 안엔 작업 수행에만 필요한 변수가 들어간다.)
코드블록은 if, for, while문 등에서의 {...}블록을 모두 포함하는 말.
2. 중첩함수
함수 내부에서 선언한 함수는 '중첩(nested)' 함수라고 부른다.
중첩함수는 코드르 정돈하는데 사용할 수 있다.
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
//중첩함수는 새로운 객체의 프로퍼티 형태나 정첩함수 그 자체로 반환될 수 있다. 또한 이렇게 반환된함수는 어디서든
//호출될 수 있다
3. 렉시컬 환경
<<변수>>
자바스크립트에서 실행중인 함수, 코드블록, 스크립트 전체는 렉시컬 환경(Lexical Enviroment)라 불리는 내부 숨김 연관객체 (internal hidden associated object)를 갖는다.
렉시컬 환경 객체는 두 부분으로 구성된다.
1. 환경 레코드(Environment Record) - 모든 지역 변수를 프로퍼티로 저장하고 있는 객체. (this값과 같은 기타 정보도 여기에 저장)
2. 외부 렉시컬 환경(Outer Lexical Environment) 에 대한 참조 - 외부 코드와 연관됨.
'변수'는 특수 내부 객체인 환경 레코드의 프로퍼티이다. --> '변수를 가져오거나 변경' 하는 것은 '환경 레코드의 프로퍼티를 가져오거나 변경'함을 의미함.
아래 두줄짜리 코드에는 렉시컬 환경이 하나만 존재한다.
let phrase = "Hello"
console.log(phrase)

이렇게 스크립트 전체와 관련괸 렉시컬 환경은 전역 렉시컬 환경(global Lexical Environment)이라고 한다.
여기서 네모상자는 변수가 저장되는 환경레코드를 나타내고, 화살표는 외부 렉시컬 환경에대한 참조를 나타낸다.
전역렉시컬 환경은 외부참조를 갖지 않기 때문에 화살표가 null을 가리키는 걸 확인할 수 있다.
코드가 실행되고 실행흐름이 이어져 나가면서 렉시컬 환경은 변화한다.
let phrase;
phrase = "Hello";
phrase= "Bye?;

위 그림은 네모상자들이 코드가 실행될 때마다 전역 렉시컬 환경이 어떻게 변화하는지 보여준다.
1. 스크립트가 시작되면 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라간다. (pre-populated)
--> 이때 변수의 상태는 특수 내부상태(special internal state)인 'uninitialized'가 된다. 자바스크립트 엔진은 uninitialized 상태의 변수를 인지하긴 하지만, let을 만나기 전까진 이 변수르 참조할 수 없다.
2. let phrase가 나타났지만, 아직 값을 할당하지 않았기 때문에 프로퍼티값은 undefined이다. phrase는 이 시점부터 사용할 수 있다.
3. phrase 값이 할당되었다.
4. phrase 값이 변경되었다.
** 렉시컬 환경은 명세서에만 존재하는 '이론상의' 객체이다. 따라서 코드를 사용해 직접 렉시컬 환경을 얻거나 조작하는 것은 불가능하다.
<<함수 선언문>>
함수는 변수와 마찬가지로 값이지만, 함수 선언문(function declaration)으로 선언한 함수는 일반 변수와는 달리 바로 초기화된다는 점에서 차이가 있다.
--> 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다. (선언되기 전에도 함수를 사용할 수 있는건 이때문이다.)
let phrase = "Hello";
function say(name) {
console.log(`${phrase}`, `${name}` )
};

<<내부와 외부 렉시컬 환경>>
함수를 호출해 실행하면 새로운 렉시컬 환경이 자동으로 만들어진다. 이 렉시컬 환경에는 함수호출 시 넘겨받은 매개변수와 함수의 지역변수가 저장된다.

say("John")을 호출하면 다음과 같은 내부 변화가 일어난다.
1. (함수가 호출중이 동안엔 호출중인 함수를 위한 내부 렉시컬 환경과 내부 렉시컬 환경이 가리키는 외부 렉시컬 환경을 갖게됨)
위의 내부 렉시컬 환경은 현재 실행중인 함수인 say에 상응한다.(say를 가리킨다) 내부 렉시컬 환경엔 함수의 인자인 name으로부터 유래한 프로퍼티만 존재한다. (이때 name의 값은 "John")
여기서 맨 오른쪽의 외부 렉시컬 환경은 전역 렉시컬 환경.
2. 내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다.
** 코드에서 변수에 접근할 땐, 먼저 내부 렉시컬 환경을 검색 범위로 잡는다. 내부 렉시컬 환경에서 원하는 변수를 찾기 못하면 검색 범위를 내부 -> 외부 렉시컬 환경으로 확장한다. 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복됨.
전역 렉시컬 환경에서도 원하는 변수를 찾지 못하면 에러가 발생한다.

((대충 요런 과정을 거친 다는 뜻))
<<함수를 반환하는 함수>>
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
위 코드에서 makeCounter()를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어지고 여기에 makeCounter를 실행하는데 필요한 변수들이 저장된다.

--> 현재는 makeCounter()안에 중첩함수가 생성되기만 하고 실행은 되지 않은 상태이다.
**중요한 사실은, 모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다는 점이다. 함수는 [[Environment]]라는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장된다.
원래는 이렇게 {[[Environment]]}가 숨어있는 그림이었던 것!

--> 여기서 counter.[[Environment]]엔 {count:0}이 있는 렉시컬 환경에 대한 참조가 저장된다. (참고로 이 Environment는 함수가 생성될 때 딱 한번만 값이 세팅되고 영원히 변하지 않는다.)

--> counter()을 호출하면 각 호출마다 새로운 렉시컬 환경이 생성된다. 이 렉시컬 환경은 counter.[[Environment]]에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조한다.
그리고 실행 흐름이 중첩 함수의 본문으로 넘어오면 count변수가 필요한데, 먼저 자체 렉시컬환경에서 변수를 찾는다. (익명 중첩함수에는 지역변수가 없기 때문에 이 렉시컬 환경은 비어있는 상태!!) <empty>. 이제 counter()의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾아야한다. count : 0 찾음!!
이제 count++가 실행되면서 count값이 1 증가해야되는데, 변숫값 갱신은 변수가 저장된 렉시컬 환경에서 이루어진다.
따라서 실행이 종료된 수의 상태는 아래 코드와 같다.

** 클로저
'클로저(closer)'는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다. 자바스크립트에서는 모든 함수가 자연스럽게 클로저가 된다.
--> 자바스크립트 함수는 숨김 프로퍼티인 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억하고, 함수 본문에선 [[Environment]]를 사용해 외부 변수에 접근하는 것이다.