티스토리 뷰

안녕하세요. 좋아요요정입니다.

오늘은 클로져에 대해서 알아보겠습니다. 사실 저는 제가 작성하는 코드에 클로저를 사용하지 않는다고 생각하고 있었습니다.

이름은 알지만 낯설고 사용해보지 않았다고 생각했었는데 글쎄 리액트의 Hooks이.. 클로저의 특성이라고 합니다!(두둔)

const [count, setCount] = useState(0);

리액트의 함수형 컴포넌트 훅에서는 usdState()를 통해  state를 선언하고, state와 함께 선언되어지는 setCount라는 함수를 호출해 count의 상태를 관리할 수 있습니다.

'클로저를 사용해봤다'라고 생각하니, 클로저가 조금 친숙해지네요. 

 

 

 


 

클로저

클로저는 함수함수가 선언된 어휘적 환경의 조합이라고 합니다.

"함수가 선언된 어휘적 환경"을 이해하기 위해선 자바스크립트에서 변수의 유효범위를 지정하는 Lexical Scope. 어휘적 환경의 이해가 먼저 필요합니다. 

 

 

자바스크립트에서 작성된 코드를 실행하면 무슨 일이 발생할까요?

 

1. 자바스크립트 엔진은 코드를 실행하면 전체 소스코드를 평가하며 실행 컨텍스트를 생성합니다. 

전역 실행 컨텍스트에는 코드를 한줄씩 받아와 실행시키는 쓰레드 환경(Thread of Execution)과 변수 식별자들과 값을 할당하는 메모리(Memory) 영역이 있습니다.

2. 메모리영역에 변수 식별자와 값을 할당합니다.

3. 쓰레드 영역에서 코드를 한줄씩 받아와 실행시킵니다.

4. 코드를 받아와 실행시키던 중 함수를 만나면, 실행되는 함수를 위한 새로운 실행 컨텍스트를 생성하고 1~3의 동작을 반복합니다.

5. 함수의 코드가 종료되면 생성되었던 실행 컨텍스트는 소멸합니다.

6. 전체 소스코드가 종료되면 생성되었던 글로벌 실행컨텍스트는 소멸합니다.

 

 

 

예제를 보겠습니다.

function container() {
    const count = 0;
    console.log(count);
}
    
container();
console.log(count); //외부 환경에서는 ReferenceError!

위의 예제를 실행하게되면 자바스크립트엔진은 어떻게 해석할까요? 

1. 전역 실행 컨텍스트를 생성합니다.

2. 변수 식별자와 값을 할당하고 코드를 한줄씩 실행합니다.

3. container() 함수가 실행되고, container 함수를 위한 실행 컨텍스트가 생성됩니다.

4. container함수 내의 코드가 실행됩니다.

함수를 만나면 함수를 위한 실행컨텍스트를 생성합니다

5. 함수 내 코드가 종료되고 생성되었던 실행컨텍스트가 소멸합니다.

6. 전역 실행 컨텍스트가 마저 실행됩니다.

7. count 변수를 전역 실행 컨텍스트의 메모리에서 찾고 not defined에러가 출력됩니다.

 

 

 

 

container 함수의 코드 내부의 메모리에 선언된 count 변수는 container함수의 소스코드가 종료되고, 실행 컨텍스트와 함께 소멸됩니다.

자바스크립트의 렉시컬 스코프로 인한 동작방식입니다.

다시 클로저의 정의로 돌아가보겠습니다.

 

 

 

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이라고 합니다.

다음 예제를 통해 이때의 함수와 함수가 선언된 어휘적 환경의 조합이 무엇인지 살펴보겠습니다.

function container() {
    const count = 0;
    function printCount() {
        console.log(count);
    }
    return printCount;
}
    
const getCount = container();
getCount();

현재 container()안에는 count 변수와 printCount 함수가 있습니다. printCount는 count를 출력해주는 함수입니다.

container는 printCount함수를 리턴했습니다. 

 

getCount에는 printCount함수가 저장되어있는 힙메모리 영역의 레퍼런스가 저장됩니다.

이때  getCount를 출력하면 어떻게 될까요? 

0이 출력되는 것을 볼 수 있습니다.

container() 실행 컨텍스트가 소멸하면서 count 변수도 함께 소멸되었어야 하는데 출력이 되고 있죠

 

