클로저(closure)란?

2018-11-23

클로저란 과연 무엇인가?
closure 폐쇄, 닫는다의 의미이다.

자바스크립트를 다루면서 책에는 나왔지만 실제로 아직 사용을 해본 적은 없다.

클로저는 함수 내부에 함수를 작성하는 것이다.
클로저를 사용하는 이유는 다음과 같다.

1. 사이드 이펙트(side effect) 제어

우선 side effect는 함수에서 값을 반환할 때를 제외하고 무언가를 행할 때를 가르킨다. ( ajax요청이나 timeout생성, console.log선언하는 것도…)
보통 ajax, timeout과 같이 코드 흐름을 방해하는 것들이 신경쓰일 때 사용한다.

1
2
3
4
5
6
7
8
9
10

var arr = []
for(var i = 0; i < 5; i++){ //i의 값은 외부함수의 변수가 아님
arr[i] = function() {
return i; //결국 i에 5가 담기게 되는 이유 ==> 배열을 돌게 되면 마지막으로 i =5가 되는데 return하고 있는 i의 값은 외부함수의 지역변수가 아님
}
}
for(var index in arr) {
console.log(arr[index]()); // 5가 다섯번찍히게 됨
}

위의 예제는 var가 함수 스코프를 따르기 때문에 5가 나오긴한다. let인 경우 0.1.2.3.4가 찍힌다.
하지만 이 예제의 요점은 아니다.

1
2
3
4
5
6
7
8
9
10
11
var arr = []
for(var i = 0; i < 5; i++){
arr[i] = function(id){ //외부함수
return function() { // 내부함수
return id;
}
}(i); //외부 함수를 즉석에서 실행함
}
for(var index in arr) {
console.log(arr[index]());
}

이 예제에서 보면 외부함수가 실행되면 외부함수의 스코프는 끝이 나기 때문에 외부함수의 인자인 id 값은 메모리에서 정리가 되어야한다.
그러나 리턴이 되면서 내부함수를 실행되면서 id값인 함수의 인자와 지역변수값이 내부함수의 클로저 객체로 남아 외부함수의 인자와 변수에
접근이 가능하다. 그러나 클로저를 남발하면 위험하다. 가비지컬렉션 대상이 되어여할 객체들이 메모리상에 남아있어 오버플로우가 발생할 수 있다.

2. private 변수 생성

private한 변수로 접근가능하기 때문에 안전하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Hello(name) {
this._name = name;
}

Hello.prototype.say = function() { //prototype을 통해 객체를 다룸
console.log('Hello, ' + this._name);
}

var hello1 = new Hello('a');
var hello2 = new Hello('b');
var hello3 = new Hello('c');

hello1.say(); // 'Hello, a'
hello2.say(); // 'Hello, b'
hello3.say(); // 'Hello, c'
hello1._name = 'anonymous'; //외부에서 접근 가능함
hello1.say(); // 'Hello, anonymous'

원하는 시점에서 내부 클로저를 실행할 수 있으며 private변수를 가질 수 있다.
위의 코드는 바로 변수에 접근해서 속성값들을 바꿀 수 있어 안전하지 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hello(name) {
var _name = name;
return function() {
console.log('Hello, ' + _name);
};
}

var hello1 = hello('a');
var hello2 = hello('b');
var hello3 = hello('c');

hello1(); // 'Hello, a'
hello2(); // 'Hello, b'
hello3(); // 'Hello, c'

hello1 = null; // 메모리가 소모됨, 그러므로 사용이 끝나면 참조를 제거한다!
hello2 = null;
hello3 = null;

클로저를 사용한 예제는 private한 변수 이므로 아무리 속성값을 위와 같이 바꾸려고 해도 바뀌지 않는다.
또한 사용이 끝나면 참조를 제거한다.

업무에 적용한 부분

업무를 진행하면서 클로저를 사용할 일이 많지는 않았다.
calculateCoords함수에서 외부함수의 참조변수를 내부함수인 return하는 함수에서 접근할 수 있기 때문에
루프문을 돌때마다 참조변수를 넣을 필요가 없다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  $img.siblings('map').children().each(function () {
const $area = $(this);
// 원본 coords
const originCoords = $area.data('origin-coords').split(',');
// 업데이트된 coords 속성 추가
const updateCoords = originCoords.map(calculateCoords(imgWidth, imgHeight, wPercent, hPercent));
$area.attr('coords', updateCoords.toString());
});
});

function calculateCoords(imgWidth, imgHeight, wPercent, hPercent) {
return function (location, i) {
return i % 2 === 0 ? parseInt(((location / imgWidth) * 100) * wPercent) : parseInt(((location /imgHeight * 100) * hPercent);
}
}