클로저

Chanhee Jang·2023년 3월 30일
0

JS

목록 보기
5/9

클로저?
어떤 함수의 선언된 변수를 참조하는 내부함수에서 발생하는 현상

var outer = function () {
    var a = 1;
    var inner = function () {
        console.log(++a);
    };
    inner();
};

outer(); // 2
var outer = function () {
    var a = 1;
    var inner = function () {
        return ++a;
    };
    return inner();
};

var outer2 = outer();

console.log(outer2); // 2

위 두 코드의 예시는 inner()가 호출되면 함수의 실행 컨텍스트가 종료되므로 a 변수를 참조하는 대상이 없어진다.

결국 a, inner 변수의 값들은 언젠가 가비지 컬렉터에 의해 소멸된다.

outer 함수의 실행 컨텍스트가 종료되기 전에, inner 함수 실행 컨텍스트가 종료돼 있으며, 별도로 inner 함수를 호출할 수 없다는 공통점이 있다.

그렇다면 outer의 실행 컨텍스트가 종료된 후에도, inner 함수를 호출할 수 있게 만들면?

var outer = function () {
    var a = 1;
    var inner = function () {
        return ++a;
    };
    return inner;
};

var outer2 = outer();

console.log(outer2()); // 2
console.log(outer2()); // 3

inner 함수의 environmentRecord에는 수집할 정보가 없다. outerEnvironmentReference에는 inner가 선언된 위치의 LexicalEnvironment가 참조복사 된다.

inner 함수는 outer 함수 내부에서 선언된다. outer 함수의 LexicalEnvironment가 담긴다.

스코프 체이닝에 따라 outer에서 선언된 변수 a에 접근해 1만큼 증가시켜 2를 리턴하고 inner함수의 실행 컨텍스트가 종료된다.

가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 컬렉팅 대상에 포함되지 않는다.

어떤 함수의 LexicalEnvironment가 이를 참조할 예정인 다른 실행 컨텍스트가 있다면 실행 종료 이후에도 GC되지 않는다.


어떤 함수의 선언된 변수를 참조하는 내부함수에서 발생하는 현상은 ‘외부함수의 LexicalEnvironment가 가비지컬렉팅 되지 않는 현상’이다. (메모리에 여전히 존재한다는 말)

클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상이다.


클로저를 이용한 접근 권한 제어(정보 은닉)

함수차원에서 public과 private한 값을 구분지어보자.

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을 활용해서 일부 변수에 대한 접근 권한을 부여할 수 있다.

var createCar = function () {
    var fuel = Math.ceil(Math.random() * 10 + 10);
    var power = Math.ceil(Math.random() * 3 + 2);
    var moved = 0;

    var publicMembers = {
        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}`);
        },
    };

    Object.freeze(publicMembers);

    return publicMembers;
};

var car = createCar();
car.run();

클로저를 이용한 부분 적용 함수 구현

n개의 인자를 받는 함수에 m개의 인자를 미리 넘겨놓고, 나중에 n-m개의 인자를 넘기면 실행되는 함수를 부분 적용 함수라고 한다.

Object.defineProperty(global, "_", {
    value: "EMPTY_SPACE",
    writable: false,
    configurable: false,
    enumerable: false,
});

var partial = function () {
    var originalPartialArgs = arguments;
    var func = originalPartialArgs[0];
    if (typeof func !== "function") {
        throw new Error("첫 번째 인자가 함수가 아닙니다.");
    }

    return function () {
        var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
        var restArgs = Array.prototype.slice.call(arguments);
        for (var i = 0; i < partialArgs.length; i++) {
            if (partialArgs[i] === _) {
                partialArgs[i] = restArgs.shift();
            }
        }

        return func.apply(this, partialArgs.concat(restArgs));
    };
};

var dog = {
    name: "강아지",
    greet: partial(function (prefix, suffix) {
        return prefix + this.name + suffix;
    }, "왈왈"),
};
console.log(dog.greet("배고파요!"));

디바운스 구현하기

var debounce = function (eventName, func, wait) {
    var timeoutId = null;
    return function (event) {
        var self = this;
        console.log(eventName, "event 발생");
        clearTimeout(timeoutId);
        setTimeout(func.bind(self, event), wait);
    };
};

커링 함수

커링 함수는 여러개의 인자를 받는 함수를 한 인자만 받는 함수로 나눠 순차적으로 호출될 수 있게 체인 형태로 구성한 걸 말한다.

커링은 한번에 한 인자만 전달하는 걸 원칙으로 하며 마지막 인자가 전달될 때까지 원본 함수가 실행되지 않는다.

(부분 적용 함수는 여러 개의 인자를 전달하며, 실행 결과를 재실행할 때마다 원본 함수가 무조건 실행된다는 차이점이 있다.)

var curry = (func) => (a) => (b) => (c) => func(a, b, c);
var getMinWith10 = curry(Math.min)(10);

console.log(getMinWith10(8)(3));

당장 필요한 정보만 받아 전달하고 필요한 정보가 들어오면 전달하는 식으로 하면 결국 마지막 인자가 넘어갈 때 까지 함수 실행을 미룬다. 이를 lazy execution이라고 한다.


정리

클로저는 어떤 함수에서 선언된 변수를 참조하는 내부 함수를 외부로 전달할 때, 함수의 실행 컨텍스트가 종료된 후에도 해당 변수가 사라지지 않는 현상이다.

내부 함수를 외부로 전달한다는 건 함수를 return하는 것 뿐만 아니라, 콜백으로 전달하는 경우도 포함된다.

클로저는 메모리를 차지하는 개념이므로 더는 사용하지 않는다면 메모리를 차지하지 않도록 관리해줄 필요가 있다.

정보 은닉, 부분 적용 함수, 디바운스, 커링등등 여러 곳에서 활용될 수 있다.


Refer - 코어 자바스크립트

profile
What is to give light must endure burning

0개의 댓글