6/ 함수표현식의 다른 용도

6/ 함수표현식의 다른 용도

‘프론트엔드 개발자를 위한 자바스크립트 프로그래밍’ 책을 참고하여 정리합니다. 오류가 있다면 언제든지 댓글 남겨주세요.

목차

  1. 함수와 재귀
  2. 클로저
    2-1. 클로저 사용 (반복문)
    2-2. 클로저의 메모리 누수
    2-3. 클로저를 이용한 고유 변수(private variable : 객체 외부에서 접근할 수 없는 변수)
    = 은닉화

1. 함수의 재귀

함수의 재귀 부분은 함수Part 포스팅의 callee의 예시를 보면서 설명했었다. 추가로 스트릭트 모드일 경우에 arguments.callee에 접근할 수 없으므로 이를 보완하는 방법을 알아본다.

  • 이름 붙은 함수 표현식을 써서 같은 결과를 낼 수 있게 한다.
    1
    2
    3
    4
    5
    6
    7
    const factorial = (function f(num){ // f()를 생성하여 factorial에 할당.
    if (num <= 1){
    return 1;
    } else {
    return num * f(num-1);
    }
    })

f라는 이름은 함수를 다른 변수에 할당하더라도 그대로 유지되므로 재귀 호출은 정확히 실행됨.


2. 클로저

클로저란 다른 함수의 스코프에 있는 변수에 접근 가능한 함수이다.(내부함수가 외부함수의 스코프에 접근가능). 즉, 내부함수가 참조하는 외부함수의 지역변수가 외부함수에 의해 내부함수가 반환된 이후에도 life-cycle이 유지되는 것을 의미한다.
생성될 당시의 환경을 기억하는 함수를 말한다. 클로저를 잘 이해하기 위해서는 스코프 체인이 어떻게 생성되고 사용되는지 자세히 알아야 한다.

함수에서 변수에 접근할 때마다 스코프 체인에서 해당 이름의 변수를 검색한다. 함수 실행이 끝나면 로컬 활성화 객체는 파괴되고 메모리에는 전역 스코프만 남는다. 하지만 클로저는 외부함수가 실행을 마쳐도 활성화 객체는 내부함수가 파괴될 때가지 메모리에 남는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createComparisonFunction(propertyName){
return function(object1, object2){
const value1 = object1[propertyName]
const value2 = object1[propertyName]

if(value1 < value2) {
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}

const compare = createComparisonFunction("name")
const result = compare({name: "Nicholas"}, {name:"Greg"})
compare = null; // 함수파괴 - GC이 메모리 회수가능
내부 함수가 반환되어 다른 컨텍스트에서 실행되는 동안에도 `propertyName`에 접근하 수 있다. 이런 일이 가능한 것은 내부 함수의 스코프 체인에 `createComparisonFunction()`의 스코프가 포함되기 때문이다.
  1. 외부함수가 실행을 마치고 익명함수를 반환하면 익명함수의 스코프체인은 외부함수의 활성화객체AO와 전역변수객체GO를 포함하도록 초기화된다.
    • 이 때문에 익명 함수는 외부 함수의 번수 전체에 접근할 수 있다.
  2. 아직 익명함수의 스코프 체인에서 활성객체를 참조하기 때문에 외부 함수가 실행을 마쳤는데도 활성객체는 파괴디지 않는다.
    • 즉 활성화객체는 익명함수가 파괴될 때까지 메모리에 남는다.

중간지점..개념정리

자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우,
내부함수가 외부함수의 스코프에 접근할 수 있고,
외부함수는 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 소멸되지 못하고 상태가 유지되며
내부함수에 의해서 소멸하게 되는 특성을 클로저라고 부른다.

외부함수의 지역변수를 Free variable(자유변수)이라고 부르는데, 클로저라는 이름은 자유변수에 함수가 닫혀있다(Closed)라는 의미로, 자유변수에 엮여있는 함수라는 뜻이다.

외부함수가 이미 반환되었어도 외부함수 내의 변수는 이를 필요로 하는 내부함수가 하나 이상 존재하는 경우, 계속 유지된다. (내부함수가 외부함수에 있는 변수의 복사본이 아니라 실제변수에 접근한다.)

클로저는 외부함수의 스코프를 보관해야하므로 다른 함수에 비해 메모리를 많이 요구한다. 클로저를 과용하면 메모리 문제가 생길 수 있으니, 반드시 필요할 때만 사용하길 권장한다.

2-1. 클로저 사용 (반복문)

함수 안에 함수를 정의하고 내부에 정의한 함수를 노출시키면, 클로저를 사용할 수 있다. 함수를 노출시키기 위해서는 함수를 반환하거나 다른 함수로 전달하면 됩니다.

1
2
3
4
5
6
7
8
9
function createFunctions(){
var result = [];
for (var i=0; i<10; i++){
result[i] = function(){
return i;
}
}
return result;
}

result 배열에 들어가는 모든 함수가 스코프체인에 createFunctions()의 활성화객체를 포함하므로, 이들은 모두 같은 변수, i를 참조한다. 때문에 마지막에 할당된 i가 저장이 되는것이다.

이는 for루프의 초기문에 사용된 var키워드 변수의 스코프가 전역이기 때문에 발생하는 현상이므로, ES6의 let키워드를 이용하여 블록레벨 스코프 개념을 만들면 원하는 결과값이 나오긴한다.

하지만.. 우선 let이 없다는 상황에서 클로저를 사용하여 원하는 결과값을 반환해보자.

1
2
3
4
5
6
7
8
9
10
11
function createFunctions(){
var result = [];
for (var i=0; i<10; i++){
result[i] = (function(num){
return function(){
return num;
}
})(i);
}
return result;
}
1. 즉시실행함수이기 때문에 함수가 실행되면서 내부함수가 반환된다. - 즉시실행함수는 한번만 호출시 처음 한번만 실행된다. 2. num에 i를 매개변수로 넘기기 때문에(복사) result에 들어가는 익명함수에는 자유변수 num이 생겼다고 보면되다. 3. 배열에 들어가는 함수는 즉시실행함수 매개변수로 i를 받는다. i는 num에 매개변수로 **복사**가되고, num은 해당 함수의 자유변수가 된다. 반환된 내부함수는 자유변수 num에 엮여있는 함수 클로저가 된다. 때문에 외부함수에서 매개변수로 받는 i값에 따라 고유한 num과 클로저를 갖게 된다.

2-2. 클로저의 메모리 누수

클로저는 외부함수의 활성화객체 참조를 계속 유지하기 때문에 참조카운트가 줄지 않는다. (가비지컬렉션이 잡지 치우지 못한다.) 필요하다면 클로저를 갖고 있는 변수에 null을 할당하여 참조를 끊어야한다.

2-3. 클로저를 통한 은닉화 (고유변수)


참고링크

  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Strict_mode
  2. http://poiemaweb.com/js-closure