[javascript] Closure

mook9288·2021년 1월 15일
0

javascript 기초 정리

목록 보기
7/9

Closure - 클로저

A closure is the combination of a function and the lexical environment within which that function was declared.
클로저는 함수와 함수가 선언되던 당시의 정보를 담은 환경(어휘적 환경)의 조합이다.
MDN - closure 발췌

lexical environment ? 선언 당시의 환경에 대한 정보를 담는 객체. 구성 환경.

클로저는 실행되는 장소나 시점은 관계가 없이 함수가 선언되는 주변 환경과 관련하여 생성되는 개념이다.
클로저가 형성되어 기억하게 되는 환경 정보는 함수가 선언될 당시에만 순간적으로 포착하고 기억되는 것이 아니라 지속적으로 그 변화를 추적하게 된다.
클로저는 어떤 데이터(어휘적 환경)과 그 데이터를 조작하는 함수를 연관시켜주기 때문에 유용하다.

클로저는 함수 선언시 생성되는 스코프(유효범위)와 밀접한 관계가 있다. 클로저를 이해하기 전에 어떻게 변수의 스코프를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

내부함수가 외부함수의 실행 컨텍스트에 접근 가능하다.
외부함수에서 선언한 변수(지역변수)를 내부함수가 접근할 수 있고, 내부함수를 외부함수의 외부로 전달할 경우, 외부함수가 종료된 이후에도 변수가 사라지지 않는 현상이다. 즉, 클로저는 지역변수가 함수 종료 후에도 사라지지 않게 할 수 있고(사용자가 선택 가능) 함수 내부에서 생성한 데이터와 그 유효범위로 인해 발생하는 특수한 현상/상태라고 할 수 있다.

클로저를 잘 활용하면 누릴 수 있는 이점

  • 접근 권한 제어
  • 지역변수 보호
  • 데이터 보존 및 활용

예제 1

접근 권한 제어, 지역변수 보호
function outer() {
  var outerTit = '외부 함수';

  function inner() {
    var innerTit = '내부 함수';

    console.log(outerTit + ' ' + innerTit);
  }

  inner();
}

outer();
console.log(outerTit);

outerTitouter함수 외부에서 접근 할 수 없지만, inner함수 내부에서는 접근할 수 있다.

👉 내부함수(inner)에서 외부함수(outer())의 지역변수(outerTit)에 접근할 수 있다.
외부함수(outer())의 외부에서는 지역변수(outerTit)에 접근할 수 없다.

객체지향프로그래밍은 외부와의 데이터 연동이 매우 활발히 이루어져야 한다. 함수 내부에서만 활용하면 한계가 있다.

위 예제를 외부에서도 활용 가능할 수 있도록 아래처럼 바꿔보자.

데이터 보존 및 활용
function outer() {
  var outerTit = '외부 함수';

  return function inner() {
    var innerTit = '내부 함수';

    console.log(outerTit + ' ' + innerTit);
  }
}

var func = outer();

console.log(func());

outer()에서 존재하는 inner()return 해주고, 외부에서 outer()func라는 변수에 담았다.
변수 func를 이용하면 외부에서 outerTit의 값을 출력 수 있게 된다.

외부에서 outer()의 지역변수 outerTit의 값을 얻을 수 있으나, 외부에서 임의로 outerTit를 변경할 수 없다.

👉 외부함수outer()에서 내부함수inner()를 반환(return)하고 외부에서 변수(func)를 만들고 외부함수outer()를 할당하면, 외부에서 외부함수outer()의 지역변수(outerTit) 값을 얻을 수 있다.

외부에서 임의로 outerTit의 값을 변경하려면, outerTit의 값을 외부에서 변경할 수 있도록 outer() 내에서 수단을 제공해줘야한다. 즉, 변경 권한을 줘야한다.

위 예제를 다시 한 번 아래처럼 바꿔보자.

접근 권한 제어, 지역변수 보호, 데이터 보존 및 활용
function outer () {
  var _outerTit = '외부 함수';

  return {
    get inner() { return _outerTitl },
    set inner(innerTit) { _outerTit = innerTit; }
  }
}

var func = outer();

func.inner = 10;

객체에 getter와 setter을 담아 return 하면, 권한을 부여 받았기 때문에 외부에서 _outerTit을 변경할 수 있다.
내부에서 반환을 통해 권한을 주면, 외부에서 부여받은 권한을 이용해서 내부와 소통할 수 잇다.

지역변수 보호도 할 수 있다. 외부에 노출되지 않기 떄문에 getter와 setter의 설정을 어떻게 하냐에 따라 영향을 줄 수도 안 줄 수도 있다. 즉, 무엇을 반환할지는 outer()에 달려있다.

예제 2

function setName(name) {
  return function() {
    return name;
  }
}

var sayMyName = setName("mook");

sayMyName(); // "mook"

