웹 분야를 공부하다보면 "클로저"라는 단어를 들어보셨을 겁니다.
오늘은 이 한번쯤은 들어본 closure에 대해 정리 해보겠습니다.
클로저(Closure)
JavaScript에서 클로저(Closure)는 이렇게 정의할 수 있습니다.
함수와 그 함수가 선언될 당시의 렉시컬 환경(Lexical Environment)과의 조합
이렇게 정의만 보아선 이해하기 어려울 수 있습니다.
조금더 간단히 설명해 보도록 하겠습니다.
내부함수가 외부함수의 context에 접근할 수 있는 함수
조금 더 이해하기 쉬우신가요?
이 개념을 기억하면서 클로저에 대해 차근차근 알아보도록 합시다.
실행 컨텍스트
먼저 클로저를 이해하기 전 함수의 실행 컨텍스트에 대해 간단히 알아야합니다.
함수는 호출 될 때 함수의 실행 컨텍스트를 생성했다가, 실행이 끝나면 실행컨텍스트가 종료됩니다.
( 이 실행 컨텍스트의 lexical environment엔 함수의 지역변수의 정보, 이 함수의 상위 스코프에 대한 정보가 들어있습니다.)
간단한 예제를 하나 봐보도록 합시다.
const HelloWorld = () => {
const hello = "Helloooo";
return hello;
}
console.log(HelloWorld());
console.log(hello)
보이는대로 위의 함수 호출이 끝난후 실행 컨텍스트가 종료되며, 안의 hello
라는 변수에 아무도 접근할 수 없게 되었습니다.
어떻게 보면 당연한 결과죠.
하지만 이 hello
라는 변수에 접근할 수 없는 것은 아닙니다.
오늘 주제인 closure
를 사용한다면 실행 컨텍스트가 끝난 이후에도 외부에서 이 hello
라는 변수에 대해 접근할 수 있습니다.
const HelloWorld = () => {
const hello = "Helloooo";
console.log(hello)
const sayHello = () => {
return hello;
}
return sayHello;
}
const helloFunction = HelloWorld();
console.log(helloFunction());
HelloWorld
가 마찬가지로 실행 후 종료되었습니다.
마찬가지로 실행 컨텍스트가 종료되었으니hello
라는 변수에는 접근할 수가 없겠죠?
하지만 sayHello
에서는 접근할 수가 있습니다.
이게 클로접니다.
클로져의 특성상sayHello
함수가 선언될 때 그 주변의 lexical enviroment와 함께 번들로 묶였기 때문입니다.
하지만 왜 기존의 규칙을 바꾸면서까지 이 클로저라는 것을 만들었을까요?
유용한 클로저
전역변수
전역변수는 어디서든 접근이 가능하기 때문에 되도록이면 적은게 좋습니다.
하지만 단 1곳에 사용하는데도 전역변수가 필요한 경우가 있는데요, 바로 이런 순간에 사용하는 것이 클로저 입니다.
예시를 하나 살펴보도록 합시다.
const btn = document.querySelector('button')
btn.addEventListener('click',handleClick)
let count = 0
function handleCilck(){
count++
return count
}
위의 예제처럼 클릭할 때마다 count를 세주는 함수가 있습니다.
count를 전역변수로 작성해 줘야지만 값을 추가하는게 가능합니다.
하지만 클로저를 사용하면 전역변수가 아니더라도 counter를 구현할 수 있습니다.
const btn = document.querySelector('button')
btn.addEventListener('click',handleClick())
function handleCilck(){
let count = 0
return function (){
count++
return count
}
}
외부함수(handleClick)의 lexical environment를 참조하는 함수를 btn의 콜백함수로 이용해 구현할 수 있습니다.
코드 재사용성
클로저를 사용하게 되면 비슷한 형태의 코드를 재사용률을 높일 수 있습니다.
const newTag = function(open, close) {
return function(content) {
return open + content + close
}
}
const bold = newTag('<b>', '</b>')
const italic = newTag('<i>', '</i>')
console.log(bold(italic("This is my content!")))
위 코드를 보면 bold, italic같이 여러 태그를 새로운 태그를 만들 수 있는 newTag를 클로저를 이용해 간단하게 구현했습니다.
인자로 open, close, content를 한번에 다 받으면 코드의 가독성이 떨어질 수 있습니다.
하지만 클로저를 통해 구현하면 코드의 가독성, 재사용성까지 챙긴 편한 코드가 됩니다.
착각하기 쉬운 클로저
function outer() {
let name = 'kyle'
if (true) {
let city = 'seoul'
return function inner() {
console.log(city)
}
}
}
위의 코드가 클로저를 사용한 코드일까요?
클로저를 정확하게 파악하지 않았을 때, 마치 함수를 리턴하는 것 자체가 클로저라고 오해하는 경우가 행길 수 있습니다.
클로저는 내부에 선언된 함수가 외부함수의 지역변수를 사용해 줬을 때만 클로저라고 선언됩니다.inner
함수에도 클로저를 사용하고 싶다면 name
변수를 사용해주면 됩니다.
function outer() {
let name = 'kyle'
if (true) {
let city = 'seoul'
return function inner() {
console.log(city)
console.log(name)
};
}
}
마무리
마지막으로 클로저를 다시한번 정리해봅시다.
클로저란 내부함수에서 외부함수의 지역변수를 사용할 때 외부함수의 lexcial environment와 함께 짝을 이루는 것이라고 생각하면 될 것 같습니다.