이때 printCount 함수와 그 함수가 선언된 어휘적 환경(container 실행 컨텍스트의 환경)의 조합을 클로저라고 합니다. 

container()지역 환경을 참조하는 printCount가 리턴될 때 클로저가 생성됩니다. 

해당 scope내의 모든 지역변수는 소멸되지 않고 printCount가 참조를 유지할 수 있도록 메모리내에 존재하게 됩니다.

printCount의 내부슬롯 [[Scopes]]에서 클로저를 확인해볼 수 있습니다.

 

 

 

 


mdn의 흥미로운 예제를 하나 더 살펴보겠습니다.(mdn 예제에 a와 b를 더 추가함)

function makeAdder(x) {
    const a = 10; //미사용되는 지역변수
    const b = 20; //사용되는 지역변수
    let y = 1;  //재할당되는 지역변수
    return function(z) {
        y = y + 1;
        return x + b + z;
    };
}

 

makerAdder함수는 x를 매개변수로 받습니다. 

함수 내에는 미사용되는 지역변수 a,  리턴되는 함수에서 사용되는 b, 리턴되는 함수에서 재할당되는 y 가 선언되어 있습니다.

그리고 리턴되는 함수는 z를 매개변수로 받습니다. y값을 재할당하고,  상위 스코프에서 매개변수로 받은 x와  상위 스코프의 지역변수 b와  z를 더한 값을 리턴합니다.

 

 

2개의 예제 함수를 선언해보겠습니다

const add5 = makeAdder(5);

add5 내부 스코프

const add10 = makeAdder(10);

add10 내부 스코프

선언당시 할당된 매개변수 x의 값이 클로저 내에 저장되어 있는 것을 확인할 수 있습니다. 미사용된 지역변수까지 전체적으로 저장되지는 않는 것을 확인해볼 수 있습니다.

 

 

 

add5(10) // 5 + 20 + 10 = 35 
add10(10) // 10 + 20 + 10 = 40

add5(10) 호출 이후 스코프
add10(10) 호출 이후 스코프

동일한 함수를 이용해 생성했지만, 서로 다른 환경의 클로저가 저장되어 있는 것을 확인할 수 있습니다.

클로저의 내부함수가 리턴된 후에 참조가 필요한 환경이 각각의 클로저로 저장되어져 있는 것을 확인할 수 있습니다. (신기하네요)

 

 

 


클로저를 이용해서 프라이빗 메소드 흉내내기

자바 언어는 클래스 메서드에 접근제어자를 설정할 수 있다고 합니다. 

클래스 내 변수, 메소드의 접근제어자가 private으로 설정되었다면 해당 클래스에서만 접근이 가능하다고 합니다. 

이러한 프라이빗한 메서드를 클로저를 이용해서 흉내내는 것이 가능합니다. 

 

간단한 예시를 보겠습니다.

function createCount() {
    let count = 0;
    return { 
        get: function() {
            console.log(count);
        }, 
        increment: function() {
            count ++;
        }
    };
}
    
const count = createCount();

createCount()함수를 이용해 count를 가져오고, ++ 할 수 있는 함수를 생성했습니다.

이때 반환되는 count.get, count.increment  두개의 함수에 공유되는 하나의 어휘적 환경을 생성합니다. 클로저가 생성됩니다.

 

 

count.get() // 0
count.increment()
count.increment()
count.increment()
ccount.get() // 3

이때 반환된 메서드 get, increment를 통해 설정되어있는 함수는 이용이 가능하지만, count의 값을 직접적으로 불러오고 조작하거나 삭제하는 행위를 할 수 없습니다. 

 

 


클로져는 강의 내용적으로는 적어보였는데 이걸 설명할 수 있는 환경에 대한 설명과 사용해보지 않아 낯설다라는 생각에 글쓰기가 더뎠습니다. 그래도 마지막까지 작성했다는 생각에 참 뿌듯합니다.

차근차근 하나씩 화이팅! :) 오늘은 스터디날입니다 ㅎㅎ 매주 공부하고 있어서 뿌듯합니다.

작년의 나보다 성장했다, 새해 목표를 꾸준히 실천하고 있다고 생각되어 힘이 나네요.

오늘도 행복한 코딩라이프되세요~ !

 

JavaScript 스터디 팀 러버덕과 함께 합니다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함