const test1 = () => {
let count = 0;
const inner = () => {
console.log(count); // count: closure scope
debugger;
};
inner();
};
test1();
const test2 = (initialValue = 0) => {
let count = initialValue;
const inner = () => {
console.log(count); // count: block scope
debugger;
};
inner();
};
test2();
의문의 시작은 위 코드에서 시작됐다. 두 함수는 Closure Scope를 사용하는 전형적인 모양을 취하고 있다. test1
의 내부 함수 inner
는 상위 Lexical Scope의 count
변수를 사용하기 위해 일반적으로 알려진대로 Closure Scope를 참조한다. 그런데 기본값 매개변수를 사용하는 test2
의 경우 Block Scope를 사용한다. 이게 대체 어떻게 된 일일까?
일반적으로 알려진대로
count
변수가 Closure Scope에 선언되어 있다.
하지만 기본값 매개변수를 사용하면
count
변수가 Block Scope에 선언된다.
The default parameter initializers live in their own scope, which is a parent of the scope created for the function body.
출처: Mozilla 공식문서: Default parameters
기본값 매개변수에 대한 공식 문서를 찾아보면 위와 같은 설명이 나온다. 즉, 함수에 기본값 매개변수를 사용할 경우, 기본값 매개변수 이니셜라이저는 함수 body의 부모가 되는 자체적인 스코프를 갖는다.
const test3 = (initialValue = 0) => {
let count = initialValue;
// initialValue: local scope
// count: block scope
debugger;
};
test3();
공식 문서를 기반으로 test3
의 스코프를 자세히 살펴보자.
처음에는 count
는 함수의 실행 컨텍스트인 Local Scope에 선언되고, initialValue는 Local Scope의 상위 스코프에 초기화되어야 한다고 생각했다. 그런데 실제로는 Block Scope에 count가 선언되고 Local Scope에 initialValue
가 초기화되어 있었다. 그렇다면 initialValue
는 함수 내부 컨텍스트에 존재하면서 그 하위에 Block Scope로 기존 함수 body가 있다는 의미이다.
Local Scope의 상위 스코프에
initialValue
가 초기화되는 것이 아니라, Local Scope에initialValue
가 초기화되고 그 하위 스코프인 Block Scope에count
가 선언된다.
const test4 = (initialValue) => {
initialValue = 0; // local scope
{ // 기존 함수 body
let count = initialValue; // block scope
debugger;
}
};
test4();
더 쉬운 이해를 위해서 test3
함수를 흉내낸 test4
함수를 작성해봤다. 함수의 Local Scope에서 initial value
를 초기화를 하고, 기존의 함수 body는 Block으로 감싸서 count
는 Block Scope에서 선언했다. 디버거를 통해 스코프 체인을 확인해보면 test3
의 경우와 같은 것을 볼 수 있다.
test3
의 스코프와 동일하다. 실제로 내부에 어떻게 구현되어 있는지는 모르겠지만, 이런 형태로 변환하니 확실히 이해가 됐다.
const test2 = (initialValue = 0) => {
let count = initialValue;
const inner = () => {
console.log(count); // count: block scope
debugger;
};
inner();
};
test2();
이제 test2
코드를 다시 보니 명확하게 보인다. initialValue
는 Local Scope에 있고, count
는 Block Scope에 있으니 count
에 접근하려면 Block Scope를 참조하는 것이 당연하다.
const test5 = (value = 5) => {
let count = 0;
const inner = () => {
console.log(value); // closure scope
console.log(count); // block scope
debugger;
};
inner();
};
test5();
그렇다면 이 경우는 어떨까?
const test5 = (value) => {
value = 5; // local scope
{
let count = 0; // block scope
const inner = () => {
console.log(value); // closure scope
console.log(count); // block scope
debugger;
};
inner();
}
};
test5();
이해를 위해 아까처럼 흉내를 내면 이렇게 변환된다.
여기서 value
는 test5
의 Local Scope에 선언되어 있다. 따라서 inner
에서 value
를 참조하려면 test5
의 Local Scope, 즉 Closure Scope를 참조해야 한다. inner
에서 count
를 참조할 때는 Closure Scope까지 두 단계 올라갈 필요 없이 바로 위의 Block Scope만 사용하면 된다.
count
는 Block Scope,value
는 Closure Scope에 선언되어 있다. 최종적으로inner
의 스코프 체인은 Local(inner
), Block, Closure, Script, Global 순으로 연결된다.