출처:
inner fuction = 내장함수 = 중첩함수
클로저란 무엇인가?
클로저는 바깥 합수의 변수에 접근할 수 있는 중첩합수이다. 클로저는 세개의 스콥체인(유효범위체인)을 갖고 있다. 1.자기 자신, 2.바깥 함수의 변수에 접근하는 것, 3.전역 변수에 접근하는 것 이렇게 세 개이다.
중첩함수는 바깥함수의 변수 뿐만 아니라 매개변수(parameter)에도 접근할 수 있다. 중첩함수는 바깥함수의 매개변수(parameter)는 사용할 수 있지만, 바깥함수의 arguments 객체를 호출할 수는 없다.
다른 함수 안에 함수를 추가해서 클로저를 만들수 있다.
자바스크립트의 클로저 기본 예제
function showName (firstName, lastName) {
var nameIntro = "Your name is ";
//이 중첩함수는 매개변수(parameter)를 포함해서 바깥 함수의 변수에 접근가능하다
function makeFullName () {
return nameIntro + firstName + " " + lastName;
}
return makeFullName ();
}
showName ("Michael", "Jackson"); // Your name is Michael Jackson
클로저는 Node.js에서 많이 쓰인다. Node.js의 비동기와 non-blocking 구조에서 자주 쓰인다. 또한 jQuery나 자바스크립트의 코드 부분부분에서도 자주 사용된다.
클로저 jQuery 예제
$(function() {
var selections = [];
$(".niners").click(function() {
//이 클로저는 selections에 접근할 수 있다.
selections.push (this.prop("name"));
//바깥 함수의 범위에 있는 변수인 selections을 수정했다.
});
});
클로저의 규칙과 부작용
1. 클로저는 바깥함수가 return된 후에도 바깥함수의 변수에 접근할 수 있다.
클로저의 가장 중요하고 다루기 어려운 특징중 하나가 중첩함수는 바깥함수가 return된 후에도 여전히 바깥 함수의 변수에 접근할 수 있다는 점이다. 자바스크립트의 함수가 실행될 때, 그 함수가 생성될 때 형성된 그 스콥체인을 사용한다. 이 의미는 바깥 함수가 리턴된 후에도 중첩함수는 여전히 바깥 함수의 변수에 접근할 수 있다는 뜻이다. 그래서 프로그램상에서 계속 중첩함수를 호출할 수 있다. 다음 예제를 보자
function celebrityName (firstName) {
var nameIntro = "This celebrity is ";
//중첩함수는 바깥함수의 매개변수와 변수에 접근할 수 있다.
function lastName (theLastName) {
return nameIntro + firstName + " " + theLastName;
}
return lastName;
}
var mjName = celebrityName ("Michael"); //이 시점에서 바깥함수인 celerityName는 리턴됐다.
//클로저인 lastName은 바깥함수가 리턴된 후에 여기에서 불린다.
//클로저는 여전히 바깥함수의 변수와 매개변수에 접근이 가능하다.
mjName ("Jackson"); // This celebrity is Michael Jackson
2. 클로저는 바깥함수 변수의 reference(참조)를 저장한다; 실제 값을 저장하는게 아니다.
클로저가 불리기 전에 바깥함수의 변수 값이 변한다는 점은 클로저를 더욱 흥미롭게 만들어준다. 바로 이것이 아래와 같은 예제처럼 창의적인 방법으로 사용할만한 강력한 기능이다.
function celebrityID () {
var celebrityID = 999;
//중첩함수를 갖는 객채를 리턴
//모든 중첩함수는 바깥 함수의 변수에 접근할 수 있다.
return {
getID: function () {
//이 중첩함수는 수정된 celebrityID를 리턴할 것이다.
return celebrityID;
},
setID: function (theNewID) {
//이 중첩함수는 바깥 함수의 변수 값을 바꾼다.
celebrityID = theNewID;
}
}
}
var mjID = celebrityID (); //이 시점에서 celebrityID가 리턴되었다.
mjID.getID(); // 999
mjID.setID(567); //바깥함수의 변수 값을 바꾸었다.
mjID.getID(); // 567: 수정된 celebrityID 를 리턴한다.
3. 엉망이 되어버린 클로저
클로저는 바깥 함수의 수정된 변수에 접근할 수 있기 때문에, 바깥 함수의 변수가 루프구문에서 변하게 되면서 버그를 유발하기도 한다.
//코드에 대한 설명은 코드 아래에서..
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function () {
return uniqueID + i;
}
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator(actionCelebs);
var stalloneID = createIdForActionCelebs[0]; console.log(stalloneID.id()); // 103
위의 예제에서, 익명함수가 불릴 때 i값은 3이다. uniqueID+3으로 모든 celebritiesID에 103이 된다. 그래서 리턴된 배열의 id는 100,101,102가 아니라 모두 103이 된다.
이러한 현상이 일어나는 이유는 전 예제에서 봤듯이, 클로저(이 예제에서는 익명함수)가 바깥 함수의 변수에 값이 아닌 참조로 접근하기 때문이다. 그래서 루프가 다 돌고 난 후의 마지막 값인 3에 클로저가 i변수에 접근하게 된다.
celebrityIDCreator함수가 리턴된 값인 createIdForActionCelebs 배열을 보면 id에 익명함수가 저장되어 있음
stalloneId.id() 에서 비로서 함수가 불리게 되고, 이 때 i값은 3이 되어있다.
이러한 부작용(버그)를 수정하기 위해 다음과 같이 즉시실행함수를 사용할 수 있다.
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
//매개변수 j는 즉시실행함수 호출에서 넘겨진 i이다.
theCelebrities[i]["id"] = function (j) {
return function () {
//각 회전마다 현재 i값을 즉시실행함수에 전달하게 되고 배열에 정확한 값을 저장하게 된다.(위의 예제처럼 id에 함수를 저장하는게 아니라)
return uniqueID + j;
} () //함수 끝에 ()를 붙이면, 함수가 바로 실행되고, 함수 자체가 아니라
uniqueID + j를 리턴할 수 있다.
} (i); //파라미터로 i를 넘겨서 함수를 바로 호출
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id); // 100
var cruiseID = createIdForActionCelebs [1]; console.log(cruiseID.id); // 101
'개발 > Javascript' 카테고리의 다른 글
알아야할 필요가 있는 자바스크립트 객체지향프로그래밍 (0) | 2015.04.13 |
---|---|
자바스크립트 프로토타입 (0) | 2015.04.11 |
자바스크립트에서의 함수(function)과 메서드(method) (0) | 2015.04.07 |
동일출처정책(same origin policy) (0) | 2015.03.26 |
$(function() {}); and (function() {})(); (0) | 2015.03.25 |