Languages/JavaScript

[HUFS/GnuVil] #12 클로저

성중 2022. 10. 18. 16:03

클로저

클로저(Closures)둘러싸여진 상태의 참조와 함께 다발로 묶여진 함수의 콤비네이션으로, 함수가 생성되는 시점에 내부 함수로부터 외부 함수에의 접근 권한을 준다

 

const x = 1;

function outerFunc() {
  const x = 10;

  function innerFunc() {
    console.log(x); // 10
  } // innerFunc와 outerFunc 사이의 closure (oER)

  innerFunc();
} // outerFunc와 전역컨텍스트 사이의 closure (oER)

outerFunc();

함수 내부의 중첩 함수에서 상위 스코프의 변수에 접근할 수 있다!

* 만약 innerFunc가 outerFunc 외부에서 정의되었다면 outerFunc의 변수에 접근 불가능

 

const x = 1;

function foo() {
  const x = 10;

  // 상위 스코프는 함수 정의 환경(위치)에 따라 결정된다.
  // 함수 호출 위치와 상위 스코프는 아무런 관계가 없다.
  bar();
}

// 함수 bar는 자신의 상위 스코프, 즉 전역 렉시컬 환경을 [[Environment]]에 저장하여 기억한다.
function bar() {
  console.log(x);
}

foo(); // 1
bar(); // 1

자바스크립트는 정의한 시점에 스코프를 결정하는 렉시컬 스코프를 따르기 때문에 위 경우는 호출이 아닌 정의된 시점의 Outer Environment Reference인 전역 컨텍스트의 변수를 참조한다

 

const x = 1;

// ①
function outer() {
  const x = 10;
  const inner = function () { console.log(x); }; // ②
  return inner;
}

// outer 함수를 호출하면 중첩 함수 inner를 반환한다.
// 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer(); // ③
innerFunc(); // ④ 10

실행 컨텍스트에서 제거된 outer의 컨텍스트로 묶인 변수를 inner가 여전히 참조한다

* 클로저의 핵심 개념 = 이미 생명 주기가 종료한 외부 함수의 변수를 참조하는 중첩 함수

 

중첩 함수가 외부 함수보다 더 오래 유지되고 상위 스코프의 식별자를 참조

이론상 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 클로저이지만, 중첩 함수가 상위 스코프의 식별자를 참조하며 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적이다

 

// 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 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;
}

// 보조 함수를 전달하여 호출
console.log(counter(increase)); // 1
console.log(counter(increase)); // 2

// 자유 변수를 공유한다.
console.log(counter(decrease)); // 1
console.log(counter(decrease)); // 0

클로저는 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다. 위와 같이 외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저를 적극 사용한다

 

const Person = (function () {
  let _age = 0; // private

  // 생성자 함수
  function Person(name, age) {
    this.name = name; // public
    _age = age;
  }

  // 프로토타입 메서드
  Person.prototype.sayHi = function () {
    console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
  };

  // 생성자 함수를 반환
  return Person;
}());

const me = new Person('Lee', 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
console.log(me.name); // Lee
console.log(me._age); // undefined

const you = new Person('Kim', 30);
you.sayHi(); // Hi! My name is Kim. I am 30.
console.log(you.name); // Kim
console.log(you._age); // undefined

캡슐화(encapsulation)프로퍼티와 메서드를 하나로 묶는 것을 뜻하며 특정 프로퍼티나 메서드를 감추는 것이 목적이라면 정보 은닉이라 한다. 위와 같이 클로저 개념을 활용해 흉내 낼 수는 있지만 완전하지 않아 ES 최신 스펙이나 TypeScript를 활용하도록 하자

 

 내용은 위키북스의 '모던 자바스크립트 Deep Dive' 바탕으로 작성되었습니다.