3. 클로저 활용 사례

 1) 콜백 함수 내부에서 외부 데이터 사용

  -예시 1)

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

fruits.forEach(function (fruit) {
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', function () {
    alert('your choice is' + fruit);
  });
  $ul.appendChild($li);
});
document.body.appendChild($ul);

  -ul 생성, ul 내부에 fruits 목록에 있는 과일을 하나씩 li로 추가

   →li에 fruits 목록의 값을 하나씩 추가하는 forEach 함수는 외부 변수를 사용하지 않으므로 클로저가 없음

  -li를 클릭하면 "'your choice is' + fruit" 출력, 이때, fruit는 addEventListener의 콜백 함수 function 내부에서 외부의 변수를 참조하여 사용

  →따라서, addEventListener의 콜백 함수에는 클로저가 있음

 

  -예시 2)

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

var alertFruit = function(fruit) {
      alert('your choice is' + fruit);
};
fruits.forEach(function(fruit) {
    var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', alertFruit);
    $ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);

  -공통적으로 사용되는 함수인 alertFruit를 콜백 함수 외부로 꺼냄

  -마지막 코드인 alertFruit(fruits[1])에서 fruits[1]은 정상적으로 'banana'를 불러와 수행

  -하지만 클릭 시 수행되는 alertFruit는 대상 과일명이 아닌 [object PointerEvent]가 출력됨

   →콜백 함수의 인자에 대한 제어권이 addEventListener한테 있는 상태

   →addEventListener는 콜백 함수 호출 시 첫번째 인자에 이벤트 객체를 넣기 때문에 alertFruit에 이벤트 객체 [Object PointerEvent]가 들어감

   →bind 메서드로 해결 가능

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

var alertFruit = function(fruit) {
      alert('your choice is' + fruit);
};
fruits.forEach(function(fruit) {
    var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', alertFruit.bind(null,fruit));
    $ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);

   →bind 메서드를 사용하면 첫번째 인자는 바인딩할 this인데 이 값은 생략할 수 없으므로 null로 정의해야하고, 이 때문에 원래의 this를 유지할 수 없는 경우가 많음

   →두번째 인자에는 이벤트 객체가 넘어옴

...

var alertFruit = function(fruit, s) {
      alert('your choice is' + fruit + ' ' + s);
};

...

   →다음과 같이 임의의 변수 s를 지정해주면 bind의 인자로 null로 정의한 this와 fruit 외에 암시적으로 이벤트 객체를 넘겨줬음을 알 수 있음

   →해결을 위해 고차함수를 활용하면 됨

...

var alertFruitBuilder = function (fruit) {
  return function () {
    alert('your choice is ' + fruit);
  };
};
fruits.forEach(function (fruit) {
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', alertFruitBuilder(fruit));
  $ul.appendChild($li);
});

...

   →alertFruitBuilder로 정의한 함수에서 익명 함수를 반환하는데 이 익명함수가 기존의 alertFruit 함수임

   →addEventListener의 콜백 함수로 alertFruitBuilder를 지정하면 alertFruitBuilder는 fruit값을 받아 alert를 띄우는 함수를 반환함

   →결국, fruit를 받아 alert를 띄우는 함수가 addEventListener의 콜백 함수가 되고, 클릭이 발생했을 때, alert('your choice is ' + fruit)가 바로 fruit를 참조함

   →alertFruitBuilder가 반환하는 함수에는 클로저가 존재함

 

 

 2) 접근 권한 제어(정보 은닉)

  -정보 은닉: 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화

   →모듈 간의 결합도 낮추고 유연성 높임

  -접근 권한의 종류

   →public: 외부에서 접근이 가능

   →private: 내부에서만 사용하며 외부에 노출되지 않는 것

   →protected: private이지만 동일 패키지의 클래스나 하위 클래스 관계에서 접근 가능, 거의 사용 안함

  -함수 내에서 return을 사용하여 선택적으로 일부 변수에만 접근 권한을 부여할 수 있음

var outer = function () {
  var a = 1;
  var inner = function () {
    return ++a;
  };
  return inner;
};
var outer2 = outer();
console.log(outer2());
console.log(outer2());

   →outer라는 변수를 통해 outer 함수를 실행할 수는 있지만, outer 함수 내부에는 개입 불가

   →외부에서는 오직 outer함수가 return한 정보(inner)에만 접근 가능

   →return한 변수들은 공개 멤버, return하지 않은 정보는 비공개 멤버

 

  -예시(자동차 경주 게임)

   →규칙 1) 각 턴마다 주사위를 굴려 나온 숫자(km)만큼 이동

   →규칙 2) 차량별 연료(fuel), 연비(power)는 무작위

   →규칙 3) 남은 연료가 이동할 거리에 필요한 연료보다 부족하면 이동 못함

   →규칙 4) 모든 유저가 이동할 수 없는 턴에 게임 종료

   →규칙 5) 게임 종료 시점에 가장 멀리 이동해 있는 사람이 승리

// 규칙에 따라 생성한 자동차 객체

var car = {
  fuel: Math.ceil(Math.random() * 10 + 10),
  power: Math.ceil(Math.random() * 3 + 2),
  moved: 0,
  run: function () {
    var km = Math.ceil(Math.random() * 6);
    var wasteFuel = km / this.power;
    if (this.fuel < wasteFuel) {
      console.log('이동불가');
      return;
    }
    this.fuel -= wasteFuel;
    this.moved += km;
    console.log(km + 'km 이동 (총 ' + this.moved + 'km)');
  }
};

   →랜덤으로 설정되는 fuel, power와 이동거리인 moved에 접근이 가능하여 다음과 같이 사용자가 조작 가능

car.fuel = 10000;
car.power = 100;
car.moved = 1000;

   →문제 해결을 위해 자동차를 객체가 아닌 함수로 만들고, 필요한 멤버만 return하면 됨

// 규칙에 따라 생성한 자동차 함수

var createCar = function () {
  var fuel = Math.ceil(Math.random() * 10 + 10);
  var power = Math.ceil(Math.random() * 3 + 2);
  var moved = 0;
  return {
    get moved () {
      return moved;
    },
    run = function () {
      var km = Math.ceil(Math.random() * 6);
      var wasteFuel = km / power;
      if (fuel < wasteFuel) {
        console.log('이동불가');
        return;
      }
      fuel -= wasteFuel;
      moved += km;
      console.log(km + 'km 이동 (총 ' + moved + 'km). 남은 연료: ' + fuel);
    }
  };
};
var car = createCar();

   →fuel과 power는 비공개 멤버로 함수 외부에서 접근할 수 없고, moved는 getter만 부여하여 읽기 전용

   →외부에서는 moved를 읽는 것과 run 메서드를 실행하는 것만 가능

console.log(car.moved) // 지금까지 총 이동거리 출력
consoel.log(car.fuel) // undefined
consoel.log(car.power) // undefined

car.fuel = 1000;          // car.fuel에 1000을 대입할 수 있고
console.log(car.fuel);  // 출력하면 대입한 값인 1000이 나오지만
car.run();                  // run함수에까지 영향을 미치지는 못함, power와 moved도 마찬가지

   →여전히 run 메서드를 다른 내용으로 덮어씌우는 어뷰징은 가능한 상태이고 이것까지 막으려면 freeze 등의 메서드 사용

+ Recent posts