클로저의 개념과 사례를 알아보자!
그리고 왜 클로저가 필요한지 알아보자!
클로저란❓
- 함수가 속한 렉시컬 스코프를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때에도 이 스코프에 접근할 수 있게 하는 기능을 뜻한다.
- 렉시컬 스코프란 개발자가 코드를 작성할 때 함수를 어디에 선언하는지에 따라 정의되는 스코프를 말한다.
- 클로저(closure)는 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것을 가르킨다.
- 클로저는 상위 스코프를 기억하는 것이다.
🤔 코드로 살펴보자
function outer(){ var title = 'coding everybody'; function inner(){ console.log(title); } inner(); } outer(); //coding everybody
- 함수 outer의 내부에는 함수 inner가 정의 되어 있다. 함수 inner를 내부 함수라고 한다.
- 내부함수는 외부함수의 지역변수에 접근할 수 있다. 결과는 coding everybody이다.
이것이 클로저인가!? 기술적으로 그렇다!
- 함수 inner()는 outer() 스코프에 대한 클로저를 가진다.
- 그러나 위의 예제를 설명하는 가장 정확한 방식은 렉시컬 스코프 검색 규칙에 따라 설명하는 것이고, 이 규칙은 클로저의 일부일 뿐이다!
😡 정체를 드러낼 수 있는 코드는 없어?
function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); //2
- 함수 bar는 함수가 선언된 렉시컬 스코프 밖에서 실행됐다.
- 일반적으로 가비지 콜렉터에 의해 사용하지 않는 함수는 메모리에 저장되지 않기 떄문에 foo()의 내부 스코프는 사라졌어야 한다.
- 하지만 클로저가 이를 허용하지 않는다!
- 선언된 위치 덕에 bar()는 foo() 스코프에 대한 렉시컬 스코프 클로저를 가지고, foo()는 bar()가 나중에 참조할 수 있도록 스코프를 살려둔다.
- 즉, bar()는 여전히 해당 스코프에 대한 참조를 가지는데, 그 참조를 바로 클로저라고 부른다.
🧐 반복문으로 보는 클로저
for(var i=1; i<=5; i++) { setTimeout(function timer() { console.log(i); }, i*1000); }
- 이 코드의 목적은 1부터 5까지 출력하는 것이다.
- 하지만 6이 출력되는 것을 볼 수 있다.
- 반복문이 끝나는 조건은 i가 5보다 클 때이다.
- timeout 함수 콜백은 반복문이 끝나고 작동하기 때문에 반복문이 끝났을 때의 i 값을 반영한 것이다.
이렇게 고쳐보자!
for(var i=1; i<=5; i++) { (function() { var j = i; setTimeout(function timer() { console.log(j); }, j*1000) })(); }
- 즉시실행함수(IIFE)는 함수를 정의하고 바로 실행시키면서 스코프를 생성한다.
- 즉시실행함수(IIFE)를 사용하여 반복마다 새로운 스코프를 생성하는 방식으로 tiemout 함수 콜백은 원하는 값이 제대로 저장된 변수를 가진 새 닫힌 스코프를 반복마다 생성해 사용할 수 있다.
- 쉽게 말해, IIFE는 함수가 실행될 때마다 함수 스코프를 생성하고, 이때 각각의 j는 독립적이기 때문에 각각의 값을 가질 수 있는 것이다.
IIFE만 가능하다고 착각하지 말자!
함수 스코프를 생성하는 구문에서는 다 가능하다.
이게 다는 아니다
for(let i=1; i<=5; i++) { setTimeout(function timer() { console.log(i); }, i*1000); }
- 반복문 시작 부분에서 let으로 선언된 변수는 한 번만 선언되는 것이 아니라 반복할 때마다 선언된다.
- 따라서 해당 변수는 편리하게도 반복마다 이전 반복이 끝난 이후의 값으로 초기화 된다.
😍 클로저가 도움될 때
function factory_movie(title){ return { get_title : function (){ return title; }, set_title : function(newTitle){ title = newTitle } } } const ghost = factory_movie('Ghost in the shell'); console.log(ghost.get_title()); //Ghost in the shell ghost.set_title('공각기동대'); console.log(ghost.get_title()); //공각기동대
- 똑같은 외부함수 factory_movie를 공유하고 있는 ghost의 결과는 서로 각각 다르다.
- 그것은 외부함수가 실행될 때마다 새로운 지역변수를 포함하는 클로저가 생성되기 때문에 ghost는 서로 완전히 독립된 객체가 된다.
- 클로저의 이러한 특성을 이용해서 Private한 속성을 사용할 수 있게된다.
- Private 속성은 객체의 외부에서는 접근 할 수 없는 외부에 감춰진 속성이나 메소드를 의미한다. 이를 통해서 객체의 내부에서만 사용해야 하는 값이 노출됨으로서 생길 수 있는 오류를 줄일 수 있다.
참고자료 :
생활코딩 클로저
<YOU DON'T KNOW JS> 스코프 클로저