- 지난 게시물에 이어서 클로저의 사용 예시에 대해 정리한다.
- 주로 코어 자바스크립트 책을 참고했다.
클로저 활용 사례
1. 콜백 함수 내부에서 외부 데이터 사용 시
콜백 함수란 다른 함수에 인자로 전달되는 함수이다. 즉, 다른 함수에서 이 콜백 함수를 사용하는 주도권을 쥐게 된다. 이러한 콜백 함수에서, 외부 변수를 참조하는 데에는 크게 3가지가 있다.
(a) 콜백함수 내부에서 외부 변수를 직접 참조하는 방법(클로저 사용)
var nameList = ['Ann', 'Gren', 'Jennifer'];
var $ul = document.createElement('ul');
nameList.forEach(function (name) {
var $li = document.createElement('li');
$li.innerText = name;
$li.addEventListener('click', function () {
console.log('Hello', name);
});
$ul.appendChild($li);
});
document.body.appendChild($ul);
- forEach 문에서, addEventListener의 인자로 전달하는 콜백 함수를 살펴보면, 바깥쪽에 정의된 name을 참조하여 사용하고 있음을 알 수 있다.
- 따라서 forEach 문이 종료된 후에도 콜백 함수 내에서는 여전히 forEach 문의 LexicalEnvironment를 참고할 것이고, 이에 따라 name은 가비지 컬렉팅 당하지 않을 것이다.
(b) bind를 사용하는 방법(클로저 사용 X)
위 코드에서 console 부분의 별도의 함수로 빼낼 경우, 아래와 같다.
var nameList = ['Ann', 'Gren', 'Jennifer'];
var $ul = document.createElement('ul');
var sayName = function (name) {
console.log('Hello', name);
};
nameList.forEach(function (name) {
var $li = document.createElement('li');
$li.innerText = name;
$li.addEventListener('click', sayName);
$ul.appendChild($li);
});
document.body.appendChild($ul);
- 이 경우, 이전 코드와 달리 각 항목을 클릭해도 이름이 나오지 않고 Event 객체가 출력된다.
- 이는 addEventListener가 콜백 함수의 인자로 이벤트 객체를 전달하기 때문이다.
- 따라서 이 경우 bind를 사용해 의도한 동작을 구현할 수 있다.
var nameList = ['Ann', 'Gren', 'Jennifer'];
var $ul = document.createElement('ul');
const sayName = function (name) {
console.log('Hello', name);
};
nameList.forEach(function (name) {
var $li = document.createElement('li');
$li.innerText = name;
// bind를 사용하여 콜백 함수에서 사용할 인자를 직접 넘겨준다
$li.addEventListener('click', sayName.bind(null, name));
$ul.appendChild($li);
});
document.body.appendChild($ul);
이 경우 원래 콜백 함수에 인자로 전달될 예정이던 이벤트 객체가 두 번째 인자로 전달될 것이라는 점, this가 원래 값과 달라지는 점 등의 문제점이 존재한다.
(c) 고차 함수를 사용하는 방법(클로저 사용)
고차 함수 (Higher Order Function): 함수를 인자로 받거나 함수를 리턴하는 함수.
- "함수를 리턴한다" === "클로저를 반환한다"
고차함수를 사용하여 위의 코드를 수정하면 아래와 같다.
var nameList = ['Ann', 'Gren', 'Jennifer'];
var $ul = document.createElement('ul');
// 인자를 받아 익명 함수(클로저)를 반환하는 고차 함수
var sayNameBuilder = function (name) {
return function () {
console.log('Hello', name);
};
};
nameList.forEach(function (name) {
var $li = document.createElement('li');
$li.innerText = name;
$li.addEventListener('click', sayNameBuilder(name));
$ul.appendChild($li);
});
document.body.appendChild($ul);
- 기존의 sayName 함수를, name을 인자로 받아 그 name을 console을 통해 출력하는 클로저를 반환하는 고차 함수로 바꾸었다.
- 이렇게 반환된 클로저는 addEventListener의 콜백함수로 전달된다.
2. 접근 권한 제어(정보 은닉)
정보 은닉(Information hiding)
: 외부에 공개될 필요가 없는 구현 일부를 감추어 객체의 상태 변경을 보호하는 것(캡슐화).
※ 캡슐화(Encapsulation): 객체의 상태를 나타내는 프로퍼티와, 이 프로퍼티들을 참조/조작할 수 있는 메서드를 하나로 묶는 것.
- JAVA나 C 등의 언어에서는 접근 제한자(private, public 등...)를 사용하여 공개할 속성과 그렇지 않은 속성을 구분할 수 있다.
- 하지만 자바스크립트에는 접근 제한자가 존재하지 않으며, 일반적으로 객체의 메서드와 프로퍼티는 public하다.
- 그러나 클로저를 사용하면 접근 권한 제어를 구현하는 것이 가능하다!
var foo = function () {
var counter = 1;
var bar = function () {
return ++counter;
};
return bar;
};
var foo2 = foo();
console.log(foo2());
- foo 함수가 종료될 때 내부 함수인 bar를 반환하고, 반환된 내부 함수는 foo2를 통해 호출할 수 있다. 이를 통해 foo 함수 내부의 지역변수인 counter에 접근하는 것이 가능하다.
- 즉, 공개하고자 하는(접근을 허용하고자 하는) 정보를 return 을 통해 반환함으로써 바깥에서 접근하게 만드는 것이 가능하다.
- return한 변수는 공개 멤버(Public), 그렇지 않은 변수들은 비공개 멤버(Private)가 된다.
3. 부분 적용 함수(Partially applied function)
: n개의 인자를 받는 함수에 일부 인자만 넘겨 기억시켰다가, 나중에 나머지 인자를 넘기면 비로소 실행되는 함수.
- bind 함수가 이러한 부분 적용 함수를 만드는 데 잘 사용된다. (참고)
var add = function () {
var result = 0;
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
};
// bind 함수를 사용하여 add 함수에 1부터 5까지의 인수를 미리 전달한다
var addPartial = add.bind(null, 1, 2, 3, 4, 5);
// 후에 6부터 10까지의 나머지 인수를 전달한다
console.log(addPartial(6, 7, 8, 9, 10));
4. 커링 함수(Currying function)
: 여러 개의 인자를 받는 함수를 하나의 인자로 받는 함수로 나눠, 순차적으로 호출할 수 있도록 한 함수.
- 부분 적용 함수와 비슷하지만, 오직 한 번에 하나의 인자만 전달한다는 원칙이 있다.
var curryingFunc = function (func) {
return function (a) {
return function (b) {
return function (c) {
return func(a, b, c);
};
};
};
};
// ES6 문법을 사용하면 가독성을 높일 수 있다
var curryingFunc = func => a => b => c => func(a, b, c);
- 함수에서 필요로하는 모든 인자가 넘겨질 때까지 함수가 실행되지 않다가, 모든 인자가 전달되면 그제서야 함수가 실행될 것이므로, 지연 실행(Lazy Execution)이 일어난다.
- 클로저의 정의를 이해하는 것까진 괜찮았는데, 아직 사용 예시가 어려운 느낌이다.
- 더 익숙해질 때까지 반복적으로 실제 사용 예시를 참고해봐야겠다...
'공부 > JS' 카테고리의 다른 글
[Javascript] 프로토타입 알아보기 - 1 (0) | 2024.01.24 |
---|---|
[Javascript] 콜백 함수 알아보기 (1) | 2024.01.21 |
[Javascript] 클로저 알아보기 - 1 (1) | 2024.01.05 |
[JavaScript] 실행 컨텍스트에 대하여 (2) | 2023.12.23 |
Typescript 정리 - 2 (1) | 2023.01.16 |