자바스크립트 클로저 2

hojoon·2023년 3월 27일
0

자바스크립트

목록 보기
5/14

자.. 전편에서 정말 길었다.. 클로저가 뭐길래 대체 클로저 클로저 하는지 공부해보려고 했는데 정신을 잃을 뻔 했다. 하지만 정말 뛰어난 여러 개발자분들의 블로그 글 솜씨와 코어자바스크립트, 자바스크립트포이마 저자분께 무한한 감사를 드린다. 덕분에 뺨 때리면서 공부했다..!

이번편은 진짜 클로저 하나만 보려고 태그도 클로저 하나만 걸었다. 시작해보자 클로저!

자 저번편에서 클로저 공부하려고 구글링 딱 시작하니까 이렇게 나왔다.

근데 저번편에서 완벽하게는 아니지만 돌아다니면서 대충 무슨뜻인지 다 읽어보고 왔다.

그럼 포이마웹 클로저부터 시작해보자.(시작만 몇번째냐.....)

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  innerFunc();
}

outerFunc(); // 10

지금부터 클로저 1편을 보고 왔다고 가정하고 설명을 쓰겠습니다.

위에 코드에서 당연히 결과값은 10입니다. innerfunc은 자신을 포함하고 있는 outerfunc 변수 x에 당연히 접근할 수 있다.
근데 바로 x에 접근이 성공한 걸까?? 아니다.

    1. innerfunc은 첫번째로 자신의 스코프에서 x를 검색한다 -> 실패.
    1. 자신을 포함하는 외부함수. 즉, outerfunc에서 변수 x를 검색하고 성공한다.
      이렇게 동작한다. 왜냐면 렉시컬 스코프의 레퍼런스를 저장하고 있는 실행 컨텍스트의 스코프 체인을 자바스크립트 엔진이 검색했기 때문이다.

말이 너무 어렵다. 클로저를 이해할려면 1편에 있는 개념들을 최소한 찾아보고 오자. 내 블로그만으로는 이해가 안간다.. 정말 좋은 개발자분들의 블로그를 참고해서 공부하고 오자.

자 함수를 살짝 바꿔보자.

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  return innerFunc;
}

/**
 *  함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
 *  그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
 */
var inner = outerFunc();
inner(); // 10

outerfunc은 innerfunc을 반환하고 죽었다. 즉 실행컨텍스트 -> 콜스택에서 함수는 제거 되었다. 그렇다면 당연히 변수 x또한 없어졌고 접근할수 있는 방법이 없는게 맞아보인다.
근데?? 이상하게도 동작한다. 정말 이상하다. 진짜 이상하다. 마법이다.

이걸 클로저라고 부른다고 한다.

진짜 멀리 돌아왔다. MDN 클로저 문서이다.

“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.

즉, 클로저는 자신이 선언됐을 때의 렉시컬 스코프를 기억한다. 그래서 자신의 스코프 밖에서 호출되어도 그 렉시컬 스코프를 기억하고 있기 때문에 그 환경에 접근할 수 있느 함수를 말한다.

첨봤을때 어려웠지만 이제 좀 이해가 간다.

조금 더 추가해서 말해보자면 내부함수가 유효한 상태에서 외부함수가 종료되어도 실행컨텍스트 객체 정보를 가지고 있고 스코프 체인을 통해서 참조할 수 있다는것이다.

  • 즉 외부함수가 이미 반환되었어도 이를 필요로 하는 내부함수가 하나 이상 존재하는 경우 계속 유지된다(MDN)
  • 더 신기한것은 변수의 복사본에 접근하는 것이 아니라 실제 변수에 접근한다는 것이다.(변수가 죽지 않고 살아 있다는게 신기하다..)

코드로 한 번 봐보자. 이해한다면 진짜 신기하다. 왜 클로저를 쓰는지 진짜 이해가 간다. 봐보자. 걍 보자

var box = document.querySelector('.box');
var toggleBtn = document.querySelector('.toggle');

