[JavaScript] 자바스크립트 클로저

정의

중첩함수에서 내부함수가 외부함수의 환경을 기억하는것을 클로저라고 한다.

function func() {
    var foo = 'data';
    return function() {
        return foo;
    }
}

func라는 함수를 선언하고 foo변수에 ‘data’문자열을 추가한 뒤 foo변수를 리턴하는 익명함수를 선언하였다.

var closure = func();
console.log(closure());
// 'data'

func함수의 리턴값을 closure변수에 할당한 뒤 closure를 실행한 값을 콘솔로 찍어보았다. 결과는 ‘data’라는 문자열이 출력되었다. func의 지역변수로 있는 foo는 func함수가 끝나면서 소멸되어야하지만 값을 잃지 않고 ‘data’값을 가지고 있다. 이 현상을 클로저라 한다. 다른 구문도 살펴보겠다.

function count() {
    var num = 0;
    return function() {
        num++;
        return num;
    }
}

var closure = count();
console.log(closure());
console.log(closure());
console.log(closure());
// 1
// 2
// 3

count함수의 지역변수인 num값이 소멸되지 않고 계속 카운트되는 것을 확인해 볼 수 있다.

특징

변수의 은닉화

자바스크립트에서는 인스턴스를 생성할때 Private Variables에 대한 접근 권한 문제가 있다.

function Create(name) {
    this._name = name;
}

var obj = new Create('민수');
console.log(obj._name);
// 민수

위에서 생성된 obj객체의 _name프로퍼티는 변수명 앞에 _를 포함하였기 때문에 Private Variables로 쓰고싶다는 의도를 알 수 있다. 하지만 _name프로퍼티는 동적으로 변경될 수 있다.

obj._name = '인성';
console.log(obj._name);
// 인성

이 경우 클로저를 사용하여 외부에서 내부변수에 접근하는것을 제한할 수 있다.

function create(name) {
    var _name = name;
    return function() {
        console.log(_name);
    }
}

var hello = create('민수');
hello();
// 민수

여기서는 외부에서 _name에 접근할 방법이 전혀 없다. 이렇게 클로저를 활용하여 은닉화를 해결할 수 있다.

고유한 환경

클로저는 고유한 환경을 가지고 있다.

function func(name) {
    var txt = name;
    return function() {
        return txt;
    }
}

var closure01 = func('민수');
var closure02 = func('인성');
var closure03 = func('한나');

console.log(closure01()) // 민수
console.log(closure02()) // 인성
console.log(closure03()) // 한나

위의 구문을 보면 txt변수가 동적으로 변화하는 것처럼 보이지만, 실제로는 txt변수 자체가 여러번 생성된 것이다. 즉, closure01(), closure02(), closure03()은 서로 다른 환경을 가지고 있다. 서로 다른 환경을 가지고 있다는것은 그만큼 메모리면에서 큰 비효율을 낳는다.

function Func(input) {
    this.name = input;
    this.get = function() {
        return this.name;
    }
    this.set = function (rename) {
        this.name = rename;
    }
}

var obj = new Func('민수');
console.log(obj.get());

위 코드는 생성자함수를 사용하여 인스턴스를 생성하는 구문인데, 클로저가 두번(get, set)이나 생성되었다. 이 상태에서 인스턴스를 계속 만들면 같은일을 하는 클로저가 중복으로 생성되고 메모리낭비가 심해질 것이다.
따라서 클로저는 객체의 prototype안에 저장함으로써 같은 기능을 모든 인스턴스가 공유하는 형태로 코드를 만들어야한다.

function Func(input) {
    this.name = input;
}
Func.prototype.get = function() {
    return this.name;
};
Func.prototype.set = function(rename) {
    this.name = rename;
};

var obj = new Func('민수');
console.log(obj.get());

이렇게 prototype안에 클로저를 넣으면, 인스턴스가 생성되더라도 중복으로 메모리를 낭비하지 않고, 생성자 내부의 prototype안의 클로저를 참조하기 때문에 메모리낭비를 방지할 수 있다.

정리

  • 클로저는 독립적인(자유) 변수를 가리키는 함수 또는 클로저 안에 정의된 내부함수는 만들어진 환경을 기억한다.
  • 클로저는 외부함수의 스코프 영역에 접근할 수 있고, 그것을 기억하고 있어야 한다.
  • 외부함수가 종료된 후에도 내부함수는 외부함수를 계속 참조하고 있어야 한다.
  • 데이터의 캡슐화 및 정보은닉에도 사용 가능하다.

References

JavaScript 클로저(Closure)

updated_at 01-02-2020