A closure is the combination of a function and the lexical environment within which that function was declared.
: 클로저는 함수와 그 함수가 선언됐을 때의 lexical 환경의 조합이다.
함수 outerFunc내에서 함수 innerFunc가 선언되고 호출되었다.
function outerFunc() {
let x = 10;
let innerFunc = function() { console.log(x) };
innerFunc();
}
outerFunc(); // 10
x
에 접근할 수 있다.렉시컬 스코핑(Lexical scoping)
lexical scope는 함수를 어디서 호출하는지가 아니라 어디에 선언했는지에 따라 결정된다.
- 함수 innerFunc가 함수 outerFunc 내부에서 선언되었다면 ➡️ 함수 innerFunc의 상위 스코프는 outerFunc이다.
- 함수 innerFunc가 전역에 선언되었다면 ➡️ 함수 innerFunc의 상위 스코프는 전역 스코프가 된다.
따라서, 함수 innerFunc은 함수 outerFunc의 내부에 선언되었으므로, 함수 innerFunc은 자신이 속한 lexical scope(전역
, 함수 outerFunc
, 자신의 스코프
)를 참조할 수 있다.
x
를 찾아가는 과정x
를 검색한다. 찾지 못하면...x
를 검색한다.함수 innerFunc이 변수 x
에 접근하는 4번째 줄에 breakpoint를 걸고, 실행해보면...
➡️ 외부함수 outerFunc에서 변수 x
를 찾은 것을 확인할 수 있다.
breakpoint(중단점)
: 말 그대로 자바스크립트의 실행이 중단되는 코드 내 지점을 의미한다.
중단점을 이용하면 실행이 중지된 시점에 변수가 어떤 값을 담고 있는지 알 수 있다.
이번에는 함수 innerFunc을 함수 outerFunc 내에서 호출하는 것이 아니라 반환하도록 변경했다.
function outerFunc() {
let x = 10;
let innerFunc = function() { console.log(x) };
return innerFunc; // 함수 outerFunc을 호출하면,
} // 내부함수 innerFunc이 반환되고 함수 outerFunc은 종료된다.
let inner = outerFunc();
inner(); // 10 // 외부함수 밖에서 내부함수를 호출했다.
x
또한 더 이상 유효하지 않게 되어 변수 x
에 접근할 수 있는 방법이 없어보이지만,➡️ 이러한 함수를 클로저(Closure)라고 부른다.
A closure is the combination of a function and the lexical environment within which that function was declared.
: 클로저는 함수와 그 함수가 선언됐을 때의 lexical 환경의 조합이다.
위에서 말하는 "함수"란 반환된 내부함수를 의미하고, "그 함수가 선언됐을 때의 lexical 환경"이란 내부함수가 선언됐을 때의 스코프를 의미한다.
즉, 클로저는 반환된 내부함수가 자신이 선언됐을 때의 lexical 환경인 스코프를 기억하여, 자신이 선언됐을 때의 lexical 환경(스코프) 밖에서 호출되어도, 그 lexical 환경(스코프)에 접근할 수 있는 환경을 말한다.
typeof adder(5) // 'function'
adder(5) // function(y) { 5 + y }
y
에 접근 가능한가? 🙅♀️x
에 접근 가능한가? 🙆♂️: 외부함수(adder)의 실행이 끝나더라도, 외부함수 내 변수 x
를 사용할 수 있다.
위의 예시에서, 변수 add5
에는 클로저를 통해 리턴한 함수가 담겨 있다.
add5 = function(y) {
return 5 + y;
}
add5(7); // 12
add5(10); // 15
x
에 계속 담은 채로 남아있다.x
를 사용할 수 있다.const tagMaker = tag => content => `<tag>${content}</tag>`
const divMaker = tagMaker('div');
divMaker('hello') // <div>hello</div>
const anchorMaker = tagMaker('a');
anchorMaker('learn more') // <a>learn more</a>
위의 화살표 함수 tagMaker를 함수 표현식으로 바꾸어 쓰면...
const tagMaker = function(tag) {
return function(content) {
return `<tag>${content}</tag>`
}
} // 외부함수에서 tag, 내부함수에서 content를 입력받아 HTML 태그를 만들어주는 함수
'div'
라는 문자열을 tag
라는 변수에 담아두고 있고,'a'
라는 문자열을 tag
라는 변수에 담아두고 있다.➡️ 클로저는 이처럼 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있게 해준다.
const makeCounter = () => {
let value = 0;
return { // 객체를 리턴, 객체 안에는 함수 increase, decrease, getValue가 있다.
increase: () => {
value = value + 1
},
decrease: () => {
value = value - 1
},
getValue: () => value // value를 리턴한다.
}
}
const counter1 = makeCounter();
console.log(counter1); // {increase: ƒ, decrease: ƒ, getValue: ƒ} // 객체가 반환된다.
counter1
에 할당했다.makeCounter 함수를 바꾸지 않고, 변수
value
에 새롭게 값을 할당할 수 있을까?❌ "외부 스코프에서는 내부 스코프의 변수에 접근할 수 없다"라는 규칙에 의해, 변수
value
는 직접 수정하는 것이 불가능하고, 리턴하는 객체가 제공하는 메소드를 통해서만value
의 값을 조작할 수 있다.
➡️ 이를 캡슐화(정보의 접근 제한)라고 부른다.
왜 캡슐화를 하는걸까?
value
를 감싸지 않았다면, value
는 전역 변수여야 했을 것이다.value
값을 보존하고 있기 때문에, 전역 변수로 따로 만들 필요가 없다.전역 변수가 좋지 않은 이유
전역 변수는 다른 함수나 로직 등에 의해 의도하지 않은 변경(side effect)을 초래하기 때문이다.
➡️ 따라서, 클로저를 통해 불필요한 전역 변수를 줄이고, 스코프를 이용해 값을 보다 안전하게 다룰 수 있다!
: 여러 개의 counter를 만드는 것이 가능하다.
const counter1 = makeCounter();
const counter2 = makeCounter();
counter1.increase();
counter1.increase();
counter1.decrease();
counter1.getValue(); // 1
// getValue 메소드는 외부 함수에 선언된 `value`값을 리턴하는 함수이다.
counter2.decrease();
counter2.decrease();
counter2.decrease();
counter2.getValue(); // -3
value
의 값을 각자 독립적으로 가지게 된다.value
와 counter2에서의 value
는 서로 영향을 끼치지 않고 각각의 값을 보존한다.➡️ 이렇게 함수의 재사용성을 극대화하여, 함수 하나를 완전히 독립적인 부품 형태로 분리하는 것을 모듈화라고 한다.
❔ 학습 후 궁금한 점
- 실행 컨택스트(execution context)는 무엇인지?
- 스코프 체인에 대해서 좀 더 자세히 알아보자.
이 글은 아래 링크를 참고하여 작성한 글입니다.
https://poiemaweb.com/js-closure