본문 바로가기

프론트엔드

Closure 클로져

Closure클로저란?


클로저란 함수가 속한 렉시컬 스코프를 기억하여 함수가 렉시컬 스코프 에서 실행될 떄도 그 스코프에 접근할 수 있게 하는 기능을 말한다.

function outer() {
	var a = 2;
	
	function inner () {
		//현재 a를 참조하고 있음
		console.log('inner함수실행! a는? ', a); //함수가 속한 렉시컬 스코프 기억 (outer에서 a를 찾아서 기억)
	} //결국 inner가 클로져임

	return inner;
}

var test = outer(); // 여기서 콘솔을 찍어보면 function inner()가 담겨있음
test(); //inner()가 실행되면서 기억된 a였던 2가 찍힘 
inner함수실행! a는? 2

여기서 GC(Garbage Collector)가 outer()의 참조를 없앨거같지만, 내부함수인 inner()가 해당 스코프의 변수인 a를 참조하고 있기 때문에 없애지 않는다.

따라서 스코프 외부에서 inner가 실행되어도 해당 스코프를 기억하기 때문에 2를 출력하게 된다.
즉, **여기서 클로저는 inner()**가 되며 func에 담겨서 렉시컬 스코프도 기억하고 밖에서도 실행된다.

 

 

‘반복문 클로저’ 1 2 3 4 찍어보기


function func() {
	for (var i = 1; i < 5; i++) {
		setTimeout(function () {  // 1번
			// 3번
			console.log('setTimeout', i); // 4번
		}, i * 500);

     console.log(i, 'i'); // 2번
	}
}
func();
  1. setTimout()이므로 task queue에 쌓이고 기다림
  2. 그사이 반복문 먼저 돌음
  3. 이벤트 루프가 감시하고 있다가 콜스택이 빌때 콜백함수를 꺼내와 실행
  4. 이때 i를 찾으려고 상위 스코프로 가서 i를 찾음. 하지만 이미 5까지 증가했기 때문에 5가 담김
  5. 이때 클로저는 setTimeout이 됨

 


반복문 클로저 해결하기

1. 새로운 함수 스코프로 해결하기 (function () {})

function solution1() {
	for (var i = 1; i < 5; i++) {
		(
			function(j) {
				setTimeout(function () {
					console.log('setTimeout', j);
				}, j * 500);
			}
		)(i);	
	}
}

solution1(); // 1 2 3 4

setTimeout() 을 IIFE(즉시실행함수 표현식)으로 감싸게 되면, 새로운 스코프를 형성하여 나중에 콜백함수가 j를 참조할때에 그 시점의 i값을 가지기 때문에 1 2 3 4 가 찍히게 된다.

2. 블록 스코프로 해결하기

function solution2() {
	for (let i = 1; i < 5; i++) {
		setTimeout(function () {
			console.log('setTimeout', i);
		}, i * 500);
	}
}

solution2(); // 1 2 3 4

let은 블록 스코프이기 때문에 블록을 벗어나지 못한다. 따라서 매 반복마다 새로운 let으로 선언된 i이기 때문에 반복이 끝난 이후의 값으로 초기화가 된다.
즉, setTimeout()의 클로저인 콜백함수가 i를 참조하기 위해 상위 스코프를 검색할 때마다 새로 초기화된 i를 참조하기 때문에 1 2 3 4 가 찍히는 것이다.

반응형

'프론트엔드' 카테고리의 다른 글