전역 실행 컨텍스트가 생성되면서,

  1. sayName 실행 컨텍스트 생성)
    • setName 함수와 변수가 선언되고 setName("mook")을 호출한다.
  2. sayName 실행 컨텍스트 종료
    • 지역변수 name을 선언한 후 "mook"을 할당하고 익명함수를 선언하여 반환한다.
  3. sayMyName 실행 컨텍스트 생성
    • 반환된 내용(익명함수)을 변수 sayMyName에 할당하고 sayMyName을 호출한다.
  4. sayMyName 실행 컨텍스트 종료
    • 반환된 익명함수 스코프에서 name을 탐색하고 없으면 상위로 올라가 sayName에서 name을 탐색한 후 "mook"을 반환한다.
  5. 전역 실행 컨텍스트가 종료한다.
예제 3
function setCounter() {
  var count = 0;
  return function() {
    return ++count;
  }
}

var count = setCounter();

count(); // "mook"
  1. setCounter 정의 후 실행한다.
  2. setCounter 스코프에 count 변수 선언 및 0 할당한다.
  3. 익명함수를 정의하여 반환한다.
  4. 반환된 익명함수를 외부의 변수 count에 할당하고 count 실행한다.
  5. 익명함수 스코프에서 count을 탐색 후 없으면 상위의 setCounter 스코프에서 count을 탐색한다.
  6. count에 1을 증가시킨 값을 반환한다.

closure로 Private Member 만들기

Private Variable
자바스크립트는 비공개 선언을 제공하지 않기 때문에 클로저를 활용해 흉내낼 수 있다.

  • 외부로부터의 접근을 제한한다.
  • 전역스코프의 변수를 최소화한다.

소프트웨어가 커지는 과정에서 어떠한 정보가 있을 때, 아무나 수정을 할 수 없게 방지한다.

자동차 게임 구현하기

TODO
1. 차량별로 연료량 및 연비는 랜덤
2. 유저별로 차량 하나씩 고르면 게임 시작
3. 각 유저는 자신의 턴에 주사위를 굴려 랜덤하게 나온 숫자만큼 이동
4. 만약 연료가 부족하면 이동 불가
5. 가장 멀리 간 사람이 승리

자동차 게임 구현
var car = {
  fuel: 10, // 연료 (l)
  power: 2, // 연비 (km / l)
  total: 0,
  run: function(km) {
    var wasteFuel = km / this.power;
    
    if(this.fuel < wasteFuel) {
      console.log("이동 불가");
      return;
    }

    this.fuel -= wasteFuel;
    this.total += km;
  }
}

이렇게 사용하게 되면 외부에서 프로퍼티 값을 마음대로 변경할 수 있다.

직접 변경할 수 없도록 하기 위해 스코프를 활용하여 위 코드를 변경할 수 있다.

외부에서 프로퍼티 변경하지 못하게 구현
var createCar = function(f, p) {
  var fuel = f;
  var power = p;
  var total = 0;

  return {
    run: function(km) {
      var wasteFuel = km / this.power;
      
      if(this.fuel < wasteFuel) {
        console.log("이동 불가");
        return;
      }

      fuel -= wasteFuel;
      total += km;
    }
  }
}
var car = createCar(10, 2);

이렇게 변경이 되면 사용자가 아무리 프로퍼티를 변경해도 영향이 미치지 않는다.
사용자는 오직 run 이라는 메소드만 사용할 수 있다.

run: function(km) { ... }set으로 변경하여 작성한다면, 권한은 보다 더 직접적일 수도 있다.
set fuel(f) { fuel = f }, ... 이런 식으로 변경하게 되면
외부에서 직접 지역변수 car.fuel의 값을 변경할 수 있다.
이처럼 return할 객체에 어떤 내용을 담을 것인지에 따라 달라진다.

closure를 활용해서 Private Member와 Public Member 구분하는 방법

  1. 함수에서 지역변수 및 내부함수 등을 생성한다
  2. 외부에 노출시키고자 하는 멤버들오 구성된 객체를 반환(return)한다.
    • return한 객체에 포한되지 않은 멤버들은 private하다.
    • return한 객체에 포함된 멤버들은 public하다.

함수 내부에서 다시 함수를 반환하면 반환된 함수는 최초 선언될 때 담고 있는 정보를 유지한다. 외부에 노출하고자 하는 데이터들만 반환하면 반환하지 않은 데이터들은 모두 외부에서 접근을 제한할 수 있다. 따라서 지역변수를 안전하게 보호할 수 있고, 외부에 공개되는 지역변수에 변경 권한을 부여하여 데이터를 활용할 수 있다.

⭐️ 클로저는 객체지향 프로그래밍과 함수형 프로그래밍의 currying을 위해 반드시 이해하고 알아야한다.


🔎 참고자료 🔎
- MDN
- 생활코딩
- PoiemaWeb
- javascript info
- 코어 자바스크립트
- 러닝 자바스크립트
- 인사이드 자바스크립트

0개의 댓글