클로저

솜사탕·2023년 5월 23일
1

JavaScript

목록 보기
20/23

클로저

클로저는 함수가 속한 렉시컬 스코프를 기억하여
함수가 렉시컬 스코프 밖에서 실행될 때도
그 스코프에 접근할 수 있는 함수를 말한다.

즉 클로저는 함수의 그 함수가 선언된 렉시컬 환경과의 조합이다.

const x = 1;

function outerFunc() {
  const x = 10
  
  function innerFunc() {
    console.log(x); // 10
  }
  
  innterFunc();
}
outerFunc();

렉시컬 스코프

함수를 어디에 정의했는지에 따라 상위 스코프를 결정하는 스코프를
렉시컬 스코프라 한다.
다른말로 정적 스코프 라고도 불린다.

함수 객체의 내부 슬롯 [[Environment]]

함수가 정의된 위치와 호출되는 위치는 다를 수 있고,
렉시컬 스코프가 가능하려면 호출되는 위치 및 환경과 상관 없이
자신이 정의된 한경을 기억해야 한다.
이를 위해 함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경 즉 상위 스코프의 참조를 저장한다.

const x = 1;

function foo() {
  const x = 10;

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

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

foo();
bar();

아래는 일련의 과정을 나타내었다.

  • 전역으로 foo / bar 함수 정의
  • 전역 코드가 평가되는 시점에 함수 객체 생성하고 윈도우의 메서드가 됨
  • 생성된 함수 객체의 내부 슬롯 [[Environment]]에는 실행 컨텍스트이 렉시컬 환경인 전역 레시컬 환경의 참조가 저장된다.

클로저와 렉시컬 환경

const x = 1;

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

const innerFunc = outer();
unnerFunc(); // 10

아래는 일련의 과정을 나타냈다.

  • outer 함수를 호출하면 중첩 함수인 innter 함수를 반환하고 생명 주기를 마감
    (즉 함수 실행이 마무리되면 실행 컨텍스트 스택에서 제거됨, 스택에선 제거되지만 렉시컬 환경까지 소멸하는 것은 아니다.)
  • outer 함수의 지역 변수 x는 실행 컨텍스트가 제거 되었으므로 변수또한 생명 주기를 마감한다.
  • 그러나 결과 값은 10으로 나왔다.

위 일련의 과정과 결과값을 보았듯 생명 주기가 마감이 되었음에도 불구하고 외부 함수의 변수를 참조할 수 있는데
이러한 중첩 함수를 클로저 라고 부른다.

클로저의 활용

클로저는 자바스크립트의 강력한 기능으로, 필요하다면 적극적으로 활용해야 한다.

클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용된다.
즉 의도치 않게 변경되지 않도록 상태를 안전하게 은닉 하고
특정 함수에게만 상태 변경을 허용하기 위해 사용된다.

let num = 0;

const increase = function () {
  // 카운트 상태를 1만큼 증가 시킨다.
  return ++num;
};

console.log(increase()); // 1
console.log(increase()); // 2

위 코드의 기능은 정상작동 하지만 에기치 않은 오류를 발생 시킬수가 있다.

  • 전역변수 num은 increase 함수가 아닌 타 함수로 호출 및 코드로 인하여 변경될 상황을 야기할수 있다.
  • 전역변수 num은 increase 함수만이 변경할수 있어야 한다.

아래는 클로저를 사용했을 경우 문제점을 해결할 수 있다.

// 카운트 상태 변경 함수
const increase = (function () {
  // 카운트 상태 변수
  let num = 0;
  
  // 클로저
  return function () {
    return ++num
  };
}());

console.log(increase()); // 1
console.log(increase()); // 2
  • 코드 실행시 즉시 실행 함수 호출 후 반환함 함수를 increase 변수에 할당
  • 즉시 실행 함수가 반한환 클로저는 즉시 실행 함수의 렉시컬 환경을 기억
  • 자유 변수 num을 언제 어디서 호출 및 참조가 가능
    -자유 변수 num은 외부에서 접근이 불가하고 은닉된 비공개 변수다.

캡슐화와 정보 은닉

캡슐화는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것을 말한다.
캡슐화는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데
이를 정보 은닉 이라고 한다.

const Person = (function() {
  let _age = 0; // private
  
  // 생성자 함수
  function Person(name, age) {
    this.name = name; // public
    _age = age
  }
  
  // 프로토타입 메서드
  
  Person.prototype.sayHi = function () {
    console.log(this.name, _age);
  };
  
  // 생성자 함수를 반환
  return Person
}());

const me new Person('Lee', 20);
mey.sayHi(); // lee 20
console.log(me.name); // lee
console.log(me._age); // undefined

위 패턴을 사용하면 접근 제한자를 제공하지 않는 자바스크립트 에서도 정보 은닉이 가능한 것처럼 보인다.
하지만 해당 코드도 문제가 있는데, person 생성자 함수가 여러 개의 인스턴스 생성할 경우 _age 변수의 상태가 유지되지 않는다는 점이다.

이는 Person.prototype.sayHi 메서드가 단 한번 생성되는 클로저 이기 때문에 발생하는 현상인데,
자신의 상위 스코프인 즉시 실행 함수의 실행 컨텍스트의 렉시컬 환경의 참조를
[[Environment]]에 저장하여 기억한다.
따라서 Person 생성자 함수의 모든 인스턴스가 상속을 통해 호출할 수 있는 PErson.prototype.sayHi 메서드의 상위 스코프는 어떤 인스턴스로 호출하더라도 동일한 상위 스코프를 사용하게 된다.
이러한 이유로 인해 Person 생성자 함수가 여러 개의 인스턴스를 생성할 경우
_age 변수 상태가 유지되지 않는다.

그래서 자바스크립트는 정보 은닉을 완전하게 지원하지 않지만
클래스에 private 필드를 정의할수 있는 표준 사양이 제안되어있다
이내용은 추후 다룰 예정이다

profile
공부공부공부공부공부공부

0개의 댓글