07. 클로저

양희준·2021년 11월 21일
0

JavaScript Info

목록 보기
7/19
post-thumbnail

📌 7-1 클로저(Closure)란?

MDN : 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.

💡 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지 렉시컬스코프를 먼저 이해해야 한다.
💡 외부렉시컬 환경에 대한 참조인 스코프체인과 찾아가는 과정 스코프체이닝을 이해해야 한다.
💡 Call Stack에서 제거되어도 외부함수가 참조하고 있으면 메모리에서 해제하지 않는다. 이러한 특성 때문에 발생하는 현상이다.

📌 7-2 렉시컬 스코프란?

렉시컬 스코프란 참조변수는 함수가 정의되는 시점에 결정된다.
렉시컬 스코프는 정적이다.

const num = 10;
function func1() {
    const num = 20;
    func2();
}
function func2() {
    console.log(num);
}
func1(); // 10 출력

func2가 정의된 공간은 func1가 정의된 글로벌 영역에 정의되어 있다. 그러므로 func1함수 안에서 호출을 해도 func2의 렉시컬 스코프에서는 전역변수로 선언된 num이 출력된다.

const num = 10;
function func1() {
    const num = 20;
    function func2() {
        console.log(num);
    }
    func2();
}
func1(); // 20출력

위 코드에서 func2가 정의된 공간은 func1가 정의된 영역 안에 있다. 그러므로 func2에서 func1를 참조 가능하다. 그래서 func1에의 지역변수 num이 출력된다. 이처럼 함수를 정의한 공간에 따라서 참조하는 영역이 정해진다. 이것이 렉시컬 스코프이며 정적으로 영역이 정해진다.

📌 7-3 스코프 체인

실행 컨텍스트에서의 외부렉시컬환경을 참조할 수 있게 된다. 즉 현재 스코프부터 상위스코프까지 식별자를 찾는 과정이다. 이 현상을 체인처럼 얽혀있다 해서 스코프 체인이라고 한다. 찾는 과정은 스코프 체이닝이라고 한다.

// 레벨 1
const num = 10;
function func1() {
    const num = 20;
    func2();
}
function func2() {
  // 출력할 스코프 시점 (레벨 0)
    console.log(num);
}
func1(); // 10 출력

func1이 실행되면 안에 있는 func2가 실행된다. 하지만 func2의 렉시컬 스코프는 func2의 영역안과 글로벌 영역이다. func2에서 num이 없기 때문에 스코프 체인을 통해서 글로벌 영역에 num이 있나 탐색하여 출력한다.

// 레벨 2
const num = 10;
function func1() {
  // 레벨 1
  // 식별자 num이 있으므로 출력한뒤 종료
    const num = 20;
    function func2() {
      // 출력할 스코프의 시점 (레벨 0)
        console.log(num);
    }
    func2();
}
func1(); // 20출력

func1이 실행되면 func1안에 있는 func2를 정의한 뒤 func2를 출력하게 된다. 그때 func2 렉시컬 스코프영역은 func2의 영역과 func1의 영역 글로벌 영역이다. func2안에 num이 없으므로 상위스코프인 func1의 영역을 탐색하여 num을 출력한다. 글로벌 영역에도 num이 있지만 자기 영역에서부터 부모영역을 탐색하다가 식별자를 찾게 되면 그 지점의 식별자를 출력하며 더 이상 상위 스코프로 올라가지 않는다.

📌 7-4 클로저의 예시

클로저를 사용하는 가장 큰 목적은 출력한 변수에 직접 접근하지 못하게 하여 은닉화를 할 때 주로 사용된다.

function count() {
    let num = 0;
    return num += 1;
}
console.log(count()); // 1 출력
console.log(count()); // 1 출력
console.log(count()); // 1 출력

count 함수를 이용해서 변수를 1씩 더한 값을 출력하려고 하면 함수 내부의 변수가 아니라 전역변수 선언을 해야 한다.

let num = 0;
function count() {
    return num += 1;
}
console.log(count()); // 1 출력
console.log(count()); // 2 출력
console.log(count()); // 3 출력
console.log(num); // 3 출력

이렇게 전역변수를 사용하여 원하는 동작이 완료되었다. 하지만 문제점은 전역변수를 사용하였기 때문에 어디서든지 num이라는 변수에 접근이 가능하다. 이것을 해결할 수 있는 것이 클로저이다.

function count() {
    let num = 0;
    return () => num += 1;
}
const increase = count();
console.log(increase()); // 1 출력
console.log(increase()); // 2 출력
console.log(increase()); // 3 출력
console.log(num); // 오류 발생

이렇게 함수 반환값에 함수를 정의하여 반환되는 함수가 count 안에 있는 변수 num를 참조할 수 있게 바꾼다. 그러면 변수 num은 은닉화가 되어 외부에서 접근하지 못한다. 이것이 클로저를 사용하는 이유이다.

function count() {
    let num = 0;
    return () => {
        const newNum = 1;
        return newNum;
    }
}
const increase = count();
console.log(increase()); // 1 출력
console.log(increase()); // 1 출력
console.log(increase()); // 1 출력

얼핏 보면 함수를 반환하여 클로저같이 보이지만 클로저가 아니라고 평가된다. 이유는 외부 스코프에서 변수를 참조하지 않아 클로저가 생성과 동시에 상위 함수보다 먼저 소멸되기 때문이다. 이처럼 외부 함수가 참조하고 있으면 메모리에서 해제하지 않는 특성을 이용한다.

profile
JS 코린이

0개의 댓글