[JavaScript] 클로저와 커링

2 분 소요

1. 클로저

http://dl.dropbox.com/s/h3wj24y9s4q4srq/JavaScript-%ED%81%B4%EB%A1%9C%EC%A0%80%EC%99%80%20%EC%BB%A4%EB%A7%81-1.png)

클로저는 자바스크립트 고유 개념이 아니라, 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다. 위 정의에 따르면, 클로저는 내부 함수로부터 외부 함수의 영역에 접근할 수 있게 해준다고 한다.


function init() {
  let name = "Mozilla";

  function displayName() {
    console.log(name);
  }

  displayName();
}

init(); // Mozilla

위 코드는 init() 함수 호출을 통해 그 안에 displayName() 함수가 선언되고 실행된다. 먼저, 함수 안에 함수가 어떻게 존재할 수 있는 지에 대해서 찾아봤다.


http://dl.dropbox.com/s/eklq8kxutd6aotm/JavaScript-%ED%81%B4%EB%A1%9C%EC%A0%80%EC%99%80%20%EC%BB%A4%EB%A7%81-2.png)

위 사진은 스택 오버플로우JS에서는 중첩 함수를 작성할 수 있는가?에 대한 질문의 답이다.


JS에서 함수는 1급 객체(first class citizen/object/function)로써 사용된다. 1급 객체의 조건은 아래와 같다.

  • 함수를 변수나 데이터 구조 안에 담을 수 있다.
  • 함수를 파라미터로 전달할 수 있다.
  • 함수를 리턴 값으로 사용할 수 있다.


JS는 함수를 함수 선언식 혹은 함수 표현식으로 작성할 수 있다. 함수 표현식의 의미 자체가 함수를 변수에 담는다는 데 있기에, 함수 내부에 함수를 둘 수 있게 되는 것이다.

1급 객체 특성에 따라 함수가 중첩 구조로 작성될 수 있다는 것을 알게 됐다. 다시 위의 코드로 돌아오면, displayName 함수 내부에 변수가 없지만 name이라는 변수를 출력하는 모습을 볼 수 있다. 이는, 내부 함수에서 외부 함수의 변수에 접근 가능하기 때문에 init 함수의 name을 출력할 수 있게 되는 것이다. 위와 같이 외부의 접근 가능한 영역을 lexical scope라고 부른다.


function makeFunc() {
  let name = "Mozilla";
  function displayName() {
    console.log(name);
  }
  return displayName;
}

let myFunc = makeFunc();
myFunc();

이번 코드는 위와 달리 makeFunc 함수는 displayName 함수를 호출하지 않고 리턴한다(일급 객체의 특성).

함수는 호출되어 리턴되면 메모리에서 내려간다. makeFunc(); 를 통해 호출하고 리턴하는 순간 name이 사라질 것이라고 생각할 수 있지만, 여전히 myFunc 함수 내부에서 접근이 가능하다.

myFunc 이라는 이름의 함수가 displayName 함수가 만들어질 때의 환경을 기억하고 있기 때문이다. 내부 함수에서 외부 함수의 데이터에 접근을 하는 코드가 작성되어 있으므로, name 에 대한 참조를 여전히 남겨둔다고 한다.

이처럼, 외부 함수 호출이 끝났음에도 불구하고, 내부 함수에서 외부 함수의 변수 및 파라미터를 계속 보존하는 이런 성질을 클로저라고 한다. (외부 함수 실행이 끝났지만, 메모리에서 내려가지 않고 내부 함수에서는 계속해서 접근할 수 있는 단절된 느낌?)


2. 커링

커링클로저의 특성을 이용한 기술이다. 클로저는 내부 함수에서 외부 함수의 파라미터를 포함한 지역 변수 데이터들을 접근할 수 있기에, 파라미터를 나눠 내부 함수로 전달시킬 수 있다.


function curry(f) {
  return function (a) {
    return function (b) {
      return f(a, b);
    };
  };
}

function sum(a, b) {
  return a + b;
}

let curriedSum = curry(sum);

console.log(curriedSum); // [Function (anonymous)]
console.log(curriedSum(1)); // [Function (anonymous)]
console.log(curriedSum(1)(2)); // 3

위 코드를 보면, 함수 안에 함수 안에 함수가 작성된 모습을 볼 수 있다.

curry(sum); 이 호출되면, curriedSum이라는 이름의 함수를 리턴받는다. 이 함수는 파라미터로 a를 받으며, 그 내부에 파라미터 b를 받는 함수가 작성되어 있으며, 외부 함수인 curry의 파라미터 sum 함수를 접근할 수 있다.

curriedSum(1); 이 호출되면, 파라미터 a로 1을 전달하며, 동시에 파라미터 b를 전달 받아 외부 함수의 sum 함수에 두 인자 a, b를 전달한 결과 값을 리턴하는 익명 함수를 리턴한다. 여기서 리턴하는 함수를 다시 변수에 담아 재활용할 수 있다.

재활용 하지 않고 한 번 curriedSum(1)(2); 를 호출해보면, 3이 출력된다. curriedSum(1); 호출시 a에 1을 전달했고, 내부에 만들어진 함수의 파라미터 b에 2를 전달했으니, sum 함수의 호출 결과 값 3이 리턴되는 것이다.


http://dl.dropbox.com/s/o3o75w0e99pn3s4/JavaScript-%ED%81%B4%EB%A1%9C%EC%A0%80%EC%99%80%20%EC%BB%A4%EB%A7%81-3.png) 위는 커링 기술을 사용해 함수를 구현한 예제다. 세 함수는 모두 맨 처음 인자로 sum을 받는다. 얘는 배열의 모든 요소를 다 더해 그 값을 리턴하는 함수다. 이 자리에는 배열 모든 요소를 다 곱한 결과를 리턴하는 함수를 전달해서 함수를 재활용할 수도 있다. 커링은 이처럼 매 깊이마다 함수를 리턴하므로 재사용에 용이하다고 한다.

카테고리:

업데이트:

댓글남기기