티스토리 뷰

Javascript

자바스크립트 클로져 뿌시기

변기원 2022. 6. 23. 00:20

제가 공부하면서 이해한 내용을 정리했습니다. 잘못된 내용이 있으면 바로 수정하겠습니다. 언제나 가르침 주시면 감사하겠습니다!

이 글의 예제는 Younho Choo님의 블로그를 참고했습니다. 링크는 아래에 있고 좋은 글이 많습니다. 감사합니다.

 

클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수를 말한다

 

예??????????????????

이 글이 끝나면 위 말을 이해하는 것을 목표로 작성해보겠습니다!

 

1. 실행 컨텍스트 이해

저와 비슷한 비전공 주니어 개발자 분들은 실행컨텍스트부터 이해를 해야 합니다. 실행 컨텍스트를 설명하는 글을 엄청 많이 봤는데요, 개념 자체가 한마디로 이해할 수 없는 개념입니다. 가장 비슷한 건 '실행 가능한 자바스크립트 코드블록이 실행되는 환경'인 것 같아요.

일단 실행 컨텍스트란 '실행 가능한 자바스크립트 코드 블록이 실행되는 환경'으로 받아들이시면 되겠습니다.

위 ppt 자료를 보시면 실행컨텍스트가 Stack에서 실행되는 장면을 시각화해봤습니다.

프로세스가 시작되면 일단 전역실행컨텍스트가 기본으로 생성됩니다. 코드를 보시면 전역에 변수 x가 선언되었고 foo함수가 실행되면서 foo실행 컨텍스트가 생겼습니다. foo실행 컨텍스트의 스코프 안에 y변수가 선언되어 있고 bar함수가 실행되었습니다. bar실행 컨텍스트가 생성되었고 bar 실행 컨텍스트 안에서 z변수가 선언되었고 콘솔이 출력되며 함수가 종료됩니다. 그리고 foo실행 컨텍스트도 종료되고 마지막엔 전역 실행 컨텍스트만 남습니다. 이해되시죠? (Stack에 대해 모르시면 검색!)

 

2. 실행컨텍스트 생성과정

그럼 저기 파란색으로 칠해져 있는 실행컨텍스트를 한번 열어볼게요. 어떻게 생긴 녀석인지?

인사이드 자바스크립트 책의 이미지입니다.

자바스크립트에서 함수를 실행하면 해당 블록의 실행 컨텍스트가 생성됩니다. 이미지화하면 위와 같습니다.

1. 활성 객체(변수 객체) 생성: 해당 컨텍스트 실행에 필요한 정보를 담을 객체, 이곳에 모든 정보가 들어갑니다.

2. arguments객체 생성

3. 스코프 정보 생성: 해당 컨텍스트의 유효 범위를 나타냅니다. 상위 실행 컨텍스트부터 유지되며 연결 리스트와 유사한 형태로 만들어집니다. 이것을 스코프 체이닝이라고 합니다. 클로져를 이해하려면 이 부분이 가장 중요합니다. 나중에 이해됩니다. 

4. 변수 생성: 해당 스코프 내의 변수가 선언되고 할당됩니다. 

5. this 바인딩

6. 코드 실행

 

그럼 예제를 보면서 실행컨텍스트에 활성객체와 스코프 정보가 어떻게 생기는지 이해해보겠습니다.

3. 실행컨텍스트 예제

(배치, 디자인 깔끔하지 못한 점 양해 부탁드립니다. ㅠㅠ 조금 더 정리되면 깔끔하게 수정하겠습니다.)

전역에 var1과 var2가 선언되었습니다. 전역실행컨텍스트 생성되었습니다. 안에 보시면 전역 객체라서 arguments는 없네요. 

스코프 정보가 있습니다. 변수도 생성되어 있고 this도 바인딩 되어있습니다. 스코프는 해당 컨텍스트의 유효 범위입니다. 전역 컨텍스트는 당연히 전역에서 유효하니 스코프 정보로 전역 스코프를 가지게 되겠죠? 그래서 var1 var2는 전역에서 사용 가능한 변수가 됩니다.

 

 

똑같이 하나씩 보겠습니다. 전역에 var1 var2변수가 있고 func함수 선언식도 있습니다.

그래서 전역 실행컨텍스트의 활성 객체(변수 객체)에는 위 그림과 같이 [[scope]], var1, var2, func, this가 있습니다.

새로운 문맥의 코드가 실행되면 컨텍스트가 생성되어 stack에 들어가고 제어권이 넘어간다고 말씀드렸죠?

func함수의 실행컨텍스트가 생성되고 변수 객체가 생성됩니다.

그 안에 역시 [[scope]]가 있고 var1, var2가 또 선언되어 있네요.

scope는 선언된 환경의 상위 실행 컨텍스트부터 유지되며 연결 리스트와 유사한 형태로 만들어집니다.

즉, 상위 실행 컨텍스트인 전역 실행 컨텍스트의 scope인 전역객체가 0번째, 현재 실행 컨텍스트의 scope인 func 블록이 1번째로 체이닝 됩니다.

그리고 변수를 참조하게 되면 이 스코프체인 가장 안쪽에서부터 변수를 찾습니다.

지금 func함수 내부의 console.log(var1, var2)는 가장 안쪽에 있는 스코프 체인인 func함수 블록 안에서 먼저 찾습니다.

바로 나오네요. 10, 20 이 출력됩니다.

전역 스코프까지 찾을 필요 없이 바로 찾았으니 바로 출력됩니다. 

 

