Chapter 05. 클로저
5-1 클로저의 의미 및 원리 이해
클로저: 어떤 함수에서 선언한 변수 a를 참조하는 내부함수를 외부로 전달할 경우 외부함수의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상
외부 함수의 변수를 참조하는 내부 함수 (1)
var outer = function () {
var a = 1;
var inner = function() {
console.log(++a);
};
inner();
};
outer();
inner 함수에 a를 선언하지 않았기 때문에 environmentRecord에서 값을 찾지 못하여
→ outerEnvironmentReference에 지정된 상위 컨텍스트인 outer의 LexicalEnvironment에 접근하여 a 찾음
outer 함수의 실행 컨텍스트가 종료되면 LexicalEnvironment에 저장된 식별자들 (a, inner)에 대한 참조 지움
그러면 각 주소에 저장돼 있던 값들은 자신을 참조하는 변수가 하나도 없게 되므로 가비지 컬렉터 수집 대상
외부 함수의 변수를 참조하는 내부 함수 (3)
var outer = function () { // 외부 함수
var a = 1;
var inner = function () { // 내부 함수
return ++a;
};
return inner();
};
var outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3
(1) 코드와 다른점은 return 방법이 inner 함수 자체를 반환함.
(3)의 outer 함수는 실행 종료 시점에 inner 함수를 반환한다. 외부함수인 outer의 실행이 종료되더라도 내부 함수인 inner함수가 언젠가 outer2를 실행함으로써 호출될 가능성이 열려서, inner함수의 실행 컨텍스트가 활성화 되면 outerEnvironmentReference가 outer 함수의 LexcialEnvironment를 필요로 할것이므로 수집 대상에서 제외 된다.
(1)은 outer의 LexicalEnvironment에 속하는 변수가 모두 가비지 컬렉팅 대상에 포함되지만, (3)의 경우 변수 a가 대상에서 제외되었다.
5-2 클로저와 메모리 관리
메모리 누수 : 개발자의 의도와 달리 어떤 값의 참조 카운트가 0이 되지 않아 가비지 컬렉터의 수거 대상이 되지 않는 경우
메모리 소모 : 클로저의 본질적인 특성 (의도 대로 설계한 메모리 소모에 대한 관리법만 잘 파악해서 적용하는 것으로 충분)
- 관리법 : 필요성이 사라진 시점에 메모리를 차지하지 않도록 참조 카운트를 0으로 만든다!⇒ 참조 카운트 0으로 만드는 방법 : 식별자에 참조형이 아닌 기본형 데이터(null, undefined) 할당
5-3 클로저 활용 사례
5-3-1 콜백함수 내부에서 외부 데이터를 사용하고자 할 때
콜백 함수를 내부함수로 선언해서 외부변수를 직접 참조하는 방법
var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul'); // (공통 코드)
fruits.forEach(function (fruit){ // (A)
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', function) (){ // (B)
alert('your choice is ' + fruit); // 클로저 o
});
$ul.appendChild($li);
});
document.body.appendChild($ul);
bind 메서드로 값을 직접 넘김 (클로저 사용 x, 제약사항)
**...
fruits.forEach(function (fruit){
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventLis**tener('click', alertFruit.bind(null, fruit));
$ul.appendChild($li);
});
...
콜백 함수 → 고차함수 변경하여 사용 (클로저 적극적으로 활용)
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);
});
...
5-3-2 접근 권한 제어 (정보 은닉)
정보 은닉: 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 것
자바스크립트는 기본적으로 변수 자체에 이러한 접근 권한 직접 부여하지 못함
클로저를 이용하면 함수 차원에서 public 한 값과 private한 값을 구분 가능.
→ return을 활용하여 외부 스코프에서 함수 내부의 변수들 중 선택적으로 일부의 변수에 대한 접근 권한 부여
return한 변수들은 공개 멤버, 그렇지 않은 변수들은 비공개 멤버라고 함.
클로저로 변수를 보호한 자동차 객체
var createCar = function() {
var fuel = Math.ceil(Math.random() * 10 + 10); // 연료(L)
var power = Math.ceil(Math.random() * 3 + 2); // 연비(km/L)
var moved = 0;
return {
get moved() {
return moved;
}
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 이동 (총 ' + moved + 'km). 남은 연료: ' + fuel);
}
};
};
var car = createCar();
클로저로 변수를 보호한 자동차 객체(2)
var createCar = functuin() {
...
var publicMembers = {
...
};
Object.freeze(publicMembers);
return publicMembers;
}
// return 하기전에 미리 변경할 수 없게 하는 방법
접근 권한 제어 방법
- 함수에서 지역변수 및 내부함수 생성
- 외부에 접근권한을 주고자 하는 대상들로 구성된 참조형 데이터 (대상이 여럿일 때는 객체 또는 배열, 하나일 때는 함수)를 return 한다.
→ return한 변수들은 공개 멤버가 되고, 그렇지 않은 변수들은 비공개 멤버가 됨.
5-3-3 부분 적용 함수
부분 적용 함수: n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억 시켰다가, 나중에 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게 만드는 함수
디바운스(debounce)
: 짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것!
⇒ 성능 최적화에 큰 도움을 주는 기능
5-3-4 커링 함수
커링 함수 : 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것
→ 커링은 한 번에 하나의 인자를 전달하는 원칙.
var curry3 = function (func) {
return function (a) {
return function (b) {
return func(a, b);
};
};
};
var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWiht10(8)); // 10
console.log(getMaxWiht10(25)); // 25
인자가 많으면 가독성이 떨어진다.
→ 이럴땐 ES6 문법에 화살표 함수를 사용하면 한줄코드로 가능하다.
var curry3 = func => a => b => c => func(a,b,c);
💡 커링 함수가 유용한 경우
당장 필요한 정보만 받아서 전달하고 또 필요한 정보가 들어오면 전달하는 식으로 하면 결국 마지막 인자가 넘어갈 때까지 함수 실행을 미룸 ⇒ 지연실행
'코어자바스크립트' 카테고리의 다른 글
[CoreJavaScript] Chapter 04. 콜백함수 내용 정리 (0) | 2024.10.14 |
---|---|
[CoreJavascript] Chapter 03. this 내용 정리 (0) | 2024.09.27 |
[CoreJavaScript] Chapter 02. 실행 컨텍스트 내용 정리 (1) | 2024.09.13 |
[CoreJavaScript] Chatper 01. 데이터 타입 내용 정리 (3) | 2024.09.10 |