var toggle = (function () {
  var isShow = false;

  // ① 클로저를 반환
  return function () {
    box.style.display = isShow ? 'block' : 'none';
    // ③ 상태 변경
    isShow = !isShow;
  };
})();

// ② 이벤트 프로퍼티에 클로저를 할당
toggleBtn.onclick = toggle;
toggle

토글 버튼을 누르면 없어졌다가 나타났다가 하는 그런 코드이다. 진짜 기초적이고 많이 쓰이는 코드이다. 여태까지 let 으로 전역변수를 만들어서 불리언값을 줘서 같은 기능을 만들었었다. 쓰면서 let은 어디서나 접근 가능하고 변경할 수 있기 때문에 오류의 원인이 된다고 알고 있었다. 쓰면서도 let을 쓰는게 맞을까? 이런 기초적인 것도 let을 남발하는데 실제 코드에서도 이걸 쓰는게 맞을까 생각했었는데 클로저를 쓰면 매우 유용하다.

하나만 더 봐보자. 재밌다.

버튼을 누를때마다 숫자를 1씩 증가하는 카운트 버튼을 만들어보자.

var incleaseBtn = document.getElementById('inclease');
var count = document.getElementById('count');

// 카운트 상태를 유지하기 위한 전역 변수
var counter = 0;

function increase() {
  return ++counter;
}

incleaseBtn.onclick = function () {
  count.innerHTML = increase();
};

클로저를 알고 나니까 부끄럽다. 맨날 이렇게 만들었다. 이 코드에서는 문제가 무엇일까?
전역 변수 counter의 값이 반드시 0 또는 숫자여야만 동작할 것이다.
근데 전역변수는 어디서나, 누구나 접근이 가능한 변수이고 의도치 않게 변경될 수 있다. 이는 오류로 이어진다. 그렇기 때문에 counter변수는 increase 함수가 관리하는것이 바람직한 방향이다.

지역 변수로 변경하면?

function increase() {
  // 카운트 상태를 유지하기 위한 지역 변수
  var counter = 0;
  return ++counter;
}

incleaseBtn.onclick = function () {
  count.innerHTML = increase();
};

0에서 1은 변경이 되지만 함수가 호출될 때마다 counter가 0으로 초기화 되기 때문에 언제나 1이 표시된다.

클로저를 써보자

var increase = (function () {
  // 카운트 상태를 유지하기 위한 자유 변수
  var counter = 0;
  // 클로저를 반환
  return function () {
    return ++counter;
  };
}());

incleaseBtn.onclick = function () {
  count.innerHTML = increase();
};

즉시실행함수가 호출되고 변수 increase 카운터를 올려주는 함수가 할당된다. 이 함수는 렉시컬 스코프, 즉 자신이 만들어졌을때의 환경을 기억하는 클로저다. 그렇기 때문에 counter변수를 기억하고 있고 접근이 가능하다. 즉, 내부함수가 counter를 참조하는 한 죽지 않고 살아서 계속 유지된다.

  • 변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다. 상태 변경이나 가변(mutable) 데이터를 피하고 불변성(Immutability)을 지향하는 함수형 프로그래밍에서 부수 효과(Side effect)를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다

그렇다. 클로저 어렵다. 이해하고 나면 재밌다. 공부하고 보면 써보고 싶다. 다음에는 let, var, const에 대해 더 공부해보면서 기록해야겠다. 하지만 잘 쓴다면 좀 간지나는 개발자가 될 것 같다.

갑자기 이런 생각이 들었다. 리액트를 하면서 usestate는 왜 const로 변수를 만들까?? state값을 계속 바꿔주고 여러가지 별거 다하는데 왜 let보다 const일까?? 궁금해서 검색해봤는데 클로저 때문이라고 한다.... 그냥 검색해봤는데 갑자기 클로저가 튀어나왔다. 클로저 키워드 보자마자 일단 도망쳤는데 다음에는 날잡고 공부해봐야겠다.

poimaWeb 감사합니다. 예제는 거의 다 가져왔지만 그래도 정말 열심히 공부했어요.

profile
프론트? 백? 초보 개발자의 기록 공간

0개의 댓글