[자바스크립트 문법] 클로저

김형빈·2024년 4월 30일
0

클로저

  • 함수와 그 함수가 선언된 렉시컬 환경과의 조합 (MDN)

렉시컬 스코프(Lexical Scope)

  • JS엔진은 함수를 어디서 '호출했는지'가 아닌 함수를 어디에 '정의했는지'에 따라 상위 스코프 결정
//예시 1
const x = 1;

function foo() {
  const x = 10;
  // scope chain에 의해 바깥쪽 scope인 foo를 찾는다
  // bar의 LexicalEvironment outer에 foo를 기억한다
  // 따라서 10에 먼저 접근
  function bar() {
  	console.log(x);
  }
}

foo();
//예시2
const x = 1;

function foo() {
  const x = 10;

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

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

foo();
bar();

클로저와 렉시컬 환경

  • 클로저
    • 외부 함수보다 중첩 함수가 더 오래 유지되는 경우
    • 중첩 함수가 이미 생명주기가 종료한 외부 함수의 변수를 여전히 참조할 수 있다
const x = 1;

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

const innerFunc = outer();
innerFunc();
  • 동작 과정
    • outer 함수를 호출하면 중첩 함수 inner를 반환(return)한다
    • 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스탭에서 팝되어 제거된다(역할을 다 했으니깐)
    • inner 함수는 런타임에 평가된다
    • inner 함수가 innerFunc에 전달되었는데, 이는 outer 함수 의 렉시컬환경을 (여전히) 참조하고 있다
    • 즉, outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만 outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다
    • 안 쓰는 데이터만 가져가는 가비지 컬렉터 덕분!

클로저의 활용

  • 상태(state)가 의도치 않게 변경되지 않도록 안전하게 은닉한다
  • 특정 함수에게만 상태 변경을 허용하여 안전하게 변경하고 유지한다

예제1

// 카운트 상태 변경 함수 #1
let num = 0;

const increase = function () {
    return ++num;
};

console.log(increase());
// num = 100; // 치명적인 단점이 있다
console.log(increase());
console.log(increase());
  • 카운트 상태(num 변수의 값)는 increase 함수만이 변경할 수 있어야 한다
  • 전역변수인 num이 문제이다

예제2

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

// 이전 상태값을 유지 못함
console.log(increase()); //1
console.log(increase()); //1
console.log(increase()); //1
  • 의도치 않은 변경은 방지했다
  • 그러나 increase()가 호출될 때마다 num이 초기화된다

예제3

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

// 이전 상태값을 유지
console.log(increase()); //1
console.log(increase()); //2
console.log(increase()); //3
  • 의도한 대로 동작한다

예제4 (decrease 추가)

// 카운트 상태 변경 함수 #4
// 클로저 카운트 기능 확장(값 감소 기능 추가)
const counter = (function () {
  //카운트 상태 변수
  let num = 0;

  // 클로저인 메서드(increase, decrease)를 갖는 객체를 반환한다
  // property는 public -> 은닉되지 않는다
  return {
    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

예제5

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

  // 인스턴스 메서드
  // 따라서, Person 객체가 생성될 때 마다 중복 생성됨
  // : 해결방법 -> prototype
  this.sayHi = function () {
    console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
  };
}

const me = new Person("Choi", 33);
me.sayHi(); // Hi!, My name is Choi. I am 33.
console.log(me.name); // Choi
console.log(me._age); // undefined

const you = new Person("Lee", 30);
you.sayHi(); // Hi! My name is Lee. I am 30.
console.log(you.name); // Lee
console.log(you.age); // undefined
  • 캡슐화
  • 프로퍼티메서드를 하나로 묶는 것
  • 목적
    • 객체의 상태 변경을 방지함으로써 정보 보호
    • 객체 간의 의존성(결합도 - coupling)을 낮춤
profile
The secret of getting ahead is getting started.

0개의 댓글