[JavaScript] 클로저 (Closure)

D uuu·2023년 12월 20일
0

JavaScript

목록 보기
6/8

클로저(Closure) 란?

클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다. -MDN 클로저 정의

MDN 이 정의한 클로저는 글만 봐서는 솔직히 이해하기가 어렵다. 따라서 무슨 뜻인지 찬찬히 살펴보자.
우선 렉시컬 환경이 무엇인지 아래 코드를 보면 알 수 있다.

const x = 1;

function outerFun() {
	const x = 10;
    const innerFun = () => {
    	console.log(x)
    }
    innerFun()
}

outerFun() 

innerFun 함수 안에 콘솔 x 의 값은 10 이 나온다.
왜냐하면 outerFun 함수의 내부에서 innerFun 함수가 선언되었기 때문에 innerFun 에서 외부 함수인 outerFun 의 변수에 접근이 가능하기 때문이다.

const x = 1;

function outerFun() {
	const x = 10;
    innerFun()
}

function innerFun() {
	console.log(x)
}

outerFun()

위의 코드에서 innerFun 함수 안에 콘솔 x 의 값은 전역 변수 x 의 값인 1 이다.
따라서 함수를 어디서 호출했는지가 아니라 함수를 어디서 정의했는지에 따라 상위 스코프를 결정한다!

✨자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프(정적 스코프) 라고 한다.


클로저와 렉시컬 환경

const x = 1;

function outerFun() {
	const x = 10;
    const innerFun = function() {console.log(x)};
    return innerFun;
}

const result = outerFun()
result()
  • outerFun 함수를 호출하면 outerFun 함수는 중첩 함수 innerFun 을 return 하고 생명주기가 마감된다.

  • 즉, outerFun 함수는 return 과 동시에 생명주기가 마감되면서 실행 컨텍스트 스택에서도 제거 된다.

  • 이때 outerFun 안에 선언된 지역변수인 x 또한 생명주기를 마감하게 된다.

  • 따라서 outerFun 함수의 지역 변수 x 는 더는 유효하지 않게 되어 변수 x 에 접근할 수 있는 방법 역시 없다.

  • 그러나 result 함수를 실행했을때의 결괏값은 10 이 나온다. innerFun 에서 어떻게 생명주기가 마감 된 outerFun 의 지역변수에 접근할 수 있을까?

이처럼 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩함수는 이미 생명주기가 종료한 외부 함수의 변수를 참조할 수 있다.
이러한 중첩함수를 클로저 라고 부른다.

✨핵심

위에서 언급했듯이 자바스크립트는 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. innerFun 함수는 outerFun 함수 안에서 정의되었기 때문에 outerFun 함수 내부의 지역 변수인 x 값에 접근이 가능하다.

그런데 outerFun 함수가 이미 생명주기가 끝났는데도 불구하고 innerFun 함수에서 outerFun 의 지역변수인 x 에 접근이 가능한 이유는 innerFun 함수는 자신이 코드가 실행 되기 전 자바스크립트 엔진에 의해 평가될 때 자신이 정의된 위치에 의해 결정된 상위 스코프를 렉시컬 환경 내부 슬롯에 저장하기 때문이다.

outerFun 함수가 생명주기가 끝나 스택에서 제거될때 렉시컬 환경 내부 슬롯까지 소멸되는 것은 아니다. 따라서 innerFun 함수는 자신의 상위 스코프인 outerFun 함수의 렉시컬 환경을 참조하고 있기 때문에 지역변수 x 에 접근이 가능했던 것이다.


그렇다면 클로저는 어디에 사용할까?

클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용한다. 다시말해 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.

let num = 0;

const increase = () => {
	return ++num
}

console.log(increase()) /// 1
console.log(increase()) /// 2
console.log(increase()) /// 3

전역 변수 num 에 1씩 더해주는 increase 함수가 있다.
언뜻 보면 잘 동작하는 것처럼 보이지만, 오류를 발생시킬 가능성을 내표하고 있는 좋지 않은 코드이다.

let num = 0;

const increase = () => {
	return ++num
}

