📎 Reference
코드가 실행되기 위해 필요한 환경
- 변수객체 (arguments, variable) : 선언된 변수와 함수가 등록되는 곳
- scope chain (자신과 상위 스코프들의 변수객체)
this
🗃 컨텍스트의 모습 (객체 형식으로 표현)
'컨텍스트': {
변수객체: {
arguments: (전역 컨텍스트인 경우 null)
variable: [var1, func1, func2],
},
scopeChain: ['전역 변수객체'], ['(함수인 경우) 해당 함수 변수객체'],
this: (전역 컨텍스트이거나 함수 컨텍스트에서 따로 설정하지 않은 경우 window),
선언 당시 환경에 대한 정보를 담은 객체
함수의 호출이 아닌 선언 시점에 해당 변수의 상위 스코프가 결정된다. (스코프 체인이 결정된다.)
실행 컨텍스트와 lexical scope는 다른 개념이다. 실행 컨텍스트는 함수 호출시마다 생성되지만, scope chain은 lexical scoping에 따라 함수 선언시에 생성된다. 코드의 문맥은 lexical scope로 이루어지며 이를 구현한 것이 실행 컨텍스트이다. 모든 코드는 실행 컨텍스트에서 평가되고 실행된다.
변수를 선언할 때 선언 부분이 최상단으로 끌어올려지는 것과 같은 현상
실제로 코드가 끌어올려지는 것은 아니고, 코드 실행 전에 변수 선언이 먼저 메모리에 저장된다는 의미이다.
함수 선언식
호이스팅이 적용되며, 전역 컨텍스트 생성시 전역 컨텍스트의 변수 객체에 함수로 바로 대입된다. 따라서 선언보다 먼저 호출을 해도 실행이 된다.
함수 표현식
호이스팅이 적용되지 않으며, 표현식의 경우 함수로 대입되기 전에 호출이 되면 에러가 발생한다.
function func1() { ... } // 함수선언식
const func2 = function() { ... } // 함수표현식
위와 같이 함수선언식과 함수표현식이 있을 때, 전역 컨텍스트는 다음과 같다.
'전역 컨텍스트': {
변수객체: {
arguments: null,
variable: [{ func1: Function }, 'func2'],
// func1은 Function으로 대입되어(초기화되어) 있지만 func2는 그렇지 않다.
},
scopeChain: ['전역 변수객체'],
this: window,
}
var
, let
, const
변수 모두 선언시 호이스팅은 적용된다. 단지 var
의 경우 선언과 동시에 'undefined'로 초기화가 진행되고 let
, const
의 경우 초기화 없이 선언 자체만 메모리에 저장되기 때문에 선언문 이전에 참조하면 참조에러가 발생하여 마치 호이스팅이 되지 않는 것처럼 보이는 것이다.MDN 에서는 클로저를 함수와 함수가 선언된 lexical scope의 조합으로 정의하고 있다. 이보다 조금 실용적인 정의는 아래와 같다.
- 클로저
비공개 변수를 가질 수 있는 환경에 있는 함수- 비공개 변수
함수 내부에 생성한 것도 아니며 매개변수도 아닌 변수
함수와 함수가 접근할 수 있는 상위 scope는 클로저 관계를 맺는다. 클로저 관계일 때에는 실행이 끝난 함수의 스코프를 참조할 수 있다. 또한 모든 함수는 최소한 1개의 스코프(전역 스코프)와 클로저 관계를 맺는다.
const outer = function() {
const name = 'yooni';
const inner = function() {
console.log(name);
}
return inner;
}
const func = outer();
console.log(func()); // yooni 출력
inner 함수는 선언시의 lexical scope와 클로저 관계이기 때문에, outer 함수의 실행이 종료된 이후에도 outer 함수의 name 변수의 값에 접근이 가능하다.
"Closure(폐쇄)"라는 이름에서도 클로저의 의미와 역할을 유추할 수 있다. 클로저 함수는 폐쇄적인 비공개 변수로 상위 스코프에 접근하며 사용자가 직접 접근하는 것은 불가능하여 내부 변수와 메소드를 안전하게 관리할 수 있다. 하지만 메모리 낭비나 속도 저하를 야기할 수 있다.
react에서는 아래와 같이 state와 state 변경 함수를 선언하는데, 이 역시 클로저의 형태로 작성되어 있다.
const [state, setState] = useState(initialValue)
render()
메소드를 통해 상태 변경을 감지하여 필요한 부분만 업데이트하는 클래스형 컴포넌트와 달리, 함수형 컴포넌트는 렌더가 필요할 때마다 함수를 다시 호출하기 때문에 함수가 다시 호출될 때 이전의 상태를 기억하고 있어야 한다. 하지만 함수는 실행이 완료되면 함수 내에서 사용했던 모든 메모리를 정리한다. Hook은 클로저를 통해 이를 해결한다.
내부함수 setState
가 지역 변수를 참조하고 있어, 내부의 데이터가 사라지지 않으며 setState
는 외부에 노출되어 내부 변수에 지속적으로 접근하여 호출과 재할당을 할 수 있다.