remagine
Closure 클로저에 대해서 본문
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures
는 정말 천국과도 같은 사이트다
1. 클로저는 독립적인(자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 기억한다.
뭔소리인지 모르겠다.
일단 클로저는 내부함수를 지칭하는 말인듯 하다
2.
1 2 3 4 5 6 7 8 | function init() { var name = "모질라"; // init에 있는 지역 변수 name function displayName() { // 내부 함수, 즉 클로저인 displayName() alert(name); // 부모 함수에 정의된 변수를 사용한다 } displayName(); } init(); | cs |
실행하면 모질라 얼럿창이 뜬다
init를 쳐보면
1 2 3 4 5 6 7 | function init() { var name = "모질라"; // init에 있는 지역 변수 name function displayName() { // 내부 함수, 즉 클로저인 displayName() alert(name); // 부모 함수에 정의된 변수를 사용한다 } displayName(); } | cs |
로 정의된다.
3.
1 2 3 4 5 6 7 8 9 10 | function makeFunc() { var name = "모질라"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc(); | cs |
이번에는 좀 신기한 상황을 살펴보자
똑같이 함수를 정의했는데, 이번엔 그 함수를 var my Func에 저장했다.
myFunc를 console에서 확인해보면
1 2 3 | function displayName(){ alert(name); } | cs |
이 나온다. 즉 myFunc의 return값이 displayName함수인 것이다. (var name은 사라지고 없다)
myFunc() 실행의 결과는?
2번과 똑같이 "모질라"가 잘 나온다.
그럼 대체 var name도 없는데 어떻게 "모질라"라는 값을 가져올 수 있을까?
3.1 일단 지역변수는 함수가 실행하는 동안에만 존재한ㄷ.
3.2 myFunc의 정의는 displayName 함수이다.
3.3 myFunc()실행은 displayName 함수 실행과 같을까? (같아보이지만 그렇지 않다네)
왜 그럴까?
이것은 myFunc 함수가 클로저 이기 때문이다. 클로저는 두개의 것( 함수, 그 함수가 만들어진 환경)으로 이루어진 특별한 객체의 한 종류이다. 환경이라 함은 클로저가 생성될 때 그 범위 안에 있던 여러 지역 변수들로 이루어진다. 이 경우에 myFunc는 displayName 함수와 "모질라" 문자열을 포함하는 클로저이다.
4.
1 2 3 4 5 6 7 8 9 10 11 | function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); print(add5(2)); // 7 print(add10(2)); // 12 | cs |
makeAdder는 함수를 return한다. 그 함수는 y를 인자로 받아 return x+y를 return한다
add5, add10은 어떤값을 가질까
1 2 3 4 | add10 function (y) { return x + y; } | cs |
var add5,10 모두 함수를 return한다.
하지만 add5와 add10의 차이는 생성자 인수의 초기값이 다르다는 것인데
신기하게도 이것을 다 기억하고 있다.
두 add5, add10 함수는 같은 정의를 가지지만, 다른 환경을 저장한다. add5의 환경에서 x는 5이지만 add10의 환경에서 x는 10이다.
5. 실용적인 클로저
클로저는 어떤 데이터(환경)와 함수를 연관시키는데 사용할 수 있다.
클로저를 통해 모듈화가 가능하다.
객체지향 프로그래밍에서는 객체가 데이터(그 객체의 속성)와 하나 이상의 메소드를 연관 시킨다.
6. 예제
실용적인 예제 : 페이지의 글자 크기를 조정하는 몇 개의 버튼을 만든다고 생각해보자. body 엘리먼트에 px단위로 font-size를 설정하고 다른 엘리먼트에서는 상대적인 em 단위로 font-size를 설정하면 되겠다.
이제 body 엘리먼트의 font-size를 바꾸면 em단위로 설정된(em은 상위 font-size를 상속받는다? 정확히 말하면 현재 element의 font-size이지만, 상속현상이 일어나니...) 다른 엘리먼트들의 글자 크기도 바뀐다.
1 2 3 4 5 6 7 8 9 10 11 | body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; } | cs |
자바스크립트 코드
1 2 3 4 5 6 7 8 9 | function makeSizer(size) { return function() { document.body.style.fontSize = size + 'px'; }; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16); | cs |
함수를 return하는 makeSizer(size)함수
초기값 (환경)이 각기 다른 변수들을
var size12,14,16에 각각 저장하면 각 변수는 각기 다른 환경을 저장한 같은 정의의 함수이다.
--자바에서 클래스를 모듈화하고 이를 생성자로 각기 다르게 생성하는 느낌과 비슷하네
이제 버튼과 연관시키자
1 2 3 | document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; | cs |
1 2 3 | <a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a> | cs |
http://jsfiddle.net/vnkuZ/ 에서 결과를 확인할 수 있다
7. 클로저를 이용해서 private 함수 흉내내기
자바에서는 같은 클래스에서만 사용가능한 private 제한자가 있다.
자바스크립트는 이를 지원하지 않지만, 클로저를 이용해서 흉내낼 수 있다. private함수는 코드에 제한적인 접근만을 허용한다는 점 뿐만 아니라 전역 네임스페이스를 깔끔하게 유지할 수 있다는 점에서 중요하다.
아래에 모듈 패턴이라고 알려진 클로저를 통해 몇개의 public 함수가 private 함수와 변수에 접근하는 코드가 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); | cs |
var counter에 익명함수를 저장했다.
그 함수는
var privateCounter라 하는 private 변수를 전역으로 가지고 있다.(물론 private는 아니지만...)
1 2 3 | function changeBy(val){ privateCounter += val; } | cs |
익명함수안에 있는 changeBy라는 함수는 val 이라는 인자를 받아서 privateCounter에 더해주는 함수이다
이 익명함수의 return값은 배열인데 {} (유사배열 ? )
1 2 3 4 5 6 7 8 9 10 11 | { increment : function() { changeBy(1); }, decrement : function() { changeBy(-1); }, value : function() { return privateCounter; } } | cs |
이 담겨있다.
key = increment , value 는 changeBy(1) 인 형식이랄까
console로 확인해보자
1 2 3 4 5 6 | console.log(counter.value()); // logs 0 counter.increment(); counter.increment(); console.log(counter.value()); // logs 2 counter.decrement(); console.log(counter.value()); // logs 1 | cs |
전역으로 변수를 생성하지 않고, 지역변수임에도 불구하고
외부에서 컨트롤이 가능하다.
지역변수는 함수의 생성과 동시에 태어나고 종료와 동시에 죽는다고 알고있는데 .( ?)
이전 예제에서는 각 클로저가 자기만의 환경을 가졌지만 이 예제에서는 하나의 환경을 counter.increment, counter.decrement, counter.value 세함수가 공유한다. OMG!
공유되는 환경은 정의되자마자 실행되는 익명 함수 안에서 만들어진다. 이 환경에는 두 개의 private 아이템이 존재한다.
하나는 privateCounter라는 변수이고, 나머지 하나는 changeBy라는 함수이다. 이 두 아이템 모두 익명함수 외부에선 접근할 수 없다(? 익명함수라는 특징으로 접근이 불가능한 것인가?)
하지만 익명함수 안에 정의된 세개의 public 함수에서 사용되고 반환된다 (public일수 잇는 것은 return으로 외부로 유출되기 때문인가?)
이 세개의 public 함수는 같은 환경을 공유하는 클로저이다. 자바스크립트 문법적 스코핑(?) 덕분에 세 함수 모두 privateCounter 변수와 changeBy 함수에 접근할 수 있다.
익명 함수가 카운터를 정의하고 이것을 counter 변수에 할당한다는 걸 알아차렸을 것이다. 이 함수를 다른 변수에 저장하고 이변수를 이용해 여러개의 카운터를 만들수도 있다.
1 2 3 4 5 6 7 8 9 | var Counter1 = makeCounter(); var Counter2 = makeCounter(); alert(Counter1.value()); /* 0 */ Counter1.increment(); Counter1.increment(); alert(Counter1.value()); /* 2 */ Counter1.decrement(); alert(Counter1.value()); /* 1 */ alert(Counter2.value()); /* 0 */ | cs |
같은 함수에 접근하지만
Counter1의 연산이
Connter2에게 영향을 주지 않는다. !!
왜일까?
--> 스스로 답을 해보자
!! 알았다.
Counter1이 생성될때 {}을 return 받는다. increment의 환경은 Counter1이 된다.
Counter2도 마찬가지. 즉 this가 서로 다르다.
그래서 서로 다른 제어가 가능하다.
두개의 카운터가 어떻게 독립적으로 존재하는지 주목하라. makeCounter() 함수를 호출하면서 생긴 환경은 호출할 때마다 다르다. 클로저 변수 privateCounter는 다른 인스턴스를 가진다.
객체지향 프로그래밍을 사용할 때 얻는 이점인 정보 은닉과 캡슐화를 클로저를 사용함으로써 얻을 수 있다.
8 자주하는 실수 : 반복문 안에서 클로저 만들기
이건 예전 js로 타자게임만들때도 이해하기 어려웠던 현상이였다.
1 2 3 4 | <p id="help">Helpful notes will appear here</p> <p>E-mail: <input type="text" id="email" name="email"></p> <p>Name: <input type="text" id="name" name="name"></p> <p>Age: <input type="text" id="age" name="age"></p> | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp(); | cs |
http://jsfiddle.net/v7gjv/ 에서 확인해보자
위 코드는 제대로 작동하지 않는다.
onfocus가 어디에 잡히든 무조건 Your age(youo must be over 16)만 나온다.
왜일까? 생각해보잨
흠. 일단 느낌에는 var item이 문제인가 싶기도한데...
fot 문에서 loop돌때 break되지않고 3번다돌고 return되는건가?
이유는 onfocus 이벤트에 지정한 함수가 클로저라는 것이다. 이 클로저는 함수 본채와 setupHelp 함수의 스코프로 이루어져 있다. 세개의 클로저가 만들어졌지만 각 클로저는 하나의 환경을 공유한다. (???? 하나의 정의를 공유하는구나??)
반복문이 끝나고 onfocus콜백이 실행될 때 콜백의 환경에서 item변수는 (세개의 클로저가 공유한다) helpText 리스트의 마지막 요소를 가리키고 있을 것이다.
여러개의 콜로저를 이용해서 문제를 해결할 수 있다. 위에서 언급한 함수공장을 사용해 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function showHelp(help) { document.getElementById('help').innerHTML = help; } function makeHelpCallback(help) { return function() { showHelp(help); }; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = makeHelpCallback(item.help); } } setupHelp(); | cs |
http://jsfiddle.net/v7gjv/1/ 이제 잘 작동한다는 것을 확인할 수 있다.
왜이럴까?
이걸 이해할 수 있다면 좋을 텐데
나중에 나의 이해를 추가해보자.
추가로 원문에는 없지만 makeHelpCallback 함수를 이용하지 않고 즉시 실행함수를 이용하면 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = (function(help) { return function() { showHelp(help); } })(item.help); } } setupHelp(); | cs |
9. 성능과 관련해서
클로저가 필요하지 않은 작업인데도 함수안에 함수를 만드는 것은 스크립트 처리 속도와 메모리 사용량 모두에 현명한 선택이 아니라고 한다.
새로운 오브젝트나 클래스를 만들 때 오브젝트 생성자에 메쏘드를 정의하는 것보다 오브젝트의 프로토타입에 정의하는 것이 좋다. 오브젝트 생성자에 정의하게 되면 생성자가 불릴때마다 메쏘드가 새로 할당되기 때문이다.
- 예제 -
1 2 3 4 5 6 7 8 9 10 11 | function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; } | cs |
위의 코드는 일일이 메소드를 만들면서 클로저의 이점을 살리지 못하고 있다.
1 2 3 4 5 6 7 8 9 10 11 12 | function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype = { getName: function() { return this.name; }, getMessage: function() { return this.message; } }; | cs |
또는
1 2 3 4 5 6 7 8 9 10 | function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; }; | cs |
처럼 하자.
위의 두 예제에서는 상속된 속성은 모든 오브젝트에서 사용될 수 있고 메쏘드 정의가 오브젝트가 생성될 때마다 일어나지 않는다.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model 참고하자
'JAVASCRIPT' 카테고리의 다른 글
This의 중요성과 Call() 사용 (0) | 2017.05.04 |
---|---|
JAVASCRIPT 초급 강의 3 (0) | 2017.02.17 |
JAVASCRIPT 초급 강의 2 (0) | 2017.02.17 |
JAVASCRIPT 초급 강의 1 (0) | 2017.02.17 |