✨ 클로저의 활용 사례
1-1-1. 콜백함수 내부에서 외부 데이터를 사용하고자 할 때 _ 내부함수로 선언해서 외부변수 직접 참조(클로저O)
- B 콜백 함수는 fruit 이라는 외부 변수를 참조하므로 클로저 생성, B함수가 참조할 예정인 fruit는 A 함수가 종료되어도 GC(가비지컬렉터)의 수거대상에서 제외된다.
var fruits = ['apple','banana', 'peach'];
var $ul = document.createElement('ul');
fruits.forEach(function (fruit){ //A
var $li = document.createElement('li');
$li.innerText = fruit; //fruit을 매개변수로 받아서 사용하기 때문에 외부변수 사용이 아님
$li.addEventListener('click', function(){ //B
alert('your choice is' + fruit); //외부변수인 fruit를 사용하고 있음
});
$ul.appendChild($li);
});
document.body.appendChild($ul);
실행결과
1-1-2 콜백함수 내부에서 외부 데이터를 사용하고자 할 때 _ 공통함수로 빼기
var fruits = ['apple','banana', 'peach'];
var $ul = document.createElement('ul');
//콜백함수를 공통함수로 사용하기 위해 외부로 뺐다.
var alertFruit = function(fruit){
console.log(fruit) // MouseEvent {...}
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]);
실행결과
그러나 list를 클릭하면, [object MouseEvent] 가 출력이 된다. 콜백함수의 인자에 대한 제어권을 addEventListener가 가진 상태이며, addEventListener는 콜백 함수를 호출할 때 첫번째 인자에 "이벤트 객체"를 주입한다. (💔?!!ㅠ)
🎀 이벤트 객체 = 해당 타입의 이벤트에 대한 상세 정보를 저장하고 있다. 이벤트 객체는 이벤트 리스너가 호출될 때 인수로 전달된다.
1-2. 콜백함수 내부에서 외부 데이터를 사용하고자 할 때_ bind 메서드 사용(클로저x)
- this를 사용하지 않기 때문에 첫번째 인자에 null 설정, 두번째 인자인 fruit을 매개변수로 바인드된 함수 alertFruit에 넘겨줌
- 매개변수를 넘겨서 받았기 때문에, 클로저는 생성되지 않았다.
- 이벤트 객체(fruit)가 인자로 넘어오는 순서가 바뀌는 점 (애플 -> 바나나 -> 피치 -> 바나나) ➡ 바나나
및 함수 내부에서의 this가 원래 this와 달라진다. ( 전역 ➡ null? )
- 링크
✨ fruit가 새롭게 반환되는 함수(alertFruit)의 인자로 사용될 것이다. bind를 사용하면 새로운 함수를 반환하고, 새로운 함수는 첫번째 인자로 항상 fruit을 받으며 전역 컨텍스트를 사용한다( null이 컨텍스트로 사용되기 때문에)
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;
//bind 함수 사용
$li.addEventListener('click', alertFruit.bind(null, fruit));
$ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);
실행결과
1-3. 콜백함수 내부에서 외부 데이터를 사용하고자 할 때_ 콜백함수를 고차함수로 바꿔서 사용하는 방식(클로저O)
고차함수 (Higher-Order Function)
- 고차함수란 하나 이상의 함수를 인자로 취하거나 함수를 결과로 반환(return) 하는 함수이다. = 함수가 함수를 인자로 받는다
(=자바스크립트에서 함수는 일급객체이기 때문이다(함수를 값으로 사용할 수 있음)
- 고차 함수는 인자로 받은 함수를 필요한 시점에 호출하거나 클로저 생성하여 반환한다.
- 자바스크립트에서 제공하는 고차 함수의 예, map, filter, forEach, sort 등
🍄 고차함수의 예시
function calculate(a,b){
function addValue(){
return a + b;
}
return addValue; // 함수를 리턴!
}
let add = calculate(4,5);
console.log(add()); // 9
function calculate(val, func) { // 함수를 인자로 전달!
return func(5, val);
}
function addValue(a, b) {
return a + b;
}
console.log(calculate(7, addValue)); // 12
var fruits = ['apple','banana', 'peach'];
var $ul = document.createElement('ul');
//함수를 리턴하는 함수 = 고차함수
//익명함수를 리턴하고 있다
var alertFruitBuilder = function(fruit) {
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);
});
document.body.appendChild($ul);
2. 접근권한제어(정보은닉)
🌟 정보 은닉
어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념 중 하나 = 내부 변수와 메서드를 안전하게 관리할 수 있다.
자바스크립트는 기본적으로 변수 자체에 이러한 접근 권한을 직접 부여하도록 설계되어있지 않다. but 클로저를 이용하여 구분 가능하다.
🌟 접근권한 종류
- public
- private
- protected
🌟 private method : 코드에 제한적인 접근만을 허용, 전역 namespace를 관리하는 방법을 제공하여 불필요한 method가 공용 interface를 남용하는 것을 방지
var outer = function(){
var a = 1;
var inner = function(){
return ++a;
};
return inner;
};
var outer2 = outer();
console.log(outer2());
console.log(outer2());
outer함수를 종료할 때, inner 함수를 반환함으로써 outer 함수의 지역변수인 a의 값을 외부에서도 읽을 수 있다.
클로저를 활용하면 return을 통해 외부 스코프에서 함수 내부의 변수들 중 선택적으로 변수에 대한 접근 권한 부여 가능
outer함수가 return한 inner 함수에 접근이 가능하다.
외부에 제공하고자 하는 정보(public)를 모아서 return, 내부에 사용할 정보(private)들은 return하지 않는 것
캡슐화는 간단히 내부의 값들을 외부에서 참조하지 못하도록 숨기는 것으로 클로저의 역할
기존의 javascript의 prototype를 이용해 구현하는 방식이 있다.(this 키워드를 통해 prototype으로 묶음) -> 컨벤션일뿐 외부에서 접근/수정 가능하다.
but 클로저를 이용하여 안정적으로 구현 가능하다.
function Count() {
this._count = 0
}
Count.prototype.counting = function() {
console.log(++this._count)
}
const counter = new Count();
counter.counting() // 1
counter.counting() // 2
counter._count = 99
counter.counting() // 100
//Private 변수로 _ underscore을 사용하는 보통의 네이밍 컨벤션임
function Count() {
let _count = 0
return function() {
console.log(++_count)
}
}
const counting = Count()
counting() // 1
counting() // 2
counting._count = 100;
counting() //3
❓간단하지 않은 자동차 객체 예제
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.fule < wasteFuel){
console.log('이동불가');
return;
}
this.fuel -= wasteFuel;
this.moved += km;
console.log(km + 'km 이동 (총 '+this.moved +'km)');
}
};
car.fuel = 10000;
car.power = 100;
car.moved = 1000;
// 로 설정한다면 car.run() 했을때
//2km 이동 (총 1002km)
🍄 클로저로 변수를 보호한 자동차 객체(1)
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();
car.run();
//4km 이동 (총 4km). 남은 연로: 11
console.log(car.moved); //4
console.log(car.fuel); //undefined
console.log(car.power); //undefined
car.fuel = 1000;
console.log(car.fuel); //1000
car.run(); // 3km 이동 (총 7km). 남은 연로: 10.25
car.power = 100;
console.log(car.power); //100
car.run(); // 6km 이동 (총 13km). 남은 연로: 8.75
car.moved = 1000;
console.log(car.moved); // 13
car.run(); // 4km 이동 (총 17km). 남은 연로: 7.75
클로저를 활용해 접근권한을 제어하는 방법
1) 함수에서 지역변수 및 내부함수 등을 생성한다
2) 외부에 접근권한을 주고자 하는 대상들로 구성된 참조형 데이터(대상이 여럿일 때는 객체 또는 배열, 하나일때는 함수)를 return 한다
✔ return한 변수들은 공개 멤버, 그렇지 않으면 비공개 멤버
💎즉시호출함수(IIFE)
= Immediately Invoked Function Expression
(function () {
//변수 선언
//변수 처리
//특정 로직 등 구현
})();
✔ 괄호로 둘러쌓인 익명함수
✔ 맨 끝에 () 괄호를 통해 함수를 즉각 실행시키는 구조. 즉 선언과 할당 및 실행이 동시에 이루어진다.
✔ 즉시호출 함수를 사용하는 이유??
즉시호출함수에 정의한 기능,변수들은 호출 시 단 한번만 실행되고 종료된다.
JS는 변수,함수 선언시 전역으로 선언되기에 즉시호출함수 선언 시 전역에 함수, 변수가 선언되지 않아 충돌이 없다.
✔ 즉시 실행 함수의 내부에서 정의된 변수는 외부 범위에서 접근 불가
(function () {
var crying = '클로저는 어려워';
})();
console.log(crying);
// ReferenceError: crying is not defined
✔ 즉시 실행 함수를 변수에 할당할 때, 함수가 담기는 것이 아닌 return 값만 담는다.
var result = (function () {
var crying = '클로저는 어려워';
return crying;
})();
console.log(result); //클로저는 어려워
✔ 즉시실행함수와 클로저
- 즉시실행함수를 이용한 클로저 생성
//f의 리턴값 f2 함수가 count 변수를 참조해 클로저 생성
// f1 함수 내 스코프 계속 유지
const f = (function f1() {
let count = 0;
return function f2() {
console.log(`나는 ${++count}번 호출됨.`);
return
}
})();
f(); // 출력값 : "나는 1번 호출됨."
f(); // 출력값 : "나는 2번 호출됨."
- 일반 함수표현식을 이용한 클로저 생성
//즉시실행함수가 아닌 일반적인 함수표현식 사용
// 함수가 실행될때마다 count가 0으로 초기화 되기 때문에 결과값은 계속 1이다
const f = function f1() {
let count = 0;
return function f2() {
console.log(`나는 ${++count}번 호출됨.`);
return
}
}
f()(); // 출력값 : "나는 1번 호출됨."
f()(); // 출력값 : "나는 1번 호출됨."
😣 즉시실행함수인 f1 함수는 선언과 동시에 딱 한번 실행이 되고, 이후 f() 함수를 실행했을 때에는 f2() 함수만 실행이 된다. f2() 함수는 클로저를 만들어 count 변수가 살아있기 때문에 값이 계속 누적된다.
= f() 함수만 접근할 수 있는 스코프 생성
=== 즉시실행함수는 호출 이후 소멸되나, 즉시실행함수가 반환한 함수인 f2()는 자신이 선언되었을 때의 렉시컬 환경인 즉시실행함수 f1()의 스코프에 속해있는 지역변수 count를 기억한다!!! 그래서 즉시실행함수의 변수인 count에 접근할 수 있고, 변수 count는 자신을 참조하는 함수가 소멸할때까지 유지된다. 변수 count는 외부에서 직접 접근할 수 없는 private 변수이므로 안정적이다.
🍋 즉시실행함수를 이용한 클로저 생성_2
- 카운터를 생성하는 익명함수를 즉시호출함수로 만들고 그 결과를 counter 변수에 할당한다.
- 익명함수의 반환값인 세 함수 increment, decrement, value는 비공개멤버에 접근할 수 있는 클로저이다
var counter = (function() { //IIFE 함수
var privateCounter = 0; // private member
function changeBy(val) { // private member
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1
🍋 기명함수를 이용한 클로저 생성_2
- 익명함수를 makeCounter라는 변수에 저장하고, 이 변수를 이용해 여러개의 독립적인 카운터를 만들 수 있다.
- 그들 고유의 클로저를 통한 privateCounter 변수의 다른 버전을 참조한다. 카운터가 호출될 때마다, 하나의 클로저에서 변수값을 변경해도 다른 클로저의 값에 영향을 주지 않는다.
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* 2 */
counter1.decrement();
alert(counter1.value()); /* 1 */
alert(counter2.value()); /* 0 */
#클로저를 사용하는 이유는?
1. 전역 변수의 사용을 억제하기 위해(전역변수를 대체)
2. 정보를 은닉하기 위해(변수 은닉)
3. 실수를 줄이기 위해
출처
코어 자바스크립트(정재남, 위키북스)
'개발공부 > 자바스크립트' 카테고리의 다른 글
[JavaScript] 스터디 9일차_ 프로토타입(1) 개념 및 constructor (0) | 2021.07.12 |
---|---|
[JavaScript] 스터디 8일차_ 클로저 (3) 활용(부분적용함수, 커링함수) (1) | 2021.07.12 |
[JavaScript] 스터디 6일차_ 클로저 (1) 개념 및 메모리 관리 (0) | 2021.06.28 |
[JavaScript] 스터디 5일차_ 콜백 함수 (0) | 2021.06.23 |
[JavaScript] 스터디 4일차_ this (2) call, apply, bind (0) | 2021.06.20 |