함수와 함수가 선언된 어휘적 환경(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가 초기화 되지 않기 때문에 이전 상태를 유지할 수 있다.