JavaScript Closure에 대해 조사한 내용을 정리하였다. 다 알고 있어도 원리와 동작까지 깔끔하게 포스트로 정리하려니 여간 어려운 것이 아니었다.
Closure 이해를 위해선 Scope 개념에 대해 알아야한다. 변수, 함수, 클래스 등 선언된 위치를 기준으로 다른 코드에서 참조할 수 있는 유효범위가 있다.
let globalVar = 1; // global scope
function test(x) {
let y = 'world'; // local scope
console.log(globalVar); // 1
console.log(x); // hello;
console.log(y); // world;
}
test('hello');
console.log(globalVar); // 1
console.log(x); // ReferenceError
console.log(y); // ReferenceError
예시에서 globalVar 변수는 전역 변수이고, test 함수 안에 x와 y는 지역 변수이다. 함수도 변수처럼 global 영역에 정의할 수도 있고 local 영역에도 정의할 수 있다.
일반적으로 다른 언어에서 코드 블록(if, for, while, try/catch 등)에서도 local scope가 만들어질 수 있으나, JavaScript var 키워드 특성에서는 그렇지 않다. 그래서 다른 말로 function level scope라고도 한다.
function level scope
var 키워드가 모든 코드 블록이 아닌 함수에 의한 코드 블록에서만 local scope가 생성된다.
var x = 1; // global scope
if (x) {
// local scope로 생각하겠지만 그렇지 않다. global scope이다.
var x = 3;
console.log(x); // 3
}
console.log(x); // 3
block level scope
block level scope은 모든 코드 block(if, while, try/catch, for, function 등)을 local scope로 인정하는 scope이다.
let, const 키워드가 해당된다.
let x = 1;
if (x) {
let x = 3;
console.log(x); // 3
}
console.log(x); // 1
구현하다보면 위 방식에서 중첩되거나 중복되는 상황이 생긴다.
let x = 1; // global scope
function test() {
let x = 'hello'; // local scope
console.log(x); // hello
// 중첩 함수
function test2() {
let x = 'world';
console.log(x); // world;
}
test2();
}
test();
console.log(x); // 1
위 현상을 통해 scope는 계층적임을 알 수 있고, JavaScript 엔진은 scope chain을 통해 참조한 변수를 검색하여 유효한 범위의 변수를 먼저 참조한다.
그래서 예시 변수 x가 각 scope에서 적절히 값을 출력하는 것이다.
JavaScript 엔진은 이런 chain을 코드 실행 전에 lexcal environment 자료구조로 실제로 생성한다. 그리고 이 구조들을 단방향으로 연결하면 scope chain이 되는 것이다.
lexical environment
lexical environment는 코드가 실행된 순간에 주변 코드에 대한 문맥을 의미한다. 이를 구현한 것이 Execution context이다.
함수가 정의되는 시점에 상위 스코프를 정적으로 결정하는 방식을 lexical scope라 한다. 이를 static scope라고도 한다. 함수 호출 시점마다 다르진 않다. 그래서 아래 예시에서도 test2의 상위 스코프는 정의될 때 global scope로 1이 출력된다.
var x = 1; // global scope
function test() {
var x = 'hello'; // local scope
test2();
}
function test2() {
console.log(x);
}
test(); // 1
test2(); // 1
Closure란 함수와 그 함수가 선언된 lexical environment의 조합으로, 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 외부 함수의 변수를 참조할 수 있는데 이 때 이 중첩 함수를 closure라고 한다.
아래처럼 test 안에서 test2 함수를 선언하고 반환하였을 때 test가 끝나면 x의 스코프는 끝난다. 그러나 실제로 실행되는데 이것이 바로 closure의 특성이다.
const x = 1; // global scope
function test() {
const x = 'hello'; // local scope
const test2 = function() {
console.log(x);
};
return test2;
}
const func = test();
func(); // hello
Closure는 자바스크립트 고유의 개념이 아니며, 함수를 객체로 취급하는 여러 함수형 프로그래밍 언어에서 사용하는 중요한 특성이라고 한다.
아래와 같은 경우 x에 대해 test 외부에서 접근할 수 없고 오로지 test 변수로만 접근할 수 있다.
const test = (function() {
let x = 1;
return function() {
return ++x;
};
}());
console.log(test()); // 2
console.log(test()); // 3
console.log(test()); // 4
여담
공부를 하다보면 항목별로 다 나눠져있는데 결국엔 A를 알아야 B를 알았고 B를 알아야 A가 이해되는 상황이 많다.
내용이 방대하다보니 발생한 문제인가 싶은데 intro로 큰 그림을 보여줄 수 없을까하는 생각이 들었다. 가능하다면 큰 맵을 정리해볼 수 있는지 탐구해볼 생각이다.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
모던 자바스크립트 deep dive
https://ko.javascript.info/closure