일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 이슈번호자동화
- JavaScript
- vscode
- css instead of js
- 카페인
- 클로저
- storybook
- Husky
- eslint 자동화
- 자바스크립트
- react
- 우아한테크코스
- eslint에러 자동fix
- webpack
- 프로젝트 카페인
- 협업
- 성능 베이스캠프
- prettier 자동화
- CSS
- importOrder
- 유틸함수
- IDL attributes
- chromatic
- string-width
- git hooks
- 클린코드
- 크로마틱
- 프로젝트
- 우테코
- import정리
- Today
- Total
FEB:)DAIN
JavaScript Closure는 무엇일까? 본문
- 클로저를 설명하기에 앞서,
클로저는 하나의 어떤 것을 정의하는 개념이 아니기 때문에 사람마다 말하는 클로저가 다를 수 있다.
1. 함수의 주변 상태(렉시컬 환경)에 대한 참조를 같이 가지고 있는 함수는 클로저다. 즉 (new Function을 제외한) 모든 함수가 클로저라고 할 수 있다. (= 자바스크립트의 클로저)
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
MDN 문서에 따르면, 클로저는 함수의 주변 상태(렉시컬 환경)에 대한 참조를 같이 묶은(봉인한) 함수의 조합이다. 다시 말해 클로저는 내부 함수에서 외부 함수의 스코프에 접근할 수 있게 해 준다. 자바스크립트에서 클로저는 함수 생성 시점에, 함수가 생성될 때마다 만들어진다.
💡 함수 선언문 vs 함수 표현식 둘 중 어떤 걸 쓰느냐에 따라 함수 생성 시점이 다르다.
function(함수 선언문)을 사용하면 런타임 이전에 자바스크립트 엔진이 함수 선언과 동시에
함수 객체를 생성해서 환경 레코드에 기록 → 코드 평가 단계에서 함수 생성
함수 표현식, 화살표 함수 표현식을 사용하면 런타임 시점에 자바스크립트 엔진이 함수를 읽을 때
함수 객체를 생성해 환경 레코드에 기록 → 코드 실행 단계에서 함수 생성
따라서 여기서 말하는 클로저는 상위 렉시컬 환경(함수가 선언된 위치의 렉시컬 환경)을 참조하는 함수를 말한다. 자바스크립트에서 모든 함수는 생성될 때, 렉시컬 환경이 생성되고 이 렉시컬 환경은 상위 렉시컬 환경을 참조한다. 따라서 자바스크립트에서 new Function을 제외한 모든 함수를 클로저라고 할 수도 있는 것이다.
💡 new Function으로 생성한 함수는 상위 렉시컬 환경을 참조하지 못하고, 전역 렉시컬 환경만 참조할 수 있다.
2. 상위 렉시컬 환경이 아닌, 아래의 코드처럼 함수가 선언된 위치의 변수(외부 변수)를 참조하는 것을 클로저라고 하는 경우도 있다. (= 함수형 프로그래밍의 클로저)
function init() {
var name = "Mozilla";
function displayName() {
console.log(name); // breakpoint
}
displayName();
}
init();
이 부분은 디버깅을 통해 눈으로 확인할 수도 있다.
displayName 함수는 생성과 동시에 렉시컬 환경이 생겼고, 이 렉시컬 환경은 외부 렉시컬 환경을 참조한다. 하지만 외부 변수를 참조하고 있지 않으므로 Scope에 Closure가 생기지 않는다. 다시 외부 변수를 참조하고 있는 코드를 확인해보자.
외부 변수인 name을 참조하고 있으므로 Closure가 생긴 걸 확인할 수 있다. 그리고 이 클로저에는 외부 변수인 name이 담겨져있다.
하지만 이런 의문이 생길 수 있다. displayName을 return 하지 않았는데 클로저라고 할 수 있나? 클로저는 상위 실행 컨텍스트가 콜 스택에서 빠졌음에도 불구하고 그 상위 실행 컨텍스트의 변수를 참조할 수 있는 거 아닌가?
3. 클로저는 상위 실행 컨텍스트가 콜 스택에서 빠졌음에도 불구하고 그 상위 실행 컨텍스트의 변수를 참조할 수 있는 것이 클로저의 본질이므로 클로저를 얘기할 때 이 정의를 말하는 경우가 많다.
이제 클로저의 본질에 맞는 클로저 예시 코드를 살펴보자.
function makeFunc() {
const name = "Mozilla";
function displayName() {
console.log(name);
}
return displayName;
}
const myFunc = makeFunc();
myFunc();
이렇게 클로저를 사용하면 makeFunc 함수가 실행되고 콜 스택에서 빠져도 displayName 함수(내부 함수)에서 makeFunc 함수(외부 함수)의 name에 접근할 수 있다.
여기서 잠깐
두 번째, 세 번째에서의 클로저 관점에서 봤을 때 '전역 변수를 함수 안에 사용해도 클로저'라고 착각하기가 쉬운데, 이는 클로저가 아니다. 개발자 도구 > Sources로 들어가 아래 fn1 함수 안 콘솔에 브레이크 포인트를 걸고 reload 시켜보자.
<script>
let a = 1;
function fn1() {
let message = 'hi';
console.log(a, message, message2); // breakpoint
}
function fn2() {
let message2 = 'bye';
fn1();
}
fn2();
</script>
전역에 변수를 선언하면 Script 스코프에 변수가 생긴다. ( a: 1 )
그리고 함수 fn1의 변수는 Local 스코프 안에 생긴다. ( message: 'hi' )
(번외) 자바스크립트는 정적 스코프(= lexical scope)를 채택하고 있기 때문에 fn2에서 fn1을 호출한다고 하더라도, fn1에서 fn2의 message2 변수에 접근할 수 없다. *정적 스코프는 함수가 어디에서 정의되느냐에 따라 유효 범위가 결정되고, 동적 스코프는 어디에서 호출되느냐에 따라서 접근할 수 있는 유효 범위가 정해진다.
하지만 전역 변수와 외부 함수 outer의 변수를 참조하고 있는 inner 함수의 콘솔에 breakpoint를 걸어서 실행시켜 보면, Closure (outer)라는 것이 생기는 걸 볼 수 있다.
<script>
let a = 1;
function outer() {
let b = 2;
function inner() {
console.log('closure', a, b); // breakpoints
}
return inner;
}
const test = outer();
test();
</script>
전역 변수를 사용하고 있는 함수(fn1)를 실행시켰을 때는 클로저가 생기지 않았는데, outer 함수 변수인 b를 사용하고 있는 inner 함수를 실행시켰을 때는 클로저가 생겼다. inner 함수를 실행시켰을 때 b가 inner의 로컬에 없으므로 그 상위(외부 함수)의 로컬에 접근한다.
💡 스코프(Scope)가 계층적으로 연결되어있는 것을 스코프 체인이라고 한다.
자바스크립트 엔진은 스코프 체인을 통해 상위 스코프의 변수를 참조한다.
부모 함수인 outer 함수에 b가 존재하므로 에러 없이 콘솔에 b까지 찍히는 것을 확인할 수 있다.
그리고 전역 변수 참조 함수는 세 번째에서 말하는 클로저에도 부합하지 않는다. 전역 실행 컨텍스트는 가장 마지막에 빠지기 때문이다.
그렇다면 다시 첫 번째로 돌아가서, new Function으로 만든 함수를 전역에서 선언하고 실행하면 이것은 클로저일까? 아니라고 생각한다. new Function은 상위 렉시컬 환경을 참조할 수 없는 성질을 가졌다. 전역에서 선언하고 실행했기 때문에 전역 렉시컬 환경이 상위 렉시컬 환경이 된 것이지, 상위 렉시컬 환경을 참조할 수 있기 때문에 참조한 것이 아니기 때문이다.
클로저의 이론은 그만하고, 클로저의 활용 방법에 대해서 알아보자. (일반적으로 자바스크립트에서 클로저를 사용했다고 했을 때는 세 번째 클로저를 의미한다.) 클로저는 아래의 두 가지 상황을 원할 때 사용하면 된다.
1) 변수가 의도치 않게 변경되지 않도록 은닉
2) 특정 함수에게만 변수 변경을 허용
결론
클로저는 통용되는 단어라 상황에 따라 달라질 수 있다. 일반적으로 말하는 클로저는 세 번째, 즉 상위 실행 컨텍스트가 콜 스택에서 빠졌음에도 불구하고 그 상위 실행 컨텍스트의 변수를 참조할 수 있는 것을 말한다. 변수가 의도치 않게 변경되지 않도록 은닉하거나 특정 함수에게만 변수 변경을 허용하고 싶을 때 이 클로저를 활용할 수 있다.
'코딩 > 공부' 카테고리의 다른 글
[우테코-FE] 성능 최적화하기 (0) | 2023.09.03 |
---|---|
웹팩에서 파비콘 설정하기 (2) | 2023.08.16 |
Object.key/Entries/FromEntries 메서드 사용할 때도 타입이 잘 추론되게 할 수는 없을까? (0) | 2023.08.13 |
깃 커밋 메시지에 이슈 번호를 자동으로 입력할 순 없을까? (2) | 2023.07.04 |