[JavaScript] Closure(클로저)

soyeon·2022년 7월 7일
0

execution context(실행 컨텍스트)

: 정보를 저장하기 위한 메모리 공간 + 코드 실행 순서 관리
-> (lexical environment + stack)

각각의 코드들을 실행할 때 나오는 데이터를 관리하기 위해 실행 컨텍스트 내에 저장한다.

여기서의 메모리 공간이 lexical environment이다.
코드 실행 순서 관리는 stack으로 관리된다.(Execution context Stack)

lexical environment

: 현재 실행 컨테스트에 대해서 현재 스코프, 상위 스코프에 대한 참조를 가지고 있다.

Execution context Stack

: 코드 실행 순서를 저장한다.

  • 코드에서 스택이 변해가는 과정 확인하기
const x = 1;

function foo() {
    const y = 2;
    function bar() {
        const z = 3;
        console.log(x + y + z);
    }
    bar();
}

foo();
  1. 프로그램 실행
    • 전역 실행 컨텍스트
      : 코드의 흐름 저장용
  2. foo() 호출
    • foo 실행 컨텍스트
  3. bar() 호출
    • bar 실행 컨텍스트
  4. bar() 종료
    • bar 실행 컨텍스트가 삭제된다.
      ...
  5. 스택이 비게 된다.

ECMAScript 명세

: 4가지 종류의 소스 코드가 있다.

코드 종류에 따라 scope가 달라지게 된다. 각각의 코드가 실행 될 때마다 별도로 실행 컨텍스트가 생성되고 관리된다.

  1. 전역 코드
    : 전역에 존재하는 소스 코드
    전역 함수의 내부 코드는 포함되지 않는다.

  2. 함수 코드
    : 함수 내부에 존재하는 소스 코드
    중첩 합수의 내부 코드는 포함되지 않는다. 별도의 함수 코드로 간주한다.

  3. eval 코드 (중요X)

  4. 모듈 코드 (중요X)

var obj = []  // 전역 코드

function myFunc() {  // 전역 함수
    console.log('Hello');  // myFunc의 함수 코드

    // nested function, inner function, 중첩 함수
    function sayHello() {
        console.log('하이');  // sayHello의 함수 코드
    }
}

myFunc();  // 전역 코드

전역 코드가 실행된 이후

전역 변수를 관리하기 위해 전역 scope를 생성한다. -> 전역에 var keyword로 선언된 식별자를 찾고, window 객체를 만들고, 그 식별자를 window 객체에 binding 한다.

=> 이런 작업을 하기 위해 전역 실행 컨텍스트를 생성한다.

함수 코드가 실행된 이후

함수 내부에서 사용하는 지역 변수, 매개 변수, arguments 관리하기 위해 지역 scope를 생성한다. -> 이 지역 scope를 전역 scope와 연결해서 scope chain을 생성한다.

=> 이런 작업을 하기 위해 함수 실행 컨텍스트를 생성한다.

Closure

: 함수와 그 함수가 선언된 lexical 환경의 조합(MSN 정의)

JavaScript 고유의 개념이 아니다. 함수형 언어들이 가지고 있는 특징이다. 일급객체에 대한 이해가 필요하다.

  • Closure는 중첩함수이다.

  • 이 중첩함수가 외부함수의 결과값으로 return 된다.

  • return되는 중첩함수가 외부함수의 식별자를 참조한다.

  • return되는 중첩함수의 life cycle(생명주기)가 외부함수보다 길어야 한다.

  • 이때, 중첩함수에서 외부함수에 대한 참조가 남아있기 때문에 외부함수의 실행은 execution context stack에서 제거 되지만 외부함수의 lexical 환경은 메모리에 남아있어서 중첩함수에 의해 사용될 수 있는 현상

closure 예시

const x = 1;

function outer() {
    const x = 10;

    const inner  = function() {
        console.log(x);
    }
    return inner;  // 1급 객체라서 함수를 리턴할 수 있다.
}

const innerFunc = outer();  // 여기서 함수가 끝나면 지역변수 x = 10이 사라졌어야 하는데 남아있다.
innerFunc();

아래 코드는 closure가 아니다. x,y를 참조하지 않기 때문이다.

function foo() {
    const x = 1;
    const y = 2;

    function bar() {
        const z = 3;  // foo의 식별자를 참조하고 있지 않으므로 closure가 아니다.
        console.log(z);
    }
    return bar;
}

const bar = foo();
bar();

아래 코드도 closure가 아니다. 중첩함수를 리턴하지도 않고 있고, 중첩함수가 외부함수보다 생명주기도 짧기 때문이다.

function foo() {
    const x = 1;
    const y = 2;

    function bar() {
        console.log(x);
    }
    bar();
}

foo();

Closure 어디에 쓸까?

Information Hiding 구현

  • num이 하나씩 증가하는 코드
let num = 0;

const increase = function() {
    return ++num;
}

console.log(increase());
console.log(increase());
console.log(increase());

실행 결과 : 1
 		   2
		   3

문제 : num을 전역이기 때문에 아무 곳에서나 변경할 수 있다. 데이터를 보호할 수 있는 방법이 없다.

  • num을 사용할 수 있는 곳을 제한하고 싶다. 따라서 num을 함수 안으로 넣었다.
const increase = function() {
    let num = 0;
    return ++num;
}

console.log(increase());
console.log(increase());
console.log(increase());

실행 결과 : 1
 		   1
		   1

문제 : num이 함수를 실행할 때마다 초기화 된다.

  • closure로 위의 문제를 해결할 수 있다.
const increase = (function() {
    let num = 0;  // num을 지역변수화 해서 보호한다.
    return function() {
        return ++num;  // 외부함수의 변수를 참조하기 때문에 closure이다.
    }
}());

console.log(increase());
console.log(increase());
console.log(increase());

실행 결과 : 1
 		   2
		   3

closure가 될 때 함수를 직접 리턴하지 않고, 객체에 담아서 리턴해도 된다.

const counter = (function() {
    let num = 0;  // 외부로부터 보호해야 할 변수

    return {  // 함수를 직접 리턴하지 않고, 객체에 담아서 리턴한다.
        increase() {
            return ++num;
        },
        decrease() {
            return --num;
        }
    }
}());

console.log(counter.increase());
console.log(counter.increase());
console.log(counter.increase());

console.log(counter.decrease());
console.log(counter.decrease());
console.log(counter.decrease());

실행 결과 : 1
 		   2
		   3
		   2
		   1
		   0

0개의 댓글