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 변수에 접근)
test
는 outerFunc
이 실행될 때 생성된 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값에 접근하여 값을 계산
add5
와 add10
은 둘 다 클로저이다. 이들은 같은 함수 본문 정의(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 |