1.4 클로저
1.4.1 클로저의 정의
- 클로저는 함수와 함수가 선언된 어휘적 환경의 조합
- 어휘적 환경
function add() {
const a = 10
function innerAdd() {
const b = 20
console.log(a + b)
}
innerAdd() // 30
}
add()
🔖 add 함수 내부에 innerAdd가 있고, innerAdd 함수는 내부에서 b 변수를 선언한 뒤, 자신의 함수 외부에 있는 a와 b를 더해서 정상적으로 30을 출력
🔖 a 변수의 유효 범위는 add 전체이고, b의 유효 범위는 innerAdd의 전체
🔖 innerAdd는 add 내부에서 선언돼 있어 a를 사용할 수 있게 된 것
🔖 "선언된 어휘적 환경"이라는 것은, 변수가 코드 내부에서 어디서 선언됐는지를 말하는 것
🔖 호출되는 방식에 따라 동적으로 결정되는 this와는 다르게 코드가 작성된 순간에 정적으로 결정
1.4.2 변수의 유효 범위, 스코프
- 클로저는 함수와 함수가 선언된 어휘적 환경이 결정
- 변수의 유효 범위 : 스코프
전역 스코프
- 전역 레벨에 선언하는 것
- 이 스코프에서 변수를 선언하면 어디서든 호출
함수 스코프
- {} 블록이 스코프 범위를 결정하지 않음
- 가장 가까운 스코프에서 변수가 존재하는지를 확인
1.4.3 클로저의 활용
function outerFunction() {
var x = 'hello'
function innerFunction() {
console.log(x)
}
return innerFunction
}
const innerFunction = outerFunction()
innerFunction() // "hello"
🔖 자바스크립트가 함수 레벨 스코프를 가지고 있고 스코프는 동적으로 결정된다는 사실
클로저의 활용
- 전역 스코프는 어디서든 원하는 값을 꺼내올 수 있다는 장점이자 단점
- 누구든 접근할 수 있고 수정할 수 있기 때문
var counter = 0
function handleChlick() {
counter++
}
🔖 전역 레벨에 선언돼 있어서 누구나 수정 가능
🔖 window.counter를 활용하면 쉽게 해당 변수에 접근
🔖 리액트의 useState의 변수가 전역 레벨에 저장돼 있으면 어떻게 될까?
🔖 누구나 리액트 애플리케이션을 망가뜨림
- 리액트가 관리하는 내부 상태 값은 리액트가 별도로 관리하는 클로저 내부에서만 접근 가능
function Counter() {
var counter = 0
return {
increase: function() {
return ++counter
},
decrease: function() {
return --counter
},
counter: function() {
console.log('counter에 접근!')
return counter
},
}
}
var c = Counter()
console.log(c.increase()) // 1
console.log(c.increase()) // 2
console.log(c.increase()) // 3
console.log(c.decrease()) // 2
console.log(c.counter()) // 2
🔖 counter 변수를 직접적으로 노출하지 않음으로써 사용자가 직접 수정하는 것을 막았음은 물론, 접근하는 경우를 제한해 로그를 남기는 등의 부차적인 작업 수행 가능
🔖 counter 변수의 업데이트를 increase와 decrease로 제한해 무분별하게 변경되는 것을 막음
- 클로저를 활용하면 전역 스코프의 사용을 막고, 개발자가 원하는 정보만 개발자가 원하는 방향으로 노출시킬 수 있음
리액트에서의 클로저
- 리액트 함수 컴포넌트의 훅에서 클로저는 어떻게 사용?
- useState
function Component() {
const [state, setState] = userState()
function handleClick() {
// useState 호출은 위에서 끝났지만,
// setState는 계속 내부의 최신값(prev)을 알고 있다.
// 이는 클로저를 활용했기 때문에 가능하다.
setState((prev) => prev + 1)
}
}
1.4.4 주의할 점
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
🔖 의도는 0부터 시작해 1초 간격으로 console.log로 0,1,2,3,4를 차례대로 출력하는 것
🔖 실제로는 0,1,2,3,4초 뒤에 5만 출력됨
왜? i가 전역 변수로 작동하기 때문
🔖 자바스트립트는 기본적으로 함수 레벨 스코프를 따르고 있기 때문에 var는 for 문의 존재와 상관없이 해당 구문이 선언된 함수 레벨 스코프를 바라보고 있으므로 함수 내부 실행이 아니라면 전역 스코프에 var i가 등록돼 있을 것
- 함수 레벨 스코프가 아닌 블록 레벨 스코프를 갖는 let으로 수정하는 것
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
🔖 let은 기본적으로 블록 레벨 스코프를 가지게 되므로 let i가 for 문을 순회하면서 각각의 스코프를 갖게 됨
- 클로저를 제대로 활용하는 것
for ( var i = 0; i < 5; i++) {
setTimeout(
(function (sec) {
return function () {
console.log(sec)
}
})(i),
i * 1000,
)
}
🔖 즉시 실행 함수는 i를 인수로 받는데, 이 함수 내부에서는 이를 sec이라고 하는 인수에 저장해 두었다가 setTimeout의 콜백 함수에 넘기게 됨
🔖 setTimeout의 콜백 함수가 바라보는 클로저는 즉시 실행 익명 함수가 됨
🔖 즉시 실행 익명 함수는 각 for 문마다 생성되고 실행되기를 반복
🔖 각각의 함수는 고유한 스코프, 즉 고유한 sec을 가지게 되므로 올바르게 실행할 수 있음
📢 "함수와 함수가 선언된 어휘적 환경의 조합"을 주의 깊게 살펴봐야 클로저를 제대로 활용
- 클로저를 사용하는 데는 비용이 든다는 것
- 클로저는 생성될 때마다 그 선언적 환경을 기억해야 하므로 추가로 비용 발생
- 클로저의 개념, 즉 외부 함수를 기억하고 이를 내부 함수에서 가져다 쓰는 메커니즘은 성능에 영향을 미침
- 클로저 사용을 적절한 스코프로 가둬두지 않는다면 성능에 악영향을 미침
1.4.4 정리
- 클로저는 함수형 프로그래밍의 중요한 개념, 부수 효과가 없고 순수해야 한다는 목적을 달성하기 위해 적극적으로 사용되는 개념
- 클로저는 공짜로 쓸 수 있는 것이 아님
'읽은 책들 > 모던 리액트 DeepDive' 카테고리의 다른 글
[모던 리액트 Deep Dive] 1장. 리액트 개발을 위해 꼭 알아야 할 자바스크립트 (0) | 2024.11.07 |
---|---|
[모던 리액트 Deep Dive] 1장. 리액트 개발을 위해 꼭 알아야 할 자바스크립트 (0) | 2024.11.04 |
[모던 리액트 Deep Dive] 1장. 리액트 개발을 위해 꼭 알아야 할 자바스크립트 (3) | 2024.10.31 |
[모던 리액트 Deep Dive] 1장. 리액트 개발을 위해 꼭 알아야 할 자바스크립트 (1) | 2024.10.30 |
[모던 리액트 Deep Dive] 1장. 리액트 개발을 위해 꼭 알아야 할 자바스크립트 (0) | 2024.10.29 |