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 등의 메서드 사용
'Front-end > Javascript' 카테고리의 다른 글
[코어 자바스크립트] 06. 프로토타입(1) (0) | 2022.12.26 |
---|---|
[코어 자바스크립트] 05. 클로저(3) (0) | 2022.12.21 |
[코어 자바스크립트] 05. 클로저(1) (0) | 2022.12.20 |
[코어 자바스크립트] 04. 콜백 함수(2) (0) | 2022.12.14 |
[코어 자바스크립트] 04. 콜백 함수(1) (0) | 2022.12.12 |