Giyoun's Blog

클로저

2023-03-05

클로저

클로저 제대로 알아보기

개념 정의

  • 함수와 그 함수가 선언된 렉시컬 환경의 조합이다.
  • 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상

💡 어떤 함수의 렉시컬 환경은 해당 함수의 생명주기가 종료되는 즉시 사라지는 것이 아니고, 참조가 없을 때 가비지 컬렉터에 의해서 사라진다.

렉시컬 환경과 클로저

  • 함수 객체는 내부 슬롯에 저장한 렉시컬 환경의 참조, 즉 상위 스코프를 자신이 존재하는 한 기억한다.

예시

const x = 1; function outer() { const x = 10; const inner = function () { console.log(x); } return inner; } const innerFunc = outer(); // 여기서 outer()는 inner를 리턴해주고 생명주기 종료 innerFunc(); // 10
  • outer 함수를 호출하면 중첩 함수 inner를 반환하고 생명 주기 마감
  • outer 함수 실행 컨텍스트 스택에서 팝
  • 이 때 outer 지역변수 x와 값 10 또한 생명 주기 마감
  • 그러나 innerFunct() 호출 하면 10 출력

💡 이와 같이 중첩함수가 외부함수보다 더 오래 유지되는 경우 이미 종료된 외부함수의 변수를 참조할 수 있다! 이러한 중첩함수를 클로저라고 한다.

클로저에 해당하지 않는 경우

  • 상위 스코프의 식별자를 참조 하지 않을 때
function foo () { const x = 1; const y = 2; // 클로저 x function bar () { const z = 3; debugger; // 상위 스코프의 식별자를 참조하지 않는다. console.log(z); } return bar; } const bar = foo (); bar();
  • 외부 함수보다 먼저 소멸하는 경우
function foo () { const x = 1; // bar함수는 클로저였지만 곧바로 소멸한다. // 이러한 함수는 일반적으로 클로저라고 하지 않는다. function bar () { debugger; // 상위 스코프의 식별자를 참조한다. console.log(x); } bar(); } foo();

클로저에 해당하는 경우

function foo () { const x = 1; const y = 2; // 클로저 // 중첩함수 bar는 외부함수보다 더 오래 유지되며 상위 스코프의 식별자를 참조한다. function bar () { debugger; console.log(x); } return bar; } const bar = foo (); bar();

💡 클로저에 의해 참조되는 상위 스코프의 변수를 자유변수라고 부른다.

  • 클로저란 함수가 자유 변수에 대해 닫혀있다.

클로저 활용

  • 상태를 안전하게 변경하고 유지하기 위해 사용! ( 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용)

오류 발생 우려 되는 코드

let num = 0; const increase = function () { return ++num; }; console.log(increase()); // 1 console.log(increase()); // 2 console.log(increase()); // 3
  • num은 전역변수이기 때문에 누구든 접근할 수 있고 변경할 수 있다는 문제점 발생

클로저 사용 안전성 보장 코드

const increase = (function () { let num = 0; // 클로저 return function () { return ++num; } }()); console.log(increase()); // 1 console.log(increase()); // 2 console.log(increase()); // 3
  • 클로저는 상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.

즉시 실행 함수

const counter = (function () { let num = 0; // 클로저인 메서드를 갖는 객체를 반환 // 객체 리터럴은 스코프를 만들지 않는다. // 따라서 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다. return { // num: 0 //프로퍼티는public하므로 은닉되지 않는다. increase() { return ++num; }, decrease() { return num > 0 ? --num : 0; } }; }()); console.log(counter.increase()); // 1 console.log(counter.increase()); // 2 console.log(counter.decrease()); // 1 console.log(counter.decrease()); // 0

생성자 함수로 표현

const Counter = (function () { let num = 0; function Counter() { // this.num = 0; // 프로퍼티는 public하므로 은닉되지 않는다. } Counter.prototype.increase = function () { return ++num; } Counter.prototype.decrease = function () { return num > 0 ? --num : 0; } return Counter; }()); const counter = new Counter(); console.log(counter.increase()); // 1 console.log(counter.increase()); // 2 console.log(counter.decrease()); // 1 console.log(counter.decrease()); // 0
  • 즉시 실행 함수가 반환하는 객체 리터럴은 즉시 실행 함수의 실행 단계에서 평가되어 객체가 된다. 이 때 객체의 메서드도 함수 객체로 생성된다. 객체 리터럴의 중괄호는 코드 블록이 아니므로 별도의 스코프를 생성하지 않는다.

함수형 프로그래밍에서의 활용

// 함수를 인수로 전달받고 함수를 반환하는 고차 함수 // 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다. function makeCounter(aux) { // 카운트 상태를 유지하기 위한 자유 변수 let counter = 0; // 클로저를 반환 return function () { // 인수로 전달받은 보조 함수에 상태 변경을 위임한다. counter = aux(counter); return counter; }; } // 보조 함수 function increase(n) { return ++n; } // 보조 함수 function decrease(n) { return n > 0 ? --n : 0; } // 함수로 함수를 생성한다. // makeCounter함수는 보조 함수를 인수로 전달받아 클로저를 반환한다. const increaser = makeCounter(increase); console.log(increaser()); // 1 console.log(increaser()); // 2 // increaser 함수와는 별개의 독립된 렉시컬 환경을 갖기 때문에 카운터 상태가 연동하지 않는다. const decreaser = makeCounter(decrease); console.log(decreaser()); // -1 console.log(decreaser()); // -2

💡 자유변수를 공유하지 않기 때문에 증감이 연동되지 않는다.

렉시컬 환경을 공유하는 클로저

// 함수를 인수로 전달받고 함수를 반환하는 고차 함수 // 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다. const Counter = (function () { // 카운트 상태를 유지하기 위한 자유 변수 let counter = 0; // 클로저를 반환 return function (aux) { // 인수로 전달받은 보조 함수에 상태 변경을 위임한다. counter = aux(counter); return counter; }; }()); // 보조 함수 function increase(n) { return ++n; } // 보조 함수 function decrease(n) { return n > 0 ? --n : 0; } // 보조함수를 전달하여 호출 console.log(Counter(increase)); // 1 console.log(Counter(increase)); // 2 // 자유 변수를 공유한다. console.log(Counter(decrease)); // 1 console.log(Counter(decrease)); // 0
댓글을 불러오는 중입니다.
실행 컨텍스트

자바스크립트는 어떻게 실행되는가

캡슐화

캡슐화하면 어떤데??

You can look back but don't regret it | All Right Reserved by Giyoun