무브라더

[JavaScript] 클로저 정리 본문

Programming/JavaScript

[JavaScript] 클로저 정리

동스다
반응형
SMALL

※ 본 게시글은 모던자바스크립트 deep dive를 보고 정리한 내용입니다. 틀린 내용이 있다면 댓글로 적어주시면 감사드리겠습니다 (__)

 

 

클로저에 대해 공부하고 클로저에 대해 간단히 설명을 해보자면 

 

  •  둘러싸여진 상태의 참조와 함께 다발로 묶여진 함수의 콤비네이션
  •  내부함수로부터 외부함수의 접근 권한을 줌
  •  함수 생성 시점에 언제나 생김

작성한 글을 쭉 읽어보면 무슨 말을 하고 싶은 건지 대강 이해가 될것이다.

 

 

렉시컬 스코프

함수를 어디에 정의했는지에 따라 상위 스코프를 결정 -> 렉시컬(정적) 스코프

 

렉시컬 환경의 '외부 렉시컬 환경에 대한 참조' 에 저장할 참고값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경에 의해 결정된다. 이것이 바로 렉시컬 스코프이다.

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

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

 

클로저와 렉시컬 환경

const x = 1;

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

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

outer함수를 호출하면 inner를 반환하고 실행 컨텍스트에서 제거되는데 innerFunc() 호출 시, 결과로 10이 나오게 된다. 이처럼 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다.
outer함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만 outer함수의 렉시컬 환경 까지 소멸하는 것은 아니다.

 

클로저의 활용

클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다. 즉 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.

const increase = (function () {
  let num = 0;
  
  // 클로저
  return function () {
    return ++num;
  };
}());

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

이 예제에서 즉시 실행 함수가 반환한 클로저는 increase 변수에 할당되어 호출되어서 즉시 실행 함수의 렉시컬 환경을 상위 스코프로 가지고 있기 때문에 num에 접근하여 참조하고 변경할 수 있다.

 

const Counter = (function () {
  let num = 0;
  
  function Counter() {
  }
  
  Counter.prototype.increase = function() {
    return ++num;
  };
  
  Counter.prototype.decrease = function() {
    return num > 0 ? --num : 0;
  };
  
  return Counter;
}());

const counter = new Counter();

인스턴스의 프로퍼티가 아닌 즉시 실행 함수 내부에 num을 정의해두고 프로토 타입을 통해 increase, decrease 메서드를 상속받는 인스턴스를 생성한다. 이 메서드는 즉시 실행 함수 실행 컨텍스트의 렉시컬 환경을 기억하는 클로저이어서 자유 변수 num을 참조할 수 있다.

 

함수형 프로그래밍에서 클로저를 활용하기 위해서는 일반 함수로 만들어서 함수를 전달받고 함수를 반환하는 상위 함수로 만들게 되면 보조 함수에 따라 여러번 호출할 수 있어서 함수를 반환할 때 반환된 함수는 자신만의 독립된 렉시컬 환경을 갖는다. --> 카운터 증감이 연동이 되지 않을 수 있다.

 

따라서 독립된 카운터가 아닌 연동하여 증감이 가능한 카운터를 만들려면 렉시컬 환경을 공유하는 클로저를 만들어야 한다. 다음 코드를 살펴보자

 

const counter = (function() {
  let counter = 0;
  
  // 함수를 인수로 전달받는 클로저 반환
  return function (predicate) {
    // 인수로 전달받은 보조 함수에 상태 변경 위임
    counter = predicate(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

* predicate 에 관한 내용은  ==> https://livelikeabel.tistory.com/86

 

[Functional JavaScript] 함수형 자바스크립트 소개

@markdown > *이 글은 유인동님의 함수형 자바스크립트 책의 내용을 정리한 내용입니다 :)* > https://github.com/ToBeFrontEndMaster/AbelKo/blob/master/chapter1.md # Introduce Funcitonal programming ## 함..

livelikeabel.tistory.com

캡슐화와 정보 은닉

 

캡슐화는 객체지향 특성 중 하나이다.

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

 

생성자 함수안에 지역변수로 선언한 변수는 외부에서 참조하거나 변경할 수 없는데 이 변수를 참조하는 메서드를 프로토타입으로 만든 경우 보통 방법으론 이 변수에 접근을 할 수 없다. --> 즉시 실행함수를 사용하여 생성자 함수와 prototype에 정의할 메서드를 한곳에 모아서 선언.
하지만 이 방법도 생성자 함수가 여러 개의 인스턴스를 생성할 경우 그 변수의 상태가 유지 되지 않는다.

(프로토타입 메소드가 단 한번 생성되는 클로저 이기 때문에 하나의 동일한 상위 스코프를 사용한다.)

 

클로저를 사용하면서 사용하는 실수

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]());
}

결과가 어떻게 나올까? 나도 처음에 0 , 1 , 2 가 출력이 될꺼라고 생각했지만 3이 3번 출력된다.

 

왜냐하면 for문 안에 var 키워드로 선언한 i 변수가 함수레벨 스코프이기 때문에 전역변수 취급을 하기 때문이다.

 

그렇다면 0, 1, 2 를 출력하기 위해 클로저를 활용한 코드는 어떻게 작성하면 될까?

var funcs = [];

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

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

i를 인수로 받아 매개변수 id에 할당한 후 중첩 함수를 반환하고 종료된다. 매개 변수 id는 즉시 실행 함수가 반환한 중첩 함수의 상위 스코프에 존재하기 때문에 자유 변수가 되어 그 값이 유지된다.

 

 

 

var funcs = [];

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

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

for문 안 var 키워드를 let 키워드로 바꿔줘도 된다.

반응형
LIST

'Programming > JavaScript' 카테고리의 다른 글

[JavaScript] this 바인딩  (0) 2022.03.29
[JavaScript] Array.from  (0) 2022.03.25
[JavaScript] '?' 옵셔널 체이닝 연산자  (0) 2022.03.24
[JavaScript] prototype 정리  (0) 2022.03.24
[JavaScript] 가비지콜렉션, WeakRef  (0) 2022.03.21
Comments