outer의 실행 컨텍스트가 종료된 뒤엔 inner함수를 호출할 수 없음.
위의 코드는 inner함수의 결과값만 outer2에 저장
outer의 실행 컨텍스트가 종료됐음에도 그 안에 선언된 inner함수에 접근할 수 있음.
뿐만 아니라 outer의 변수를 사용할 수 있음.
inner함수의 실행 컨텍스트
environmentRecord : 수집할 정보 없음 (선언된 변수 없음)
outerEnvironmentReference : inner함수가 선언된 위치의 LexicalEnvironment가 참조복사됨 -> outer함수의 lexicalEnvironment가 저장됨.
inner함수의 실행 시점(let outer2=outer()
)에는 outer 함수는 이미 실행이 종료된 상태 -> 어떻게 접근 ?
== 가비지 컬렉터의 동작 방식 때문
가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않음.
함수의 실행 컨텍스트가 종료된 후에도 LexicalEnvironment가 가비지 컬렉터의 수집 대상에서 제외되는 경우는 지역변수를 참조하는 내부 함수가 외부로 전달된 경우가 유일
스펙상으로는 선언 당시의 LexicalEnvironment를 전부 GC하지 않도록 되어있으나, 2019 기준 크롬이나 Node.js 등에서 사용중인 v8엔진의 경우 내부 함수에서 실제로 사용하는 변수만 남겨두고 나머지는 GC하도록 최적화돼 있음.
따라서 클로저란,
➤ 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상
클로저는 실행 컨텍스트가 끝난 뒤에도 변수가 남아있는 현상
-> 저장 공간을 계속 차지하고 있음
필요성이 사라진 뒤에 메모리를 소모하지 않도록 식별자에 참조형이 아닌 기본형 데이터(null/ undefined)를 할당해 참조카운트를 0으로 만들어주면 좋음!
let outer = (function() {
let a = 1;
let inner = function() {
return ++a;
}
return inner;
})();
console.log(outer());
console.log(outer());
//outer 식별자의 inner함수 참조를 끊음.
outer = null
(function() {
let count = 0;
let button = document.createElement('button');
button.innerText = 'click'
let clickHandler = function() {
console.log(++count, 'time clicked');
if(count >= 10) {
button.removeEventListener('click', clickHandler);
// clickHandler 식별자의 함수 참조를 끊음
clickHandler = null;
}
}
button.addEventListener('click', clickHandler);
document.body.appendChild(button)
})
let fruits = ['banana', 'apple', 'lemon'];
let $ul = document.createElement('ul');
fruits.forEach(function(fruit){
let $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', function() {
alert('this is '+fruit)
})
$ul.appendChild($li)
});
document.body.appendChild($ul)
let fruits = ['banana', 'apple', 'lemon'];
let $ul = document.createElement('ul');
let alertFruit = function(fruit){
alert('this is ' + fruit)
}
fruits.forEach(function(fruit){
let $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', alertFruit.bind(null, fruit));
$ul.appendChild($li)
})
document.body.appendChild($ul)
addEventListener는 콜백 함수 호출 시 첫번째 인자에 '이벤트 객체'를 주입하기 때문에, bind메서드를 활용해 이 문제를 해결
다만 이 경우 다음의 문제가 있음
let fruits = ['banana', 'apple', 'lemon'];
let $ul = document.createElement('ul');
let alertFruit = function(fruit){
return function () {
alert('this is ' + fruit)
}
}
fruits.forEach(function(fruit){
let $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', alertFruit(fruit));
$ul.appendChild($li)
})
document.body.appendChild($ul)
어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 개념
외부에서는 오직 outer함수가 return한 정보에만 접근할 수 있음. return한 변수들은 public이 되고, 그렇지 않은 변수는 private이 됨.
n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가 나중에 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있는 함수 - 클로저를 핵심 기법으로 사용
짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것.
scroll, wheel, mousemove, resize 등에 적용해 프론트엔트 성능 최적화에 큰 도움을 주는 기능
여러개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것.
당장 필요한 정보만 받아 전달하고 또 필요한 정보가 들어오면 전달하는 식으로 하면 결국 마지막 인자가 넘어갈 때까지 함수 실행을 미루는 셈 = 지연 실행
: 원하는 시점까지 지연시켰다가 실행해야하는 경우 사용
클로저를 활용한 모듈 패턴도 정말 정말 맛있답니다 ~ 한번 시식해보시고 가세요 ~