[노란책] 함수표현식

함수표현식

함수선언식과 함수표현식

일급함수와 실행 컨택스트

자바스크립트의 함수는 1급 객체이다. 이는 함수를 변수처럼 사용할 수 있다는 말이다. 매개변수로 전달해도 되고, 변수에 할당해도 되고, 리턴해도 된다.

함수 호출 시, 함수의 실행컨택스트가 생성된다. 실행컨텍스트는 스코프체인, 활성화객체, this 프로퍼티를 갖는다.

함수표현식

함수를 생성하는 방법은 함수선언식함수표현식이 있다.

함수 호출 시, 활성화객체가 생성될 때 변수와 함수는 호이스팅된다. 주의할 점은 함수선언식으로 선언한 함수는 호이스팅되지만, 함수표현식으로 선언한 함수는 호이스팅되지 않는다는 것이다. 함수표현식은 변수에 함수객체를 할당하는 것과 같기 때문에 코드가 실행되기 전까지는 변수는 undefined 상태이기 때문이다.

클로저

클로저의 동작

외부 함수(자신을 감싸는)의 스코프를 참조하는 함수를 클로저라고 한다.

일반적으로 함수가 종료되면, 가비지 콜렉터에 의해 함수 내부에서 선언한 활성화 객체가 사라진다. 가비지 콜렉터는 참조할 수 없는 변수를 메모리상에서 수거해가는 데, 함수의 실행컨텍스트가 사라지면서, 함수의 스코프 체인이 사라지고 함수의 스코프 체인이 사라지면서 함수의 활성화 객체에 접근할 수 없어지기 때문이다.

하지만, 클로저가 생성된 경우는 예외다. 클로저를 통해 클로저를 감싸는 함수의 활성화 객체에 접근할 수 있기에 클로저를 감싸는 함수가 종료되어도 활성화 객체는 가비지 콜렉터에 의해 수거되지 않고, 메모리 상에 존재한다. 물론, 실행컨텍스트와 스코프체인은 클로저를 통해 참조되지 않기 때문에 사라진다.

클로저에서 외부 함수의 활성화객체에 접근가능한 이유는 다음과 같다.

a. 클로저(함수)가 생성되면서 클로저를 감싸는 함수의 스코프체인의 값을 클로저(함수) 객체의 [[Scope]] 프로퍼티로 (얕은)복사한다. b. 클로저(함수)가 실행되면서 클로저의 실행컨텍스트가 생성된다. c. 클로저(함수)의 실행컨텍스트의 스코프체인에 클로저(함수)가 갖고 있는 스코프체인의 값을 밀어넣는다. d. 클로저(함수)의 실행컨텍스트의 활성화객체가 생성되고, 스코프체인의 맨 앞에 생성된 활성화 객체가 추가된다.

함수 생성 시점의 렉시컬 스코프를 참조하기 때문에 외부 함수가 종료되어도! 또는 호출 위치에 관계없이! 활성화객체를 참조할 수 있게 된다.

클로저가 생성되면, 클로저가 NULL처리되기 전까지 참조하는 활성화객체가 사라지지 않기 때문에 메모리 누수가 발생할 수 있다.

클로저 활용1: 블록레벨 스코프 흉내내기

알다시피 ES5는 블록레벨스코프를 지원하지 않는다. 블록레벨에서 변수를 선언했다고 착각하는 실수를 하기 쉽다.

유명한 0 .. 9까지 1초 뒤 출력하고자 하는 예제가 그런 실수이다. 타이머 호출 시점의 i 값을 가리키게 하고 싶은 것인 데 블록레벨 스코프가 없어서 의도대로 동작하지 않았다.

function printTo10After1Sec() {
  // i는 함수스코프에 선언되어 있다.
  // 여기에 var i = 0;을 선언한 것과 같다.
  for(var i = 0; i < 10; i += 1) { 
    setTimeout(function() {
      console.log(i); // 변수 i를 참조하는 클로저, 함수 실행 시에는 10이다.
    }, 1000);
  }
  
  // for 문 종료 후 i는 10
}

printTo10After1Sec(); // 10 - i는 최종값인 10

이는 for 문 블록 안에 함수 스코프를 만드는 것으로 대체할 수 있다. 이렇게 하면, 각 시점의 i 값이 저장되어 의도한 대로 동작한다.

function printTo10After1Sec() {
  // i는 함수스코프에 선언되어 있다.
  // 여기에 var i = 0;을 선언한 것과 같다.
  for(var i = 0; i < 10; i += 1) {
    (function(i) { // IIFE의 스코프에 호출 시점의 매개변수 i가 저장된다
      setTimeout(function() {
        console.log(i); // IIFE의 매개변수 i를 참조한다.
      }, 1000);
    })(i);
  }
  
  // for 문 종료 후 i는 10
}

printTo10After1Sec(); // 0 .. 9 - IIFE 실행 시점의 i가 IIFE의 활성화 객체에 저장되어 있다.

클로저 활용2: this가 의도한 값을 가리키도록 한다.

this는 함수를 호출한 대상으로 함수 호출 시점에 결정된다.

만약, 외부 함수의 this에 고정하고 싶다면, 외부함수의 this를 참조하는 변수를 만들고, 해당 변수를 참조하도록 하는 클로저로 만들면 된다.

클로저와 메모리누수

모던 브라우저의 가비지 컬렉터는 마크앤스윕 전략을 사용하기 때문에 전역 스코프를 따라 추적했을 때, 참조할 수 없는 변수는 수거됩니다.

따라서 클로저가 참조하는 외부함수의 활성화 객체는 클로저가 사라지면 수거된다.

그런데, 일부 모던브라우저 버전에서 COM 객체의 가비지컬렉션 시 참조카운팅 전략을 사용하기 때문에 클로저 생성 시, COM 객체에 대한 참조를 할 경우, 가비지컬렉션 대상에서 제외되어 메모리누수가 발생할 수 있다.

이를 예방하기 위해 COM 객체에 대한 클로저 생성 시, COM 객체를 직접적으로 참조하는 경우는 없게 하고, COM 객체 사용 후 NULL로 해제해주면 좋다.

클로저 활용3: 싱글톤 모듈 만들기

자바스크립트는 private 변수 개념이 없다. private 변수를 생성하는 방법은 다음과 같다.