요약
이번 장에서는 클로저에 대해 다룰 것임.
이전 챕터에서 말했던대로, 클로저는 POLE원칙에 기반한 것임.
POLE => 변수접근을 최대한 어렵게!!(블록스코핑) => 코드이해 쉬움, 유지보수 쉬움, 스코프 충돌 피하기 쉬움
See the Closure
- 클로저는 함수인 경우에만 작동
- 내부 함수가 외부 범위의 변수에 대한 참조를 만드는 것이 클로저
Pointed Closure
- 화살표 함수도 스코프 버블을 생성함 => 화살표 함수도 클로저에 참여!!
Adding Up Closures
클로저에 인용되는 전형적인 예시
function adder(num1) {
return function addTo(num2){
return num1 + num2;
};
}
var add10To = adder(10);
var add42To = adder(42);
add10To(15); // 25
add42To(9); // 51
adder이 실행될 때마다 새로운 inner addTo함수 인스턴스가 생성됨.
Live Link, Not a Snapshot
- 클로저는 어떤 특정한 순간의 스냅샷이 아님, 실시간으로 읽기, 쓰기가 가능
그림처럼 adder를 호출할 때마다 addTo함수 스코프가 생성됨.
var keeps = [];
for (var i = 0; i < 3; i++) {
keeps[i] = function keepI(){
// closure over `i`
return i;
};
}
keeps[0](); // 3 -- WHY!?
keeps[1](); // 3
keeps[2](); // 3
var이므로 값을 공유한다 따라서 3,3,3이 출력됨
그러나 let으로 하면 1,2,3이 정상 호출됨 => 반복문을 돌 때마다 스코프가 형성되기 때문!
Common Closures: Ajax and Events
- ajax 비동기통신, event에서도 클로저를 활용할 수 있다.
What If I Can't See It?
function say(myName) {
var greeting = "Hello";
output();
function output() {
console.log(
`${ greeting }, ${ myName }!`
);
}
}
say("Kyle");
// Hello, Kyle!
위 greeting 변수는 글로벌 범위 변수로 어디에서나 접근 가능하다. 따라서 클로저로 간주되지 않음.
이는 단지 렉시컬 스코프임
=>어디에서나 접근할 수 있다면 굳이 클로저로 처리할 필요 없음!
Observable Definition
이제 클로저를 정의 해보겠음
클로저는 함수가 외부 스코프의 변수를 사용하는 것.
핵심요소
- 함수가 포함되어야 함.
- 최소한 하나의 외부 스코프 변수를 참조해야함
- 변수와 다른 분기(다른 스코프)에서 호출되야함.
The Closure Lifecycle and Garbage Collection (GC)
더이상 필요하지 않은 참조(클로저)는 폐기하는 것이 중요함
만약 10개의 함수가 동일한 변수를 클로저로 가지고 있고, 함수 참조 중 9개가 폐지된다면 여전히 변수를 보존할 것이고, 마지막 참조가 사라져야 클로저도 사라질 것.
function manageBtnClickEvents(btn) {
var clickHandlers = [];
return function listener(cb){
if (cb) {
let clickHandler =
function onClick(evt){
console.log("clicked!");
cb(evt);
};
clickHandlers.push(clickHandler);
btn.addEventListener(
"click",
clickHandler
);
}
else {
// passing no callback unsubscribes
// all click handlers
for (let handler of clickHandlers) {
btn.removeEventListener(
"click",
handler
);
}
clickHandlers = [];
}
};
}
// var mySubmitBtn = ..
var onSubmit = manageBtnClickEvents(mySubmitBtn);
onSubmit(function checkout(evt){
// handle checkout
});
onSubmit(function trackAction(evt){
// log action to analytics
});
// later, unsubscribe all handlers:
onSubmit();
이 프로그램에서 onClick 함수는 전달된 cb를 클로저로 유지함.
마지막 인자가 빈 함수를 호출 할때, clickHandler 배열이 비워지면서, 모든 클릭 핸들러 함수 참조가 폐지됨.
따라서 cb 참조 클로저도 폐기됨. => 이렇게 더이상 필요하지 않은 이벤트 핸들러를 구독 해제하는게 중요
Per Variable or Per Scope?
클로저는 변수단위로 이루어 질까, 스코프 단위로 이루어질가?
function manageStudentGrades(studentRecords) {
var grades = studentRecords.map(getGrade);
return addGrade;
// ************************
function getGrade(record){
return record.grade;
}
function sortAndTrimGradesList() {
// sort by grades, descending
grades.sort(function desc(g1,g2){
return g2 - g1;
});
// only keep the top 10 grades
grades = grades.slice(0,10);
}
function addGrade(newGrade) {
grades.push(newGrade);
sortAndTrimGradesList();
return grades;
}
}
var addNextGrade = manageStudentGrades([
{ id: 14, name: "Kyle", grade: 86 },
{ id: 73, name: "Suzy", grade: 87 },
{ id: 112, name: "Frank", grade: 75 },
// ..many more records..
{ id: 6, name: "Sarah", grade: 91 }
]);
// later
addNextGrade(81);
addNextGrade(68);
// [ .., .., ... ]
이 코드는 grades, sortAndTrimGradesList를 클로저로 활용하는 코드 스니펫임.
grades를 통해 학생 전체리스트 참조 하는 것을 방지할 수 있음.(클로저 하지 않음으로써)
그러나
function storeStudentInfo(id,name,grade) {
return function getInfo(whichValue){
// warning:
// using `eval(..)` is a bad idea!
var val = eval(whichValue);
return val;
};
}
var info = storeStudentInfo(73,"Suzy",87);
info("name");
// Suzy
info("grade");
// 87
이코드를 보면 내부함수에서 id, name, grade어떤것도 사용하고 있지만 접근할 수 있음.
변수들은 명시적으로 내부 함수에서 참조되지 않더라도 클로저를 통해 보존된 것.
그럼 클로저는 스코프 단위로 작동하는게 맞는걸까?
=>상황에 따라 다름
현대의 js엔진은 참조되지 않는 클로저 스코프 내의 변수를 제거해서 최적화함 => 그러나 eval같은 상황에서는 최적화 불가능.
Why Closure?
페이지에 있는 버튼을 클릭했을 때, ajax를 이용하여 데이터를 보내는 상황을 가정해보자
var APIendpoints = {
studentIDs:
"https://some.api/register-students",
// ..
};
var data = {
studentIDs: [ 14, 73, 112, 6 ],
// ..
};
function makeRequest(evt) {
var btn = evt.target;
var recordKind = btn.dataset.kind;
ajax(
APIendpoints[recordKind],
data[recordKind]
);
}
// <button data-kind="studentIDs">
// Register Students
// </button>
btn.addEventListener("click",makeRequest);
이 코드는 잘 작동하지만, 매번 이벤트핸들러가 돔 속성을 읽어야 된다는 단점(비효율적)이 있음
function setupButtonHandler(btn) {
var recordKind = btn.dataset.kind;
var requestURL = APIendpoints[recordKind];
var requestData = data[recordKind];
btn.addEventListener(
"click",
function makeRequest(evt){
ajax(requestURL,requestData);
}
);
}
이 코드를 사용한다면 data-kind속성은 초기 설정시 한번만 검색되어 recordKind에 할당됨.
스후에 클로저로 유지되며 필요할 때 마다 접근할 수 있음.
function defineHandler(requestURL,requestData) {
return function makeRequest(evt){
ajax(requestURL,requestData);
};
}
function setupButtonHandler(btn) {
var recordKind = btn.dataset.kind;
var handler = defineHandler(
APIendpoints[recordKind],
data[recordKind]
);
btn.addEventListener("click",handler);
}
완성된 코드.
Closer to Closure
클로저가 프로그램에 가져다주는 이점
- 함수 인스턴스가 매번 계산을 다시 수행하는 대신 이전에 결정된 데이터를 사용해 효율적인 프로그램이 됨
- 클로저는 변수들을 함수 인스턴스 내부에 캡슐화해서 스코프 노출을 제한하고, 그 변수에 있는 정보를 향후 사용 가능하도록 보장함.
느낀 점
책을 읽기전에 관심있는 챕터 중 하나였다.
어떤 기업 면접을 볼 때, 면접관이 클로저가 무엇인지 물어보았었다.
어떻게 공부한대로 대답을 하긴 했지만, 정확히 대답 했는지 의문이 있었고 항상 내가 클로저를 안다고 하기엔 찝찝함이 있었다.
이 챕터를 통해 클로저의 정의, 클로저의 핵심요소를 파악할 수 있었고
js엔진이 클로저를 어떻게 최적화 하는지,
클로저를 사용하면 계속 다시 계산 안해도 된다는 장점을 알게되고, 메모리로 인한 단점도 알게 되었다.
이 책을 모두 읽었을 때 다시한번 와서 읽어봐야겠다.
'공부 정리 > You Don't Know Js' 카테고리의 다른 글
[YDKJ] Scope & Closures - Appendix A: Exploring Further (0) | 2023.06.13 |
---|---|
[YDKJ] Scope & Closures - The Module Pattern (1) | 2023.06.05 |
[YDKJ] Scope & Closures - Limiting Scope Exposure (0) | 2023.05.27 |
[YDKJ] Scope & Closures - The (Not So) Secret Lifecycle of Variables (0) | 2023.05.22 |
[YDKJ] Scope & Closures - Around the Global Scope (0) | 2023.05.18 |
댓글