Clojure 이해하기

처음 자바스크립트를 접할 때 가장 어렵다고들 하는 Clojure(이하 클로저)에 대해 이야기하고자 합니다.

클로저란?

클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 '기억한다'. (MDN발췌)
Lisp에서 시작되었으며, 함수형 프로그래밍에 중점을 둔 범용 프로그래밍 언어입니다.(wiki)

wiki 에서 인용한 것을 보면 클로저는 자바스크립트만의 특징적인 개념이 아닌 오래전부터 다른 진영에서 존재해왔던 개념입니다.
자바스크립트가 유행하고 클로저 기술이 아주 많이 사용 되다보니 오해를 하고 있는 사람들이 많은 것 같습니다.

개념

개발자답게 코드로 이야기해보겠습니다.

function a(){
    var outerV = 1;

    return function(){
        var innerV = 2;
        var result = outerV + innerV;
        console.log(result);
    } // 내부 함수 (B)
}
var sum = a();
sum();

// 3 (결과가 이상하게 3이 나왔습니다. 왜그럴까요?)

a()가 실행되면서 내부 함수 (B)sum 반환 되었습니다. sum()을 실행하니 결과는 3 이 출력되었습니다.
여기서 의문점을 가져야하는 부분은 sum 함수에 outerV 가 어떻게 1로 할당 되었을까 입니다.

이는 앞선 포스팅 [JS] 스코프 체인 이란? 에서 유추 할 수 있습니다.(읽고오십시오.)
자바스크립트 함수는 생성 시점에 Execution Context 가 생성되고 평가 됩니다.
그래서 sum 이 생성되는 시점에 Scope Chain 에 의해 outerV 을 탐색하여 참조를 기억하게 됩니다.
그리하여 실행 시점에 outerV는 알고 있게 되어 1+2를 할 수 있게 되고, 상단에 MDN에서 발췌한 내용 클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 '기억한다' 의 명제가 성립되어집니다.

이것이 바로 클로저 입니다.

좀더 개념

다른 예제를 살펴보겠습니다.

function count(){
    var i=0;
    for(i=0 ; i<5 ; i++){
        setTimeout(function(){
            console.log(i);
        },1000);
    }
}
count();

위 예제의 결과는 예상과 다르게 1,2,3,4,5 가 아닌 5,5,5,5,5가 나오게 됩니다. 왜 그럴까요?

자바스크립트는 스코프체인에 의해 변수에 접근 할 때는 참조 방식으로 접근하게 됩니다.

자 그럼 코드를 분석해봅시다.
실제로는 for문 이 5번이 다돈 후에야 setTimeout 실행함수가 실행됩니다. (for문 5번도는건 순식간이라서..) 그러면 이미 i5가 되어있으며, i를 참조하고 있는 setTimeout 실행 함수는 5를 출력하게 되는 것 입니다.

그럼 이 문제를 해결 하기 위해선 어떻게 해야할까요?

이때 필요한 것이 클로저입니다.
클로저는 내부 함수를 둘러싼 외부 함수(환경)를 기억하고 있는다. 라고 하였습니다.

원하는 결과인 1,2,3,4,5를 나오게 하기 위한 방법은?

function count(){
    for(var i=0 ; i<5 ; i++){
        (function(i){
            setTimeout(function(){
                console.log(i);
            },1000);
        })(i);
    }
}
count();

//es6 방식
let count = () => {
    for(let i=0 ; i<5 ; i++){
        setTimeout(() => {
            console.log(i);
        },1000);
    }
}
count();

ES5에서는 setTimeout을 감싸는 즉시실행함수로 ifor문이 돌때마다 각 상태를 즉시실행함수 안의 스코프에 기억하게 만들어버리면 됩니다.
ES6는 scope 방식부터가 달라져서 var->let으로만 바꿔도 바로 됩니다. (이는 varlet의 엄청난 차이가 있기 때문입니다만.,.생략..)

결론

클로저의 마법은 정말 놀랍습니다.
위 내용은 정말 기본적인 예제를 이용한 것이지만 실제로 저런 작은 코드들 때문에 문제를 발생하는 경우가 꽤 빈번합니다.
또한 클로저를 이용하여 고차함수를 구현 할수 있고, 커링(curring)의 기초가 될 수 있습니다.
클로저 화이팅
p.s. 면접 단골질문 중 하나는 클로저 입니다.