티스토리 뷰
Javascript - 속깊은 자바스크립트 [2. 자바스크립트의 스코프와 클로저 -클로저]
Ideveloper2 2018. 4. 23. 23:38Javascript - 속깊은 자바스크립트[2. 자바스크립트의 스코프와 클로저 -클로저 ]
|시작하기에 앞서
앞서 스코프 개념에 대해서 살펴보았고, 고급 자바스크립트 개발자가 되기 위해 필수적 개념인 클로저에 대해 자세히 예제들과 함께 살펴보자.
| 2. 클로저란?
클로저란 아래와 같이 정의 할 수 있다.
특정 함수가 참조하는 변수들이 선언된 렉시컬 스코프 (lexical scope)는 계속 유지되는데, 그함수와 스코프를 묶어서 클로저라고 한다.
말로 설명하면 어려울 수있어 아래의 예제를 살펴보면서 이해해보자. 먼저 기본적으로 클로저가 나타나는 기본적인 환경은 스코프안에 스코프가 있을때, 즉 function안에 function이 선언 되었을때 이다
<script>
function outer () {
var count = 0;
var inner = function () {
return ++count;
};
return inner;
}
var increase = outer();
console.log(increase()); // === 1
console.log(increase()); // === 2
</script>
원래 count 변수는 outer() 함수의 로컬 변수이므로 일반적인 방법으로는 외부에서 접근할 수 없다. 마치 객체지향 언어에서 흔히 말하는 private 변수와 비슷하다. 그런데 count 변수에 접근하는 또 다른 함수 inner를 outer() 함수의 반환값으로 지정하고, 이를 글로벌 영역에 있는 increase 변수에 할당함으로써, outer() 함수 외부에서도 increase 변수를 통해 count 변수에 접근할 수 있게 되었다. 이러한 상황이 클로저의 가장 기본적인 개념이다.
위의 예를 더 활용한 아래예제를 살펴보자.
<script>
function outer () {
var count = 0;
return {
increase: function () {
return ++count; },
decrease: function () {
return --count;
}
};
}
var counter = outer();
console.log(counter.increase()); // === 1
console.log(counter.increase()); // === 2
console.log(counter.decrease()); // === 1
var counter2 = outer();
console.log(counter2.increase()); // === ?
</script>
<script>
var countFactory = (function () {
var staticCount = 0;
return function () {
var localCount = 0;
return {
increase: function () {
return {
static: ++staticCount,
local: ++localCount
};
},
decrease: function () {
return {
static: --staticCount,
local: --localCount
};
}
};
};
}());
var counter = countFactory(),
counter2 = countFactory();
console.log(counter.increase());
console.log(counter.increase());
console.log(counter2.decrease());
console.log(counter.increase());
</script>
앞의 counter 예에서 다시 IIFE로 스코프를 하나 더 추가하여 최상위에 staticCount 변수를 추가했다. 1번줄에서 IIFE로 statuc으로 사용할 로컬변수를 선언한다음, 앞의 예제에서 사용했던 함수를 반환한다. 해당함수가 실행되면 5번줄에 정의된 객체를 반환하여 counter별로 localCount 변수를 사용할수 있고, 공용으로 staticCount를 사용가능하다.
실행결과는 아래와 같다.
Object{static: 1, local: 1}
Object{static: 2, local: 2}
Object{static: 1, local: -1}
Object{static: 2, local: 3}
이처럼 IIFE를 한번 선언해 클로저를 생성하는 방법은다양한 곳에 활용될 수있고, 자바스크립트 개발자가 되려면 기본적으로 이해하고 있어야 할 내용이다. 위의 내용들을 살펴보면 클로저는 outer()와 같은 외부함수에서 inner와 같은 내부함수를 반환하여 다른곳에서 해당함수를 호출할때 발생한다는 것을 알 수 있다.
<script>
function sum(base) {
var inClosure = base;
return function (adder) {
return inClosure + adder; };
};
var fiveAdder = sum(5); // inClosure = 5 and return function
console.log(fiveAdder(3)); // === inClosure(5) + adder(3) === 8
var threeAdder = sum(3); // inClosure = 3 and return a new function
</script>
- 위의 예제는 같은 함수를 통해서 받은 값은 각각 A와 B로 2개의 함수가 생성되어, fiveAdder로 호출하면 A의 스코프체인을 사용하고, threeAdder로 호출하면 B의 스코프체인을 사용한다.
- 이처럼, 클로저를 통해 각 함수는 자기만의 고유한 값을 보유하고, 스코프 체인을 유지하면서 그 체인안에 있는 모든 변수의 값들을 유지한다.
- 위예에선, sum(5)를 호출할때 새로운 함수를 만들고, sum(3)을 만들때 다시 새로운 함수를 만드는 과정을 거친다. 다시말하면, sum() 함수를 호출할때마다 새로운 함수와 그함수가 참조하는 스코프체인이 각각 생성된다는 것을 이해할 수 있을것이다.
- 그리고 참고할점은 sum()함수를 호출할때마다 같은 모양의 함수들이 매번 새롭게 나오지만, 두함수가 같지 않은 이유는 두 함수가 할당받은 스코프 체인, 숨겨져있는 클로저가 다르기 때문이다.
지금까지는 함수 안에 함수가 있어서 내부의 함수가 반환 될때 클로저가 발생하고 사용된다고 하였는데, 클로저가 생기는 다른 상황이 있다. 이미 많은 사람이 사용하고 있는 패턴에서도 클로저를 사용하고 있을지도 모른다. 그 예를 아래에서 살펴보자.
<html>
<body>
<button id="btnToggle">Toggle Pending</button>
<div id="divPending">Pending</div>
<script>
(function () {
var pendingInterval = false,
div = document.getElementById("divPending"),
btn = document.getElementById("btnToggle");
function startPending() {
if (div.innerHTML.length > 13) {
div.innerHTML = "Pending";
}
div.innerHTML += ".";
};
btn.addEventListener("click", function () {
if (!pendingInterval) {
pendingInterval = setInterval(startPending, 500);
} else {
clearInterval(pendingInterval);
pendingInterval = false;
}
});
}());
</script>
</body>
</html>
위는 클로저를 이용한 아주 간단한 예이면서 자바스크립트 라이브러리 또는 모듈의 기본 형태와 비슷하다고한다. pedingInterval 아래 3줄에서는 내부함수에서만 접근 할 수 있는 private 변수를 선언했다.
글로벌 변수가 아니라 클로저를 이용해 내부에 로컬변수를 사용함으로써, 글로벌 변수를 사용하지 않는 아주 좋은 코드이다.
위의 상황에서 클로저가 발생한 경우는 크게 두가지인데, div.innerHTML="pending" 부분과, setInterval() 함수에서 첫번째 인자로 startPending 함수를 사용하는 부분이다. 이러한 경우 함수안에 함수가 있어서, 내부에 있는 함수가 반환되지 않고 이벤트 콜백함수로 호출될때도 클로저가 발생한다.
<html>
<body>
<div id="wrapper">
<button data-cb="1">Add div</button>
<button data-cb="2">Add img</button>
<button data-cb="delete">Clear</button>
Adding below<br/>
<div id="appendDiv"></div>
</div>
<script>
(function () {
var appendDiv = document.getElementById("appendDiv");
document.getElementById("wrapper").addEventListener("click", append);
function append(e) {
var target = e.target || e.srcElement || event.srcElement;
var callbackFunction = callback[target.getAttribute("data-cb")];
appendDiv.appendChild(callbackFunction());
};
var callback = {
"1": (function () {
var div = document.createElement("div");
div.innerHTML = "Adding new div";
return function () {
return div.cloneNode(true);
}
}()),
"2": (function () {
var img = document.createElement("img");
img.src="https://t1.daumcdn.net/cfile/tistory/011F554E50FD140F2B";
return function () {
return img.cloneNode(true);
}
}()),
"delete":function () {
appendDiv.innerHTML = "";
return document.createTextNode("Cleared");
}
};
}());
</script>
</body>
</html>
위의 예제와 같이 최초에 초기화 된 고정적인 값이나 변수를 자주 이용하는 경우 클로저를 통해 최초에 초기화해두고 콜백함수에서 지속해서 참조하는것이 퍼포먼스 상 유리하게 작용 할 수 있고, 객체의 속성이 자유롭고 쉬운 자바스크립트에서는 이러한 디자인 패턴이 아주 효율적이라고 한다. 특히 dom 같은 엘리먼트를 적극적으로 이용할 때 이방법이 아주 좋다고 한다.
>2.2 클로저의 단점
클로저는 메모리를 소모한다.
스코프 생성과 이후 변수 조회에 따른 퍼포먼스 손해가 있다.
- IE 최신버전에서는 해결되었지만 구버전에서는 특정 DOM에 이벤트를 할당해 놓고, DOM 삭제를 하면 메모리 누수까지 발생한다고 한다. 클로저는 이러한 메모리 누수를 가속화 한다고한다.
- 따라서 클로저를 생성할때는 하나의 거대한 클로저를 생성하기 보다는 각 변수나 함수의 생명주기를 분석하여, 효율적으로 나누는 것이 좋다고 한다.
스코프 생성과 이후 변수 조회에 따른 퍼포먼스 손해가 있다.
- with 구문과 마찬가지로 클로저의 하위에 있는 함수에서 상위에 있는 변수에 접근하고자 할때 클로저로 생성한 스코프를 탐색해야 하는 문제이다.
- 최근 자바스크립트 엔진들이 이러한 스코프 체인 내의 변수 탐색 최적화를 하고 있어서 스코프 체인이 하나나 두개이면 큰차이는 없다고 한다. 하지만 과하게 쌓이면 퍼포먼스에 영향을 끼친다고 한다.
- 클로저는 익숙하지 않으면 이해하기 어렵다는 점이 있기에,
1.어떠한 목적으로 클로저를 생성하고
2.어디에서 생성되었는지 불분명할때가 많다
고한다. 따라서 협업할때는 명확한 주석과 문서화가 필요하다고한다.
| 마치며
자바스크립트가 단순한 스크립트언어, 그리고 웹언어의 보조 역할일때는 필요없는 기능이였으나, 클라이언트 기반의 웹을 개발할 때 클로저를 활용하면 자원을 효율적으로 사용할 수 있다고 한다. 그리고 든 생각은 클로저가 모듈화와 관련해서는 정말 필수적인 개념이라고 생각했다. 왜냐하면 코드를 잘쪼개고 모듈화 할때 필수적인것이 전역변수를 쓰지않고 모듈내에서 스코프 관리를 잘해야 하기 때문이다. 아직은 이해가 확실히는 되지않은것 같고 흐름은 잡은것 같아, 필수적개념인 클로저에 대해 계속적으로 학습해 나가야겠다.
'Computer Engineering > Javascript' 카테고리의 다른 글
Javscript - 자바스크립트함수 [선언방식, hoisting, 반환값,arguments] (0) | 2018.06.26 |
---|---|
Javascript - 속 깊은 자바스크립트 [3.자바스크립트의 변수] (0) | 2018.05.07 |
Javascript - 속깊은 자바스크립트[2. 자바스크립트의 스코프와 클로저 -스코프 ] (1) | 2018.04.23 |
javascript- 속깊은 Javascript [1. 웹과 자바스크립트] (0) | 2018.04.21 |
Javascript - 전역객체와 this (0) | 2018.04.08 |