기존 블로그에 작성한 내용을 velog로 이전한 글입니다
외부 함수가 소멸했음에도 내부함수가 외부함수의 지역변수(자유변수)에 접근할 수 있는 메커니즘.
캡슐화를 통해 외부함수의 지역변수(자유변수)의 오염을 막을 수 있다.
실행 컨텍스트 스택에서 외부함수가 제거 되더라도 내부함수를 참조하고 있다면 렉시컬 환경은 소멸하지 않는다.
const x = 1;
// ①
function outer() {
const x = 10;
const inner = function () {
console.log(x);
}; // ②
return inner;
}
// 함수 outer를 호출하면 중첩 함수 inner를 반환한다.
// 그리고 함수 outer의 실행 컨텍스트는 실행 컨텍스트 스택에서 pop된다.
const innerFunc = outer(); // ③
innerFunc(); // ④ 10
① : outer 함수 객체의 상위 스코프가 할당됨
② : outer 함수 호출로 outer 렉시컬 환경 생성 및 inner 함수 객체의 상위 스코프가 할당됨
③ : outer 함수가 종료됨과 동시에 innerFunc에 의해 outer 렉시컬 환경이 참조됨
④ : innerFunc 호출과 함께 innerFunc 렉시컬 환경이 만들어지고 실행 컨텍스트 스택에 올라감
모든 함수는 이론상 모두 클로저이다.
일반적으로 외부 함수가 종료되어도 내부 함수가 유지되고, 외부 함수의 변수에 접근 가능한 함수를 클로저라고 부른다.
캡슐화를 통한 은닉(Information hiding)을 위해 사용한다
const visitors = {};
const counter = (function () {
// 카운트 상태를 유지하기 위한 자유 변수
let man = 0;
let child = 0;
// 클로저를 메소드로 갖는 객체를 반환한다.
// 객체 리터럴은 스코프를 만들지 않는다.
// 따라서 아래 메소드들의 상위 스코프는 즉시 실행 함수의 스코프이다.
return {
// child: 0, // 프로퍼티는 public이므로 정보 은닉이 되지 않는다.
increaseMan() {
visitors.count = ++man + child; // 상태 변경
},
increaseChild() {
visitors.count = man + ++child; // 상태 변경
},
viewVisitors() {
console.log("성인 수:" + man + "아이 수:" + child);
},
};
})();
counter.increaseMan();
counter.increaseChild();
counter.viewVisitors(); // 성인 수: 1 아이 수: 1
즉시 실행 함수를 사용하여 man과 child가 매번 재정의 되는 것을 막았다.
또한 man과 child를 변경할 수 있는 것을 내부 함수로 한정하여 변수의 오염을 막았다.
increaseMan과 increaseChild, viewVisitors함수의 상위 스코프는 즉시 실행 함수이기 때문에 언제 호출되더라도 man과 child에 접근할 수 있다.
초기화 문에서 let 키워드로 선언한 변수를 사용하면 for 문이 반복될 때마다 for 문 코드의 새로운 렉시컬 환경이 생성된다.
따라서 반복문 내부에 함수 정의가 있을 경우 의미있게 사용할 수 있다.
var arr = [];
for (let i = 0; i < 5; i++) {
arr[i] = function () {
return i;
};
}
for (var j = 0; j < arr.length; j++) {
console.log(arr[j]()); // 0, 1, 2, 3, 4
}