클로저(Closure)

2022. 9. 22. 23:11프로그래밍 언어/JavaScript

클로저(Closure)

클로저는 '포섭'이란 뜻으로, 독립적인(자유) 변수를 가리키는 함수이며 클로저 안에 정의된 함수는 만들어진 환경을 기억한다.

클로저 = 함수 + 함수를 둘러싼 환경(Lexical Environment)

 

 

예제 1

function outerFunc() {
	let outerVar = "outer";
	function innerFunc() {
		console.log(outerVar);
	}
	return innerFunc;
}

var test = outerFunc();
// test 변수에 innerFunc 함수를 리턴함
// 유효범위의 어휘적 환경을 유지

test();
//리턴된 innerFunc 함수를 실행(outerVar 변수에 접근)

testouterFunc이 실행될 때 생성된 innerFunc 함수의 인스턴스에 대한 참조다.

innerFunc의 인스턴스는 변수 outerVar이 있는 환경에 대한 참조를 기억한다.

따라서 test가 호출될 때 변수 outerVar은 사용할 수 있는 상태로 남게 되고 "outer"가 console.log에 전달된다.

 

이런 현상이 발생하는 이유는 내부 함수 innerFunc이 선언된 렉시컬 환경(Lexical Environment) 때문이다.

JavaScript는 정적 스코프(Lexical Scope)를 따르기 때문이다.

 

 

예제 2

function makeAdder(x) {
	var y = 1;
	return function(z) {
		y = 100;
		return x + y + z;
	}
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);
// 클로저에 x와 y의 환경이 저장됨

console.log(add5(2)); // 105 (x: 5, y: 100, z: 2)
console.log(add10(2)); // 112 (x: 10, y: 100, z: 2)
// 함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산

add5add10은 둘 다 클로저이다. 이들은 같은 함수 본문 정의(makeAdder)를 공유하지만 서로 다른 어휘적 환경(x)을 저장한다.

함수 실행시 add5의 맥락적 환경에서 클로저 내부의 x는 5이지만, add10의 맥락적 환경에서 x는 10이다.

또한 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y값을 100으로 변경한 것을 볼 수 있다. (x도 동일하게 변경 가능하다.)

이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근이 가능하다는 것을 보여주는 포인트이며, 클로저에 단순히 값 형태로 전달되는 것이 아니라는 것을 의미한다.

 

 

클로저를 이용한 모듈 패턴

var counter = (function() {
	var privateCounter = 0;
	function changeBy(val) {
    	privateCounter += val;
    }
    return {
    	increment: function() {
        	changeBy(1);
        },
        decrement: function() {
        	chanbeBy(-1);
        },
        value: function() {
        	return privateCounter;
        }
    };
})();

console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
counter.decrement();
console.log(counter.value()); // 1

counter.increment, counter.decrement, counter.value 세 함수에 의해 공유되는 하나의 어휘적 환경을 만든다.

공유되는 어휘적 환경은 실행되는 익명 함수 안에서 만들어진다. 이 익명 함수는 정의되는 즉시 실행된다.

 

이 어휘적 환경은 두 개의 private item을 포함한다.

하나는 privateCounter라는 변수이고 나머지 하나는 changeBy라는 함수이다.

둘 다 익명 함수 외부에서 접근될 수 없다.

대신에 익명 래퍼에서 반환된 세 개의 퍼블릭 함수를 통해서만 접근되어야 한다.

 

위의 세 가지 퍼블릭 함수는 같은 환경을 공유하는 클로저다.

JavaScript의 어휘적 유효 범위 덕분에 세 함수 각각 privateCounter 변수와 changeBy 함수에 접근할 수 있다.

 

 

반복문에서의 클로저

var i;
for (i = 0; i < 10; i++) {
	setTimeout(function() {
		console.log(i);
	}, 100);
}
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10

0-9까지 출력하는 코드로 예상하기 쉽지만 10만 열 번 출력된다.

setTimeout()에 인자로 넘긴 익명 함수는 모두 0.1초 뒤에 호출될 것이다.

그 0.1초 동안에 반복문이 모두 순회되면서 i값은 10되어버린다.

그 때 익명 함수가 호출되면서 이미 10이 된 i를 참조하여 출력한다.

 

var i;
for (i = 0; i < 10; i++) {
	(function(j) {
		setTimeout(function() {
			console.log(i);
		}, 100);
	})(i);
}
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

클로저를 사용해서 0-9까지 출력하는 코드로 바꿨다. (3, 7라인)

 

for (let i = 0; i < 10; i++) {
	setTimeout(function() {
		console.log(i);
	}, 100);
}
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

혹은 간단하게 let을 사용할 수도 있다.

 

 


Reference

 

 

'프로그래밍 언어 > JavaScript' 카테고리의 다른 글

디바운싱과 쓰로틀링  (0) 2023.08.03
이벤트 전달 방식  (0) 2022.09.26
호이스팅(Hoisting)  (0) 2022.09.22
스코프(Scope)  (0) 2022.09.22
TypeScript 타입 어디까지 지정해줘야 할까  (0) 2022.08.13