자바스크립트에서 클로저라는 것을 잘 이해하기 위해서는 프로그래밍 언어에서 사용되는 몇가지 개념들을 먼저 알아두면 좋을 것 같습니다. 그것들은 일급 객체(first-class object)또는 일급 함수(first-class function), 변수 범위, 중첩 함수 와 같은 것들 입니다.
1. 일급 객체 또는 일급 함수
일급 객체(first-class object)에 대한 정의를 위키피디아에서 찾아 보았습니다.
원문 : https://en.wikipedia.org/wiki/First-class_citizen
"프로그래밍 언어를 디자인 할때 주어진 프로그래밍언어에서 일급 시민(또는 일급 타입, 일급 객체, 일급 엔티티, 일급 값)은 다른 엔티티들이 일반적으로 이용 가능한 모든 연산을 지원하는 엔티티를 뜻합니다. 여기서의 연산은 전형적으로 인자로 전달되고, 함수의 반환값으로 사용되고, 수정되고, 변수에 할당하는 것을 포함합니다."
자바스크립트의 함수는 일급 함수 입니다. 자바스크립트에서의 함수는 사실 객체이므로 일급 객체라고 할 수 있습니다. 클로저 사용법을 이야기하는데 일급 함수가 중요한 이유는 자바스크립트에서 클로저를 만들기 위해서는 함수를 변수에 대입하고, 반환값으로 반환하는 작업이 필요하기 때문입니다.
2. 변수의 범위(Scope) 및 수명(Lifetime)
자바스크립트 변수들은 지역(local) 또는 전역(global)의 변수 범위를 갖습니다.
함수내에서 정의한 변수는 지역 변수(Local Variable)입니다. 지역변수는 그것이 정의된 함수 내에서만 사용되어질 수 있습니다. 다른 함수에서는 사용할 수 없습니다.
function printName() {
var name = "김철수";
console.log(name);
}
결과)
김철수
함수 밖에서 정의된 변수는 전역 변수(Global Variable)입니다. 전역 변수는 윈도우(window) 객체에 속합니다. 전역 변수는 페이지 내의 모든 스크립트에서 사용되어질 수 있습니다.
var name = "홍길동";
function printName() {
console.log(name);
}
결과)
홍길동
같은 이름은 가지는 전역변수와 지역변수가 있을 때 이 둘을 서로 다른 변수 입니다. 하나를 변경하더라도 다른것에는 영향이 없습니다. 함수 내에서 전역 변수와 같은 이름의 지역변수를 정의해 버리면 전역변수는 가려져서 사용할 수 없게 됩니다.
var name = "홍길동";
function printName() {
var name = "김철수";
console.log(name);
}
printName();
console.log(name);
결과)
김철수
홍길동
※ 참고
변수 생성시에 var 키워드를 사용하지 않고, 만들었다면 이 변수는 항상 전역변수 입니다. 이것이 함수 안에서 만들어 졌다고 하더라도 전역변수가 되어 버립니다.
function printName() {
name = "홍길동";
console.log(name);
}
printName();
console.log(name);
결과)
홍길동
홍길동
변수의 수명(lifetime) 은 글로벌 변수일 경우는 애플리케이션(윈도우 또는 웹페이지)가 살아 있는 동안에 살아있습니다. 지역변수는 수명이 짧습니다. 함수가 호출될 때 함수 안에서 생성되고, 함수가 종료될 때 지워집니다.
3. 중첩 함수(Nested Function)
중첩 함수는 함수내에서 또 함수를 정의하고 사용하는 것을 말합니다. 앞에서 보았듯이 함수내에서는 함수 외부의 변수에 접근할 수 있었습니다. 중첩함수에서도 외부의 변수에 접근할 수 있습니다.
var global_name = "홍길동";
function printName() {
var outer_name = "김철수";
function showName() {
var inner_name = "김영희";
console.log(global_name);
console.log(outer_name);
console.log(inner_name);
}
showName();
}
printName();
결과)
홍길동
김철수
김영희
4. 함수의 저장 및 반환
자바스크립트는 일급 함수를 지원하므로 함수를 변수에 저장하고, 파라미터로 함수를 넘기고, 함수를 반환 하는 것이 가능합니다.
var global_name = "홍길동";
function makePrinter() {
var outer_name = "김철수";
function printName() {
var inner_name = "김영희";
console.log(global_name);
console.log(outer_name);
console.log(inner_name);
}
return printName;
}
var print = makePrinter();
print();
결과)
홍길동
김철수
김영희
예제에서는 함수내에서 정의한 printName() 함수를 반환하여 var print; 변수에 저장합니다. 반환되어 print 변수에 저장된 것이 함수이므로 print도 함수 입니다. 그러므로 print(); 로 실행할 수 있습니다.
5. 자바스크립트 클로저
상당히 길어졌지만, 이제 자바스크립트 클로저를 설명하는데 필요한것들을 모두 알아보았습니다.
사실 앞의 예에서 var print = makePrint(); 로 반환 받은것이 클로저 입니다. 이 예에서 클로저로써 가장 특징적인 부분은 makePrinter() 함수 내에서 정의된 지역 변수인 outer_name 이 자신의 수명이 끝나는 makePrinter() 호출 후에 print(); 호출에도 살아 있다는 것 입니다(print(); 호출로 "김철수" 가 출력되었습니다).
클로저는 자신을 포함하고 있는 외부 함수의 인자, 지역변수 등을 외부 함수가 종료된 후에도 사용할 수 있습니다. 이러한 변수를 자유 변수(free variable) 이라고 부릅니다.
클로저가 생성될 때 범위내의 지역 변수들을 자유 변수로 만드는 것을 캡쳐(capture) 라고 합니다. 이 자유변수는 외부에서는 직접 접근할 수 없고, 항상 클로저를 통해서만 사용할 수 있습니다. 객체 지향언어의 private 멤버 변수와 같은 효과를 냅니다.
이처럼 자유 변수를 가지는 코드를 클로저라고 합니다.
앞의 예에서는 함수내에서 함수를 정의하고 그것을 반환했는데 아예 익명함수를 반환하는 다른 예를 보도록 하겠습니다.
function makeGreeting(name) {
var greeting = "안녕! ";
return function() {
console.log(greeting + name);
};
}
var g1 = makeGreeting("홍길동");
var g2 = makeGreeting("김철수");
g1();
g2();
결과)
안녕! 홍길동
안녕! 김철수
이 예에서는 익명함수를 바로 반환하고, makeGreeting()의 인자와 내부의 지역변수가 모두 캡쳐되어서 자유변수가 됨을 보여주고 있습니다. 인자로 주어지는 값에 따라 다른 기능을 하는 클로저가 필요하다면 이러한 형태로 작성을 하면 될 것입니다.
클로저를 여러개 생성할 필요가 없고 페이지내에서 단 하나만 생성해서 쓸 경우에는 자기 호출(self-invoking) 함수를 사용하여 클로저를 만들면 됩니다. (자기 호출 함수(self-invoking function)를 즉시 호출 함수(immediately invoked function)라고도 부릅니다.)
var print = (function() {
var name = "홍길동";
return function() {
console.log(name);
};
})();
print();
결과)
홍길동
클로저 생성시 초기값을 주는 것도 가능합니다.
var print = (function(name) {
var greeting = "안녕! ";
return function() {
console.log(greeting + name);
};
})("홍길동");
print();
결과)
안녕! 홍길동
자기 호출 함수는 정의와 동시에 즉시 실행이되고, 한번만 실행이 됩니다. 또한 인자를 통해 함수내부로 값을 보낼 수도 있습니다.
6. 클로저 예제
이제까지 클로저의 의미와 클로저를 생성하는 여러가지 방법을 알아 보았습니다. 그러나 앞서의 예제들로는 이게 어디에 쓰일 수 있을까 의문이 들 수 있습니다.
클로저에 대해 알아보게 된 계기는 jQuery 플러그인을 만들면서 였습니다. jQuery 플러그인을 만들다보니 이게 클로저로 만들어진다는것은 알게 되었고, 클로저에 대해 좀더 자세히 알고 싶어졌던 것입니다.
이제부터 볼 예제도 실용적인것은 아니지만 클로저에 대해서 좀 더 이해를 돕고, 실제 사용에 힌트가 될 수 있을 것이라고 생각합니다.
// 카운터 클로저를 생성합니다.
var counter = (function() {
// private 변수 입니다.(외부 접근 불가)
var privateCounter = 0;
// private 함수 입니다.(외부 접근 불가)
function changeCounter(val) {
privateCounter += val;
}
// public 함수를 가지는 객체를 반환합니다.
return {
// 증가 기능을 가지는 public 함수 입니다.
inc: function() {
changeCounter(1);
},
// 감소 기능을 가지는 public 함수 입니다.
dec: function() {
changeCounter(-1);
},
// public 함수 입니다.(현재값 조회)
val: function() {
return privateCounter;
}
};
})();
counter.inc();
counter.inc();
console.log("after increment : " + counter.val());
counter.dec();
console.log("after decrement : " + counter.val());
결과)
after increment : 2
after decrement : 1
예제 코드에 대해 알아 보겠습니다.
카운터 클로저는 자기호출 함수로 하나만 생성했습니다.
카운터 값을 유지하는 변수는 자유 변수로 카운터 밖에서는 직접 접근할 수 없습니다. 자기 호출 함수는 최초 한번만 실행되므로 한번 0으로 초기화 됩니다.
클로저 내부에 정의된 카운터 값을 변경하는 changeCounter() 함수는 외부에서는 접근할 수 없는 private 함수가 됩니다.
function changeCounter(val) {
privateCounter += val;
}
이 예제에서 반환하는 것은 함수가 아니라 객체 입니다. 자바 스크립트 객체는 리터럴 표기법을 사용하여 var obj = { }; 처럼 만들 수 있습니다. 그러므로 여기서 생성되는 것은 일급 함수가 아니라 일급 객체가 되겠습니다.
반환되는 익명 객체에는 클로저의 자유변수에 접근하는 세 개의 publc 함수인 inc(), dec(), val() 이 정의되어 있습니다.
return {
inc: function() {
changeCounter(1);
},
dec: function() {
changeCounter(-1);
},
val: function() {
return privateCounter;
}
};
inc() 함수는 카운터를 1 증가 시킵니다. dec() 함수는 카운터를 1 감소 시킵니다. val() 함수는 현재의 카운터를 반환합니다.
클로저를 만들때 이 예제에서처럼 사용하면 객체지향언어의 객체가 가지는 private 멤버 변수, private 메소드, public 메소드와 유사한 기능을 구현할 수 있습니다.
jQuery 플로그인을 만들때도 이와 유사하게 외부에서 접근 불가능한 설정값, 접근 가능한 설정값, 비공개 함수와 공개 함수를 적절히 사용하여 확장성 있는 플러그인을 만들 수 있도록 하는 것이 좋습니다.
이것으로 자바스크립트에서 클로저를 만드는 것에 대해서 알아 보았습니다.
'프로그래밍 > 자바스크립트' 카테고리의 다른 글
자바스크립트 함수(Function)와 함수의 call(), apply(), bind() 함수 (4) | 2018.04.10 |
---|---|
jQuery 플러그인 3 - 확장 기능 제공 (0) | 2018.04.10 |
자바스크립트 배열(javascript array) 사용법 (2) | 2018.04.10 |
jQuery 플러그인 2 - 기본값 사용자 재정의 (0) | 2018.04.10 |
jQuery 플러그인 1 - 기본적인 플러그인 만들기 (0) | 2018.04.09 |