출처 : 유튜버 프롱트님
면접 단골 질문 :
*클로져
외부함수
a = 0;
리턴되는내부함수
b;
a+b;
이러면
외부함수는 할일을 마쳐서 종료가 되어야되는데,
지금 내부함수에서 a를 쓰고있기 때문에, 계속 참조를 하고 있기 때문에,
외부함수가 종료를 못하고 있다.
(보관 되어 메모리에 남아있다는 뜻이다.)
어디에 보관? 보관인지 아무튼 메모리에 남아있는데
쉽게 이야기 하자면, 클로저 스코프? 라고 편하게 이야기를 할 수 있다.
즉,
클로저는
함수와 ,
해당 함수가
선언된 당시의 스코프와의 조합으로
이루어진 개념이다.
(예제)
function outer (a) { // 2를 인자로 받고~
return function inner(b) { // 3받고 ~ 즉, 3이 여기 들어감
return a+b; // 그럼 2 더하기 3이 되는것임.
}
}
const foo = outer(2);
foo(3); // 5가 나옴
설명 :
지금 outer 가 실행되고 없어졌...어야 하는데,
개발자 도구가서
소스에
디버거 걸고 보면,
b:3
Closuer (outer)
a: 2
이렇게 나와있다.
즉,
a는 클로져라고 하고있는, outer 함수에 저장이 되어있고, 클로져라는 환경으로 보관?된상태임.
outer 함수는 원래는 실행이 끝 났으나, 안끝나고 메모리에 계속 살아있다는거죠...a라는 데이터를 계속 참조해서 쓸 수있다는,,걸 알수가 있다.
클로져 장점, 왜쓰나?
(응용)
function outer (카운트) {
return function inner() {
return 카운트++;
}
}
const foo = outer(10);
foo() // 10
foo() // 11
foo() // 12
.
.
.
일단 함수만 실행 시켰는데도, outer함수의 실행상태를 유지할 수가 있다. 값도 상태가 계속 바뀐다.
계속 카운트라는 값을 쓰면서 유지하는 모습.
그리고 비공개로 유지가 가능하다.
(카운트라는 값은 내부에 숨겨있기때매, 밖에서는 변경 불가능)
이것은,
객체와도 비슷하다.
즉,
대부분은 소프트웨어는
상태를 유지하고
관리하며 동작한다.
그래서,
보통 객체가 그역활을 해준다.
객체는 상태를 유지하고, 관리할 수 있다.
프로퍼티가 있고,
메소드 들이 있고,
예제를 보면,
class 예제를 보면 ,
--
//카운터 클래스
class Counter {
constructor() {
this._count = 0;
}
increment() {
this._count++;
}
getCount() {
return this._count;
}
}
// 뷰
function view() {
const counter = new Counter();
function clickHandler() {
counter.increment(); // 1
render(counter.getCount()); // 화면 렌더링
}
document.querySelector('button').addEventListner('click', clickHandler);
function render(value) {
document.querySelector('#resultZone').innerHTML = `rendering : ${value}`;
}
}
위의 코드에서
클래스를 삭제하고,
함수로 해보면,
(
클래스는 계속 참조를 하면서 할 수 있는데,
함수는 참조를 하고 싶어도, 함수가 실행되면 종료가 되서,
클로져로 코드를 짜면 함수가 종료가 안되고 계속 참조를 하면서 데이터상태를 변경 시킬 수 있다.
)
--
function Counter() { // 이 함수는 결과적으로, 클릭을 하고 끝나면 종료되는건 맞음. 근데 그 찰나의 클릭되는 순간 참조되는순간은, 종료되지 않고 살아있음. 클로저 떄문.
let count = 0; // 이애가 계속 밑에서 참조를 하기 때문.
return function(renderFn) { //아래 함수를 인자로 전달 받음
count ++; // 숫자 증가
renderFn(count); // 밑에 렌더 함수 실행 + 숫자 받고 //
}
}
// 뷰
function view() {
const increment = Counter();
function clickHandler() {
increment(render); // 여기서는 함수를 실행시키는게 아니고(실행되면 안되니까), 함수 자체를 전달.
}
document.querySelector('button').addEventListner('click', clickHandler);
function render(value) {
document.querySelector('#resultZone').innerHTML = `rendering : ${value}`;
}
}
// 암튼,
리액트도 이런식으로 동작을 하는데,
리액트의 함수컴포넌트, 상태 유지 비법은 클로져이다.
클로저로 처리된다.
(참고)
클로저는 참조 형식이기 때문에
클래스처럼 값을 힙 메모리에 저장을 한다.
(클로저의 실제 명령어는 당연히 코드 영역에, 코드 영역을 가리키는 주소는 힙!! 에 저장,
클로저도 함수일 뿐이기 때문에 실행은 당연히 스택 영역)
클로저는 참조 타입으로 없어지기 전까지 힙 영역에 존재하며
클로저 외부에 있는 변수를 지속적으로 사용하기 때문에 캡처 현상이라는 것이 발생한다.
캡처 현상이란?
클로저를 변수에 할당하거나 클로저를 호출하는 순간,
클로저는 지속적으로 외부 변수를 사용해야 하기 때문에 자신이 참조하는 외부의 변수를 캡처한다.
값 타입에서는 외부적인 요인에 의해 참조하는 값이 변경되는 것을 방지하고자 할 때 사용된다 !!!
그러면 이러한 캡처 리스트를 사용하는 이유는 무엇일까?
값 타입은 값을 복사해서 캡처 - 외부 요인에 의한 값 변경을 방지!
참조 타입은 캡처리스트 내에서 weak, unowned 참조 선언 (강한 참조 해결)
잘 읽었습니다. 좋은 정보 감사드립니다.