함수와 함수가 선언된 어휘적 환경(lexical environment)의 조합 - MDN
??????????????? 진짜 도대체 뭔 소리람 ??????????????????????
즉, 클로저란 내부함수가 자신이 선언되었을 때의 환경인 스코프를 기억하면서 자신이 선언된 환경 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수를 말한다.
자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부함수 밖에서 내부함수가 호출되더라도 외부함수의 지역변수에 접근할 수 있는데 이러한 함수를 클로저라고 부른다. - poiemaweb
사진 출처 : poiemaweb
function outer() {
var name = 'movie';
function inner() {
console.log(name);
}
// inner()는 outer() 내부에 정의된 함수이다.
// 즉 outer() 내부에서만 사용가능 하다.
// inner()는 자신만의 지역 변수를 가지지 않는다.
// 하지만 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 inner()는 outer()의 변수 name에 접근할 수 있다.
// outer()는 inner()의 상위 스코프이다.
// 만약 inner()가 전역에 선언되었다면 inner()의 상위 스코프는 전역 스코프가 된다.
inner(); // [log] movie
}
outer();
function outer() {
var name = 'movie';
function inner() {
console.log(name);
}
inner(); // [log] movie
}
outer();
inner()
가 호출되면 inner()
의 실행 컨텍스트가 EC 스택에 쌓인다.outer()
스코프를 가르키는 함수', 'outer()
의 활성 객체', '함수 자신의 스코프를 가르키는 활성 객체'를 순차적으로 바인딩한다. 변수 객체 (variable object) = 활성 객체 (activation object)
- 변수, 매개변수, 인수, 함수 선언에 대한 정보를 가지고 있다.
- 객체가 사용할 매개변수, 사용자가 정의한 변수 및 객체를 저장한다.
스코프 체인 (scope chain)
사진 출처 : poiemaweb
SC: 스코프 체인, AO: 활성 객체, GO: 전역 객체
일종의 리스트
- 전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례대로 저장
- 해당 전역 또는 함수가 참조할 수 있는 변수, 함수 선언 등의 정보를 담고 있는 전역 객체 또는 확성 객체의 리스트를 가르킨다.
즉, 내부함수가 외부함수의 변수에 접근할 수 있는 이유는 lexical scope의 레퍼런스를 차례대로 저장하고 있는 실행 컨텍스트의 스코프 체인을 자바스크립트 엔진이 검색하기 때문에 가능한 일이다.
function outer() {
var name = 'movie';
function inner() {
console.log(name);
}
return inner();
} // outer() 함수에서 inner() 함수를 반환하고 있다.
// 스코프의 lexical environment 유지
var escapedInner = outer(); // return된 inner() 함수가 변수에 저장된다.
escapedInner(); // [log] movie
escapedInner
는 outer()
가 실행될 때 생성된 inner()
함수의 인스턴스에 대한 참조다.
inner()
의 인스턴스는 변수 name
이 있는 lexical environment에 대한 참조를 유지한다.
이런 이유로, escapedInner가 호출될 때 name
은 사용할 수 있는 상태이다.
- 몇몇 프로그래밍 언어에서는 함수 안의 지역변수들은 그 함수가 처리되는 동안에만 존재한다. (즉,
outer()
가 처리되는 동안에만name
이 존재한다.) 그래서outer()
의 실행이 끝나면name
변수에 접근하지 못할 것이라고 예상하는 것이 일반적이다. 하지만 자바스크립트의 경우에는 다르다!- 자바스크립트에서는 함수를 리턴하고 리턴된 함수가 클로저를 형성하기 때문이다.
function addFunctionFactory(x) {
var y = 1;
return function(z) {
y = 100;
return x + y + z; // x:? + y: 100 + z:?
};
}
var add5 = addFunctionFactory(5); // x: 5
var add10 = addFunctionFactory(10); // x: 10
//클로저에 x와 y의 환경이 저장됨
console.log(add5(2)); // 107 (x:5 + y:100 + z:2)
console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
//함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산
addFunctionFactory
addFunctionFactory
은 함수를 만들어내는 공장이다. addFunctionFactory
은 인자를 가진 함수를 리턴한다. add5
와 add10
add5
의 lexical environment에서 클로저 내부의 x는 5지만, add10
의 lexical environment에서 클로저 내부의 x값은 10이다. function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
private method : 클래스 내부 다른 메서드에서만 해당 메서드를 호출할 수 있다. (자바스크립트에서는 태생적으로 이런 방법을 제공하지 않는다 🧐;)
var counter = (function() {
var privateCounter = 0; // 프라이빗 아이템 1️⃣
function changeBy(val) { // 프라이빗 아이템 2️⃣
privateCounter += val;
}
// 1️⃣과 2️⃣는 외부에서 접근될 수 없다.
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
// 익명 래퍼
// 반환된 세개의 퍼블릭 함수
})();
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1
private 메서드는
위의 세가지 퍼블릭 함수는 같은 환경을 공유하는 클로저이다.
자바스크립트의 lexical scope 덕분에 세 함수는 프라이빗 아이템에 접근할 수 있다.
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
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 */
위와 같이 코드를 작성하게 된다면 counter1
과 counter2
는 서로 독립성을 유지한다.
각 클로저는 그들 고유의 클로저를 통해 프라이빗 아이템의 다른 버전을 참조한다.
각 카운터가 호출될 때마다 하나의 클로저에서 변수값을 변경해도 다른 클로저에는 영향을 주지 않는다.
var toggle = (function () {
var isShow = false;
// ① 클로저를 반환
return function () {
box.style.display = isShow ? 'block' : 'none';
// ③ 상태 변경
isShow = !isShow;
};
})();
// ② 이벤트 프로퍼티에 클로저를 할당
toggleBtn.onclick = toggle;
현재 상태를 기억하고 상태가 변경되어도 최신 상태를 유지해야하는 상황에 유용하다.
만약 클로저가 존재하지 않는다면 이를 위해서 전역 변수를 사용할 수 밖에 없다.
(기억하자 !: 전역 변수는 접근이 쉬워 많은 부작용을 유발하므로 사용을 억제해야한다.)
// 1️⃣ : 지역변수를 사용할 경우
function increase() {
// 카운트 상태를 유지하기 위한 지역 변수
var counter = 0;
return ++counter;
}
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
// 2️⃣ : 클로저를 활용할 경우
var increase = (function () {
// 카운트 상태를 유지하기 위한 자유 변수
var counter = 0;
// 클로저를 반환
return function () {
return ++counter;
};
}());
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
1️⃣ 지역변수를 활용할 경우 전역변수를 사용하지 않기 때문에 의도치 않은 상태 변경은 방지한다.
하지만 increase()
함수가 호출될 때 지역변수가 초기화된다. (변경된 이전 상태를 기억하지 못한다.)
2️⃣ 클로저를 활용할 경우 전역변수를 사용하지 않기 때문에 의도치 않은 상태 변경은 방지하면서,
increase()
가 호출될 때마다 counter
가 초기화 되지 않기 때문에 이전 상태를 유지할 수 있다.