[ydkjsy]Scope & Closures-7-Using Closures

p*0*q·2021년 1월 13일
0

YDKJS

목록 보기
11/16

See the Closure

몇가지 알고 들어가면 좋은 것, 클로저는 클래스나 객체에서는 발생하지 않고 함수에서만 발생하고 함수가 정의된 곳과 다른 곳에서 invoked 해야 한다.

// outer/global scope: RED(1)

function lookupStudent(studentID) {
    // function scope: BLUE(2)

    var students = [
        { id: 14, name: "Kyle" },
        { id: 73, name: "Suzy" },
        { id: 112, name: "Frank" },
        { id: 6, name: "Sarah" }
    ];

    return function greetStudent(greeting){
        // function scope: GREEN(3)

        var student = students.find(
            student/*shadowing*/ => 
          // function scope: ORANGE(4)
          student.id == studentID
        );

        return `${ greeting }, ${ student.name }!`;
    };
}

var chosenStudents = [
    lookupStudent(6),
    lookupStudent(112)
];

// accessing the function's name:
chosenStudents[0].name;
// greetStudent

chosenStudents[0]("Hello");
// Hello, Sarah!

chosenStudents[1]("Howdy");
// Howdy, Frank!

바깥 함수 스코프의 변수들이 버려지거나 GC'd(garbage collected) 되었을 거라고 생각했는데 살아있다. 이와 같이 내부의 함수가 폐쇄된 바깥 함수의 변수를 참조하는 것을 클로저라고 하고 위의 상황을 greetStudent()함수의 인스턴스가 변수에 클로즈 오버했다고 말한다(실은 greetStudent가 아니고 arrow함수가 studntID에 클로즈 오버). 즉, 원래 ReferenceError가 떴을텐데 GC 되지 않고 메모리에 남는다.

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

클로저는 함수 인스턴스마다 적용되며 런타임 특징으로 관찰된다.

live link라서 업데이트도 가능하다.

function makeCounter() {
    var count = 0;

    return function getCurrent() {
        count = count + 1;
        return count;
    };
}

var hits = makeCounter();

// later

hits();     // 1

// later

hits();     // 2
hits();     // 3

꼭 함수일 필요 없이 바깥 스코프가 있으면 된다. 근데.. FiB..쓰지마..

var hits;
{   // an outer scope (but not a function)
    let count = 0;
    hits = function getCurrent(){
        count = count + 1;
        return count;
    };
}
hits();     // 1
hits();     // 2
hits();     // 3

그리고 value-oriented가 아니고 variable-oriented.
다음은 보통 안에 이벤트 핸들러같은 콜백을 넣고 사용하는 예시로 variable-oriented를 잘 보여준다.

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 keeps = [];

for (var i = 0; i < 3; i++) {
    let j = i;
    keeps[i] = function keepEachJ(){//원래 같으면 이 함수를 setTimeout()안으로
        return j;
    };
}
keeps[0]();   // 0
keeps[1]();   // 1
keeps[2]();   // 2
//or
var keeps = [];

for (let i = 0; i < 3; i++) {
    keeps[i] = function keepEachI(){
        return i;
    };
}
keeps[0]();   // 0
keeps[1]();   // 1
keeps[2]();   // 2

Common Closures: Ajax and Events

콜백이랑 자주 쓰인다. ajax 호출의 응답이 돌아왔을 때 onRecord에서 바깥함수의 studentID를 참조해야 한다.

function lookupStudentRecord(studentID) {
    ajax(
        `https://some.api/student/${ studentID }`,
        function onRecord(record) {
            console.log(
                `${ record.name } (${ studentID })`
            );
        }
    );
}

lookupStudentRecord(114);
// Frank (114)

이벤트 핸들러. onClick에서 label를 클로스 오버.

function listenForClicks(btn,label) {
    btn.addEventListener("click",function onClick(){
        console.log(
            `The ${ label } button was clicked!`
        );
    });
}

var submitBtn = document.getElementById("submit-btn");

listenForClicks(submitBtn,"Checkout");

What If I Can't See It?

클로저는 관찰 가능해야 한다.

아래는 호출이 inner 함수와 같은 scope에서 발생하는 경우로 클로저가 아니다. 이를 좀 더 생각해본다면 전역 변수는 어디서나 접근 가능하기 때문에 클로즈 오버될 수 없다.

function say(myName) {
    var greeting = "Hello";
    output();

    function output() {
        console.log(
            `${ greeting }, ${ myName }!`
        );
    }
}

say("Kyle");
// Hello, Kyle!

매우 .. 당연한 얘기지만, 변수가 그냥 사용되지 않는다면 GC된다. 클로저가 아니다.

내부가 함수가 호출되지 않아도 클로저가 관찰되지 않는다.

function greetStudent(studentName) {
    return function greeting(){
        console.log(
            `Hello, ${ studentName }!`
        );
    };
}

greetStudent("Kyle");

// nothing else happens

Observable Definition

  • 함수가 포함되어야 하고,
  • 적어도 바깥 스코프의 변수를 참조해야 한다.
  • 해당 변수의 스코프 체인과 다른 스코프에서 호출되어야 한다.

The Closure Lifecycle and Garbage Collection (GC)

GC되지 않은 변수들이 계속 쌓이게 되면 메모리 사용이 급증할 수 있으므로 더 이상 필요하지 않으면 함수 참조를 버리는 것이 중요.

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();

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);
// [ .., .., ... ]

JS가 자동으로 최적화 해 주지만, 수동적으로 버려주는 것이 안전,

function manageStudentGrades(studentRecords) {
    var grades = studentRecords.map(getGrade);

    // unset `studentRecords` to prevent unwanted
    // memory retention in the closure
    studentRecords = null;
	getGrade = null;// 첫줄에서 끝나서 더 이상 필요 없으니까
    return addGrade;
    // ..
}

정리: 클로저의 장소, 클로저가 포함하는 변수를 잘 파악하여 메모리 관리하자.

An Alternative Perspective

내부 함수가 return되는 것이 아닌 스코프 환경(스코프 체인)을 유지하여 참조를 반환.

Why Closure?

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;//이벤트 핸들러가 DOM속성을 실행할때마다 읽어야하니 비효율
    ajax(
        APIendpoints[recordKind],
        data[recordKind]
    );
}

// <button data-kind="studentIDs">
//    Register Students
// </button>
btn.addEventListener("click",makeRequest);
var APIendpoints = {
    studentIDs:
        "https://some.api/register-students",
    // ..
};

var data = {
    studentIDs: [ 14, 73, 112, 6 ],
    // ..
};

function setupButtonHandler(btn) {
    var recordKind = btn.dataset.kind;//just once

    btn.addEventListener(
        "click",
        function makeRequest(){
            ajax(
                APIendpoints[recordKind],
                data[recordKind]
            );
        }
    );
}

// <button data-kind="studentIDs">
//    Register Students
// </button>

setupButtonHandler(btn);
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);
        }
    );
}

0개의 댓글

관련 채용 정보