이번에는 함수가 두 개입니다! 위랑 똑같은 방법으로 하나씩 보겠습니다.

전역 실행컨텍스트에 value변수와 printFunc라는 함수가 선언되어 있습니다.

[[scope]]는 당연히 전역 스코프입니다. 

그리고 printFunc 함수를 만났으니 다시 printFunc 실행 컨텍스트가 생성되겠죠?

그 안에 value라는 변수가 또 선언되어 있습니다. 값은 value2네요.

printFunc 실행 컨텍스트의 [[scope]] 상위 컨텍스트인 [전역스코프 -> printFunc블록스코프] 입니다.

그리고 함수 안에  또 함수가 선언되어 있네요. printValue함수입니다.

printValue 함수를 만났으니 또 printValue 실행 컨텍스트를 만듭니다.

printValue의 [[scope]]는 상위 컨텍스트의 스코프를 포함해서 [전역스코프 -> printFunc블록스코프-> printValue블록스코프] 가 되었습니다.

그리고 드디어 printValue에서 리턴을 만났습니다.

value를 리턴하네요. 변수를 어디서 찾을까요? 가장 가까운 스코프부터 상위로 찾아갑니다.

지금 상황에서는 printValue 블록 스코프에서 가장 먼저 찾겠네요. 근데 value라는 변수가 없습니다.

그래서 바로 상위 스코프인 printFunc블록스코프에서 찾습니다.

찾았습니다. value=value2네요. 그래서 "value2"가 리턴됩니다. 자연스럽죠?

 

이번에도 함수가 두 개인데요, 선언된 위치가 위의 예제랑 다르네요.

함수 안에 함수가 선언된 게 아니라 둘 다 전역에 선언식으로 선언되어 있습니다.

똑같은 방식으로 보겠습니다.

전역 실행컨텍스트가 먼저 생기고 value라는 변수, printValue, printFunc 함수가 선언되어 있습니다.

[[scope]]는 당연히 전역스코프입니다. 그 후에 printFunc이 먼저 호출됩니다.

printFunc 실행 컨텍스트가 생성되고 var value="value2"가 선언됩니다.

[[scope]]는 [전역스코프 -> printFunc블록 스코프]를 가집니다.

그리고 argument로 받은 func함수에 printValue가 들어가서 실행됩니다.

printValue실행 컨텍스트의 [[scope]]는 [전역객체->printValue블록스코프]를 가집니다.

상위에 있는 실행컨텍스트가 전역실행컨텍스트 이기 때문입니다. Stack을 표현한 그림과 비교해서 보시면 좋습니다.

그럼 printValue에서 리턴되는 value는 무엇일까요?

일단 printValue실행 컨텍스트의 스코프에서 찾겠죠? 하지만 변수가 없습니다.

그 바로 상위 스코프인 전역에서 찾습니다. 이번에는 value1이 출력됩니다.

위에 있는 예제와 다르게 함수가 선언되는 위치에 따라 참조할 수 있는 스코프가 달라지고 이번에는 전역 스코프에 선언된 value1이 출력되었습니다.

 

4. 클로저 이해

 

이제 마지막입니다. 드디어 클로저를 만났습니다.

전역에 inner라는 변수와 outerFunc함수가 선언되어 있습니다. [[scope]]는 전역 스코프죠. 너무 쉽죠?

다음에는 outerFunc이 실행되어(함수에 괄호가 붙으면 그 즉시 실행됩니다.) outerFunc 실행 컨텍스트가 생성됩니다.

[[scope]]는 [전역스코프->outerFunc블록스코프]를 가집니다.

변수 x가 선언되어 있고 innerFunc이라는 함수가 표현식으로 선언되어 있습니다.

그리고 이 함수를 리턴해주네요. 그럼 inner변수에 이 함수가 반환됩니다. 

그리고 outerFunc이 리턴을 만났으니 함수가 종료되고 방금 inner변수에 리턴 받은 innerFunc함수가 실행됩니다. 

이 innerFunc이 선언된 장소를 보면 outerFunc안에서 선언이 되어있습니다.

그래서 innerFunc의 스코프는 [전역스코프->outerFunc블록스코프->innerFunc블록스코프] 가 됩니다.

console.log(x)는 당연히 해당 실행 컨텍스트인 innerFunc에서 가장 먼저 찾습니다. 하지만 변수가 없습니다. 그래서 상위 스코프인 outerFunc 스코프에서 찾습니다. 10이 출력됩니다.

Stack을 보면 outerFunc이라는 실행컨택스트는 이미 종료되어 사라졌는데도 스코프 체인이 그대로 남아있어서 변수 x를 참조할 수 있었습니다. 

이처럼 생명주기가 끝난 외부 함수의 변수를 참조하는 함수를 클로져라고 합니다. 

 

 

클로저는 반환된 내부 함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수를 말한다

이제 이해됩니다!

 

 

 

 

 

 

 

참고자료

https://til.younho9.dev/docs/frontend/javascript/5-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80/

 

5. 실행 컨텍스트와 클로저 | 📝 TIL(Today I Learned)

이 글은 고현준, 송형주 님의 인사이드 자바스크립트를참조하여 작성한 글입니다.

til.younho9.dev

https://poiemaweb.com/js-closure

 

Closure | PoiemaWeb

클로저(closure)는 자바스크립트에서 중요한 개념 중 하나로 자바스크립트에 관심을 가지고 있다면 한번쯤은 들어보았을 내용이다. execution context에 대한 사전 지식이 있으면 이해하기 어렵지 않

poiemaweb.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/04   »
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
글 보관함