jiho_bae
Go devlog
jiho_bae
전체 방문자
오늘
어제
  • 분류 전체보기 (158)
    • JavaScript (38)
      • theory (34)
      • vanilla (4)
    • HTML & CSS (2)
    • Browser (3)
    • CS (6)
      • linux (1)
      • shell (2)
      • compiler (2)
    • DS & Algorithm (87)
      • theory (5)
      • basic (7)
      • programmers (30)
      • baekjoon (45)
    • Design Pattern (2)
    • Error (4)
    • Git & Github (4)
    • Tools (1)
    • 부트캠프 (4)
    • Small Tips (2)
    • Java (3)
    • test (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자바스크립트 이벤트 위임
  • 백준 17406 nodeJS
  • 계수정렬 자바스크립트
  • safari Date format NaN
  • fetch 취소하기
  • 13460 javascript nodejs
  • 자바스크립트 sort는 왜 그모양일까
  • 1753 최단경로 javascript
  • 카카오 코딩테스트 양궁대회 nodeJS
  • 자바스크립트 모듈 시스템
  • 자바스크립트 커링
  • 프로그래머스 숫자카드나누기 javascript
  • 억억단을 외우자 javascript
  • 자바스크립트 비동기 마이크로 태스크 큐와 렌더링 과정
  • 자바스크립트 배열의 특수함
  • 리코쳇 로봇 javascript
  • 가사 검색 자바스크립트
  • 깃 이전 커밋에서 새 브랜치 만들기
  • JavaScript
  • 대충만든자판 javascript
  • javascript use strict
  • 자바스크립트 채팅방 스크롤
  • 리액트 프로젝트 디버깅하기
  • safari invalid date error
  • 백준 자바스크립트 입력 템플릿
  • 외벽 점검 javascript
  • 퀵정렬 자바스크립트
  • 병합정렬 자바스크립트
  • 덧칠하기 javascript
  • 25632 소수 부르기 게임

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
jiho_bae

Go devlog

[함수형] 클로저 Closure에 대해 알아보자.
JavaScript/theory

[함수형] 클로저 Closure에 대해 알아보자.

2022. 1. 6. 21:23

클로저

클로저란, 자신이 선언된 렉시컬 환경을 기억하고 참조하는 함수다.

 

MDN에서는,

클로저는 독립적인 (자유) 변수를 가리키는 함수이다.
클로저 안에 정의된 함수는 만들어진 환경을 ‘기억한다’. 라고 한다.

 

클로저를 이해하기 위해선 우선 스코프를 알아야 한다.


스코프

스코프에 대해서는 간단한 예시만을 다룰 예정이다.

 

잘 모른다면,

 

[자바스크립트] 렉시컬 스코프

스코프와  스코프 체인 스코프란, 식별자의 유효 범위를 말한다. 스코프 체인이란, 이 식별자의 유효 범위를 뜻하는 스코프가 계층적으로 연결된 것을 의미한다. 함수에서 스코프를 결정하는

gobae.tistory.com

이전에 정리한 스코프에 대한 글을 먼저 보는게 낫겠다.

 

 

위의 예시에서 함수 abc는 내부에서 변수 a를 활용한다.


함수 스코프와 같은 {} 코드 블럭 안에선 변수 a에 대해 참조를 할 때,
함수 스코프를 먼저 탐색하기 때문에, 매개변수에 해당하는 a를 반환하게 된다.

 

만약 함수 스코프에 지역 변수 a가 없다면?

 

함수가 선언된 스코프를 탐색하며, 발견이 될 때 까지 전역스코프까지 올라가며, 전역에 선언된 a를 발견하면 출력한다.

전역 스코프에도 변수 a가 선언되어 있지 않다면, 레퍼런스 에러 'a is not defined' 에러가 출력 될 것이다.

 

 

두 가지 예를 추가로 이해해보면,

좌측 코드의 스코프를 그림으로 나타내면 우측과 같다.

 

함수 k에서 b를 찾아서 출력해야 하는데, k 함수 스코프에서 b가 존재하지 않으므로 k 함수가 선언된 if 스코프로 올라간다.

if 스코프에 있는 b=3 값을 출력한다.

 

 

이 예시에서는, k 함수 스코프에서 b가 존재하지 않으므로 k 함수가 선언된 if 스코프로 올라간다.


if 스코프에 b가 존재하지 않으므로, if 스코프가 선언된 전역 스코프로 올라간다.
전역 스코프에 b가 선언되어 있으므로, b=2 값을 출력한다.

 

 

엄밀히 말하면, const, let 키워드와 var 키워드의 스코프는 다르지만
최근 문법에서는 변수 선언 시 var는 지양되는 편이다.

 

그래서, const,let의 블록레벨스코프를 기준으로 생각해보자.


클로저의 특징

1. 이미 실행이 끝난 스코프를 참조한다.

 

실행이 끝난 함수의 스코프는, 더이상 참조가 되지 않는다면 가비지컬렉터에 의해 정리된다.
그러나 클로저의 개념에서는 내부 함수가 해당 스코프를 기억하므로, 실행이 끝난 스코프도 계속 참조되어 사라지지 않는다.

 

2. 1의 특징을 활용한다면 은닉화가 가능하다.

 

Private 변수의 예시를 생각하면 되겠다.

 

클로저를 활용한다면 외부에서 상위 환경에 선언한 변수에 직접 접근할 수 없다.

만약 이 변수에 대한 제어를 하고 싶다면, 내부 함수에 get이나 set 등의 인터페이스를 추가해줘야 한다.

 

3. 재사용이 가능하다.

 

클로저를 이용하면 같은 함수 정의를 공유하면서도, 렉시컬 환경(상위 스코프)가 다른 함수를 생성할 수 있다.

클로저를 생성해주는 함수를 실행시키고 그 반환 값을 각각의 변수에 할당하면 끝이다.

 

동일한 기능을 하는 통장을 발행해주는 은행을 예로 들면
계좌1, 계좌2, 계좌3 선언할 수 있고 각각 독립된 스코프를 가지므로 독립된 데이터(잔고)를 가지도록 할 수 있다.


클로저의 예시

내부 함수에서 외부 함수의 스코프에 접근한다.

 

여기서 외부 함수의 스코프란, 렉시컬 스코프를 의미하며 아래의 코드에 나타나 있다.

    const outerFunc = () => {
      const name = 'Tom';
      const innerFunc = () => console.log('my name is ' + name);
      return innerFunc;
    }

    const inner = outerFunc(); // my name is Tom
    inner(2);

기본적으로, 함수 안에 있는 지역변수들은 그 함수가 처리되는 동안에만 존재하고 사라지므로,
outerFunc 함수의 name 변수 또한 사라질 것이라고 예상하는 것이 일반적이다.

 

위 코드에서는 outerFunc 함수의 실행 결과로 inner 함수를 반환 받게 되는데,
inner 함수가 존재하는 한, inner함수가 outerFunc 함수의 스코프에 있는 name을 참조하기 때문에,
outerFunc 함수의 스코프에 있는 name 변수는 사라지지 않게 된다.

 

주절주절 얘기했지만, 결국 inner 함수가 존재하는 한 outer 함수의 스코프에 선언된 변수를 이용할 수 있다는 것이다.


클로저의 예시2

특징3의 계좌의 예시와 같이, 각각 독립된 변수를 다뤄야 한다면

const outerFunc = () => {
    let money = 0;
    let cnt = 0;
    const innerFunc = (amount) => {
        if(amount){
            money += amount;
            cnt++;
        }
        console.log('계좌 잔액 : ', money);
        console.log('계좌 입금 횟수 :', cnt);
    };
    return innerFunc;
}

const account = outerFunc();
account(10000);
account(10000);
// 계좌 잔액 :  10000
// 계좌 입금 횟수 : 1
// 계좌 잔액 :  20000
// 계좌 입금 횟수 : 2

const account2 = outerFunc();
account2(50000);
account2(30000);
// 계좌 잔액 :  50000
// 계좌 입금 횟수 : 1
// 계좌 잔액 :  80000
// 계좌 입금 횟수 : 2

 

 

이와 같이 선언하면 되겠다.

 

여기서 account, account2는 둘 다 클로저이며, 서로 다른 렉시컬 환경을 가진다.
각각의 변수마다 다른 상위 스코프를 가진다.


클로저의 주의사항

앞선 예시에서 살펴 봤듯이, 이미 실행이 끝난 외부함수의 스코프가 내부함수에 의해 참조되어야 하므로, 사라지지 않고 남아있다.

참조가 더이상 되지 않는 스코프가 가비지컬렉터에 의해 정리가 되는 것이 정상적이다.

그러나 클로저를 이용하면, 이 외부함수의 스코프는 계속 남아있을 것이고 가비지컬렉터가 작동하지 않을 것이다.

 

즉, 클로저의 남용은 결국 메모리 문제를 일으킬 수도 있다.

 

따라서 클로저의 장단점을 파악하여 적재적소에 사용해야 하며,
라이프사이클이 끝난 함수는 참조를 제거해서 메모리가 회수될 수 있도록 돕는 것이 좋겠다.


참고 사이트

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures
https://poiemaweb.com/js-scope

https://hanamon.kr/javascript-클로저/

 

 

 

저작자표시 (새창열림)

'JavaScript > theory' 카테고리의 다른 글

[자바스크립트] 일반 객체 다루기  (0) 2022.01.11
[자바스크립트] 고차함수와 배열 내장 메서드  (0) 2022.01.06
[객체] 자바스크립트에서 객체를 생성하는 다양한 방법  (0) 2022.01.04
[자바스크립트] 비동기 작업의 동기 처리를 위한 Async/Await  (0) 2021.08.22
[자바스크립트] 마이크로 태스크 큐와 프로미스  (0) 2021.08.22
    'JavaScript/theory' 카테고리의 다른 글
    • [자바스크립트] 일반 객체 다루기
    • [자바스크립트] 고차함수와 배열 내장 메서드
    • [객체] 자바스크립트에서 객체를 생성하는 다양한 방법
    • [자바스크립트] 비동기 작업의 동기 처리를 위한 Async/Await
    jiho_bae
    jiho_bae
    하루에 한 걸음씩

    티스토리툴바