우선 예제 코드의 실행 순서부터 보겠습니다.
let a = '1';
function outer () {
let b = "2";
let d = "4";
function inner () {
let c = "3";
console.log(a, b, c);
}
inner();
return d;
};
console.log(outer());
결과
// node
1,2,3
4
내부함수, lexical 스코프, 클로저의 기억
1. 내부함수
클로저는 함수 안에서 선언된 내부함수를 클로저 함수라고 합니다. 위에 코드에선 outer 안에 선언된 inner 함수가 클로저가 됩니다.
스코프는 함수가 호출될 때 결정되는 게 아니고 함수가 선언 되었을 때 결정됩니다. 접근 가능한 범위도 함께 결정됩니다.
function home () {
// ...something1
function room () {
// ...something2
}
}
home함수 안에 room함수가 있습니다. room함수가 선언됐을 때 lexical scope는 room함수 자신의 scope, home함수 scope, global scope까지 lexical scope입니다. room함수는 global scope에 접근이 가능합니다. home함수만 선언 되었다고 한다면 home함수 scope, global scope까지 lexical scope입니다.
let a = "1";
function outer() {
let b = "2"
return function() {
let c = "3";
console.log(a, b, c);
}
};
let inner = outer();
console.log(inner());
전역에서 변수 inner에 outer()가 반환한 익명함수를 할당했습니다. 변수 inner를 실행 시키면 에러가 날 것만 같습니다.
// 에러가 날 것 같은 상황
// let inner = outer();
// inner 변수를 풀어보면
let inner = function() {
let c = "3";
console.log(a, b, c);
}
console.log(inner());
// Uncaught ReferenceError: a is not defined...
그렇지만 에러가 발생하지 않습니다.
결과
1 2 3
그 이유는 outer함수에서 선언된 익명함수는 "내부함수" 고 선언 됐을 때 "lexical Scope" 가 결정이 된 상태입니다. "lexical Scope" 는(위의 예시 상황에서) 익명함수 자신의 scope, outer함수의 scope, global의 scope 까지 입니다. 익명함수가 선언됐을 때 lexical scope가 결정되었고, "내부함수" 는 "lexical scope" 까지 접근이 가능합니다. 변수 inner에 할당된 함수를 실행시켰을 때 클로저의 기억을 따라가보면,
"내(익명함수)가 a를 가지고 있나, 없네. outer에는 있나, 없네. 그럼 global에는 있나, 있네! 오예! 찾았다. 다음은 b를 찾아보자. 나한테 있나, 없네. outer에 있나, 있다! 다음은 내가 c를 가지고 있나, 있다!"
클로저가 자신의 만들어진 환경을 기억하고 있다는 말을 제 나름대로 해석해서 설명해보았습니다. 결국 클로저는 함수 내부에서 선언된 내부함수이고 lexical scope를 가지며 lexical scope 까지 접근할 수 있는 함수를 말합니다.
참고
https://poiemaweb.com/js-closure
https://hyunseob.github.io/2016/08/30/javascript-closure/
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures