[자바스크립트] Function - Closure
함수의 Scope
클로져(Closure)에 대해서 알아보기 전에 Scope에 대한 개념을 먼저 짚고 넘어가야 한다. scope란 범위라는 의미로 프로그래밍에서도 범위의 의미를 가지고 쓰인다.
구체적으로, 자바스크립트에서 스코프란 식별자 접근 규칙에 따른 유효 범위를 뜻한다. 여기서 식별자란 변수, 함수, 클래스 등이 있다. 즉, 스코프란 선언된 식별자가 영향을 끼칠 수 있는 범위라고 이해하면 된다!! (아래의 스코프 주요 규칙 참고!!)
자바스크립트에서는 변수의 스코프를 두가지 종류로 나눌 수 있다. 식별자가 중괄호 내에서만 영향을 끼치면, block scope라고 하고 함수 내에서만 영향을 끼치면 function scope라고 한다.
스코프의 주요 규칙
- 안쪽 스코프에서 바깥쪽 스코프로 접근할 수 있지만 반대는 불가능
- 바깥족 스코프에서 선언한 식별자는 안쪽 스코프에서 사용 가능
- 반면, 안쪽에서 선언한 식별자는 바깥쪽 스코프에서는 사용 불가
- 스코프는 중첩이 가능
- 전역 스코프와 지역 스코프
- 가장 바깥쪽의 스코프 => 전역 스코프(Global Scope)
- 전역이 아닌 다른 스코프 => 지역 스코프(Local Scope)
- 지역변수는 전역변수보다 우선순위가 높다
클로저 (Closoure)
클로저란 외부함수의 접근할 수 없는(접근할 수 없게 된) 변수에 접근할 수 있는 내부 함수를 일컫는 말로써, 스코프 체인으로 표현되기도 한다.
아래의 코드를 먼저 살펴보자.
function OuterFunc() {
//외부함수에서 x 변수 선언
// 변수 x는 함수 스코프를 가진다 => 유효범위 : OuterFunc
const x = 10;
//x를 출력하는 내부 함수
const innerFunc = function () { console.log(x); };
//innerFunc에 저장된 함수 데이타 반환
return innerFunc;
}
//함수의 OuterFunc()호출
const inner = outerFunc();
//inner에 저장된 함수데이터 호출
inner(); //10
위의 코드는 클로저의 간단한 예제이다. 위의 코드에서 outerFunc() 호출되는 부분부터 단계별로 접근하면 아래와 같은 과정을 거친다.
- outerFunc() 호출 =>
- 변수 x 가 생성 후 10으로 초기화
- innerFunc 가 생성 후 함수데이터로 초기화
- 함수 데이터 innerFunc 반환
- inner 변수에 반환된 innerFunc 대입
- 변수 x 는 함수 outerFunc()의 호출이 끝나는 순간 메모리에서 사라짐!! (함수 스코프를 가지므로)
- inner 변수에 저장된 함수 호출
- 사라져서 접근할 수 없어야 할 변수 x에 접근하여 10을 출력
즉, outerFunc의 실행이 종료된 후 변수 x 도한 유효하지 않게 되어 접근할 수 없는 변수가 되었지만 inner함수를 통해 접근하는 말이 되지 않는 동작이 일어난 것이다. 이것이 가능한 이유가 바로 클로저때문이다.
클로져란, 내부함수가 유효한 상태에서 외부함수가 종료되어 외부함수가 call stack에서 제거되어도, 외부함수 내의 활성 객체(Acivation object, 변수, 함수 선언 등의 정보)는 내부함수에 의해 참조되는 한 유효하여 내부함수가 스코프 체인을 통해 참조할 수 있는 것을 의미한다.
이러한 클로저는 아래와 같이 3가지 스코프 체인을 가진다.
- 클로져 자신에 대한 접근
- 외부 함수의 변수에 대한 접근 (위의 예시!!)
- 전역 변수에 대한 접근
클로저의 장점
데이터를 보존할 수 있다.
클로저 함수는 외부 함수의 실행이 끝나더라도 외부 함수 내 변수를 사용할 수 있다. 클로저는 이처럼 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있게 하는 폐쇄성을 가진다. (위의 예시 참고!!)
정보의 은닉, 캡슐화
OOP(객체지향 프로그래밍)의 특성인 은닉, 캡슐화를 가능하게 한다. 아래의 코드를 살펴보자.
//클로저의 장점
//정보의 은닉과 캡슐화
//예제 : 특정함수를 직접 호출하지 못하게 하기!!
const oop = function () {
const privateFunc = function () { console.log('closure'); };
return {
publicFunc : function() { privateFunc(); }
}
};
oop().publicFunc(); //closure
위의 예제에서 privateFunc은 publicFunc을 통해서만 호출할 수 있다. 이처럼 공개하고 싶은 정보와 공개 하지 않을 것을 정하는 것이 가능하다.
모듈화에 유리
클러저 함수를 각각의 변수에 할당하면 각자 독립적으로 값을 사용하고 보존할 수 있다. 이와 같이 함수의 재사용성을 극대화 함수 하나를 독립적인 부품의 형태로 분리하는 것을 모듈화라고 한다. 클로저를 통해 데이터와 메소드를 묶어 다닐 수 있기에 클러저는 모듈화에 유리하다.
아래 코드와 같이 모듈화를 하기가 유리해지는 것이다.
//모듈화 예제
//사람 모듈
//데이터 : 이름
//메소드 : printName() : 나는 ${이름}입니다.
const person = function(name) {
const personName = name;
const print = function() { console.log(`나는 ${personName}입니다.`);
return {
name : personName,
printName : function() {
print();
}
}
}
const person1 = person('HongGilDong');
const person2 = person('LeeSunSin');
person1.printName(); //나는 HongGilDong입니다.
console.log(person2.name); //LeeSunSin
클로저의 단점
이러한 클로저도 당연히 단점은 존재한다. 그렇기 때문에 무분별하게 사용하기 보다는 꼭 필요한 경우에, 구현에 있어서 훨씬 개발이 편해질 때 사용하는 것을 권장한다.
- 메모리를 소모
- scope 생성에 따른 퍼포먼스 손해
메모리의 소모는 리턴하거나 timer, 콜백 등으로 등록했던 함수들이 메모리에 계속 남아있게 되면 해당하는 closure도 같이 메모리에 계속 남아있게 되는 것이기 때문에, 지속적으로 루프를 돌면서 closure 생성하는 것은 지양해야할 수 도 있다.
또한, 클로저는 하나의 새로운 Scope를 생성하여 내부의 함수에 링크를 시키기 때문에 이에 따른 퍼포먼스 손해 불가피한 것이다.
위의 두 가지의 단점들은 클로저의 작동 방식 상 극복할 수 없는 단점이다. 그렇기 때문에 클로저를 필요한 곳에만 사용하는 것이 필요하다. (위의 장점들을 최대한 살릴 수 있는 코드에 사용!!)