console.log(increase()) /// 1
console.log(increase()) /// 2
num = 100
console.log(increase()) /// 101

왜냐하면 전역 변수 num 에 접근하여 임의로 값을 수정할 수 있기 때문이다. 값을 수정안하면 문제가 없는거 아니야? 싶을 수도 있지만, 개발은 나 혼자서 하는 것이 아니라 협업으로 진행된다. 다른 사람이 모르고 값을 수정할 수 있고 코드가 점점 길어지고 복잡해질수록 실수할 확률이 높아지므로 애초에 오류를 발생시킬 가능성을 제거하는 편이 옳다.

const increase = () => {
	let num = 0;
    return ++num
}

console.log(increase()) /// 1
num = 100  /// num is not defined
console.log(increase()) /// 1

흠..🤔 그럼 변수 num 을 함수 안에다 넣으면 되는거 아니야?? 해서 함수안에 변수를 넣으면 외부에서 변수 num 에 접근은 불가하지만, increase 함수를 호출하면 num = 0 으로 초기화하기 때문에 항상 1 이 출력된다.

어떻게 하면 외부에서 변수 num 에 접근이 불가하면서 호출할때마다 1씩 커지도록 만들 수 있을까? 답은 클로저에 있다.

function increase() {
	let	num = 0;
    function innerFun() {
    	return ++num
     }
     return innerFun
 }
 
const add = increase()

console.log(add()) /// 1
num = 100 /// num is not defined
console.log(add()) /// 2
console.log(add()) /// 3
  • increase 함수 내부에 지역 변수 num 을 선언했다.

  • increase 함수 내부에 innerFun 함수를 선언하고 increase 함수를 호출하면 innerFun 함수를 반환한다.

  • increase 함수가 호출되면 innerFun 을 반환하고 생명주기가 끝난다.

  • increase 함수는 생명주기가 끝났지만, innerFun 은 increase 함수의 렉시컬 환경을 기억하므로 지역 변수인 num 에 접근이 가능하다.

  • 따라서 add 함수를 호출할때마다 기존 값을 유지한채 1씩 더한 값을 얻을 수 있으며, 외부에서 변수 num 의 값을 변경할 수도 없다.


다시 돌아가서..

다시 처음으로 돌아가 MDN 이 정의한 클로저의 정의를 보자. 이제야 아래 글을 보고 이해가 가기 시작한다.

클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다. -MDN 클로저 정의

외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩함수는 이미 생명주기가 끝나 종료된 외부 함수의 변수를 참조할 수 있는데, 이러한 함수를 바로 클로저라고 한다.

생명주기가 끝난 외부함수의 변수에 접근할 수 있는 이유는 바로
자바스크립트 엔진은 함수를 어디에 정의했는지에 따라 상위 스코프를 결정하게 되고, 그때 상위 스코프 내부에 선언된 변수들을 렉시컬 환경 내부 슬롯에 저장하기 때문이다.

따라서 상위 스코프의 렉시컬 환경을 참조하고 있기 때문에 외부 함수의 생명주기가 끝났다 하더라도 외부 함수에 선언된 지역변수에 접근이 가능하다.

이러한 클로저의 특징을 이용해 외부에서 값에 접근하지 못하고 특정 함수에 의해서만 값을 바꿀 수 있도록 하거나 중요한 정보를 은닉할때 사용한다.

클로저를 사용할때 주의할점

클로저의 핵심은 외부 함부의 생명주기가 끝나도 중첩 함수에서 외부 함수의 변수에 접근이 가능하다는 점인데, 이 말은 즉슨 클로저가 외부 스코프에 있는 변수를 계속 기억하고 있기 때문에 필요이상으로 메모리를 소비할 수도 있다는 뜻이 된다.

따라서 클로저는 자바스크립트에서 매우 강력하고 유용한 개념이지만, 적절하게 사용해야 메모리 누수와 성능에 이슈 없이 사용할 수 있다!





도움받은 곳

모던 자바스크립트 Deep Dive - 이웅모 지음

profile
배우고 느낀 걸 기록하는 공간

0개의 댓글