클로저

이보아·2024년 6월 19일
0

모던_자바스크립트

목록 보기
2/18
post-thumbnail

클로저

함수형 프로그래밍 (하스켈, 리스프, 얼랭, 스칼라 등)에서 사용되는 중요한 특성.
자바스크립트만의 고유한 특성이 아니다.

렉스컬 스코프

자바스크립트 엔진은 함수가 정의된 위치에 따라 상위 스코프를 결정한다. (호출했는지가 아님)

const x = 1;

function foo() {
  const x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?
  • 함수의 상위 스코프를 결정한다 -> 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정한다.
  • 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장할 참조값 (상위 스코프 참조)는 함수 정의가 평가되는 시점에 함수가 정의된 위치에 의해 결정된다.

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

함수 자신의 내부슬롯 [[Environment]]에 자신의 정의 된 환경, 즉 상위 스코프의 참조를 저장한다.

  • 함수 객체의 내부슬롯에 저장된 현재 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 상위 스코프이다.
  • 상위 스코프를 자신이 존재하는 한 기억한다.

클로저와 렉시컬 환경

외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기 종료한 외부 함수의 변수를 참조 할 수 있다. 이러한 중첨 함수를 클로저 라고 부른다.

const x = 1;

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

// outer 함수를 호출하면 중첩 함수 inner를 반환한다.
// 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer(); // ③
innerFunc(); // ④ 10
  • 클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에만 한정하는 것이 일반적이다.

📄 자유 변수 (free variable)란?

클로저에 의해 참조되는 상위 스코프의 변수.
클로저란 “함수가 자유 변수에 대해 닫혀있다.” 라고 표현이 가능하다.
더 쉽게 말하면 “자유 변수에 묶여있는 함수” 라고 할 수 있다.

클로저의 활용

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

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
  • increase, decrease 함수는 모두 클로저이다.
  • 자신의 함수 정의가 평가되어 함수 객체가 될 때 실행 중인 실행 컨텍스트 (즉시 실행 함수)의 렉시컬 환경을 기억한다.
  • 즉, num 변수는 increase, decrease 메서드만 변경할 수 있다.

🚩 클로저는 상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경,유지 하기위해 사용한다.

캡슐화와 정보 은닉

캡슐화 (Encapsulation)는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작 메서드를

하나로 묶는것 캡슐화는 특정 프로퍼티, 메서드를 감출 목적으로 사용하는데 이를 정보 은닉이라 한다.

  • 정보 은닉을과 객체간의 결합도(의존성)를 낮추기 위해 사용한다. 자바스크립트는 public, privated, protected와 같은 접근 제한자를 제공하지 않는다.
    따라서 자바스크립트의 모든 객체는 기본적으로 public하다.
function Person(name, age) {
  this.name = name; // public
  let _age = age;   // private

  // 인스턴스 메서드
  this.sayHi = function () {
    console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
  };
}

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
  • Person.prototype.sayHi 메서드는 즉시 실행 함수의 지역 변수 _age를 참조할 수 있는 클로저이므로, 중복 생성 없이 private 변수에 접근이 가능하다.
    하지만 여러 개의 인스턴스를 생성할 경우 변수의 상태가 유지되지 않는다. 해당 메서드는 단 한번씩만 생성되는 클로저이기 때문이다.

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
  • 정보 은닉을 완벽하게 지원하지 않는다.

자주 발생하는 실수

// 1. 클로저가 아닌 코드 
var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = function () { return i; }; // ①
}

for (var j = 0; j < funcs.length; j++) {
  console.log(funcs[j]()); // ②
}

// 2. 클로저로 수정
const funcs = [];

for (let i = 0; i < 3; i++) {
  funcs[i] = function () { return i; };
}

for (let i = 0; i < funcs.length; i++) {
  console.log(funcs[i]()); // 0 1 2
}
  • 즉시 실행 함수가 반환한 중첩 함수는 자신의 상위 스코프를 기억하는 클로저이다.

    매개변수 id는 즉시 실행 함수가 반환한 중첩 함수에 묶여있는 자유 변수가 되어 그 값이 유지된다.
profile
매일매일 틀깨기

0개의 댓글