javascript/DeepDive

💡 클로져

hw.kr 2023. 2. 1. 17:00

✔️ 렉시컬 스코프

‘’자바스크립트 엔진은 함수를 어디서 호출했는지가 아닌, 함수를 어디서 정의했는지에 따라서 상위 스코프 참조를 결정한다’’

함수의 상위 스코프는 함수가 정의된 위치에서 결정되고, 변하지 않는다(정적 스코프)

실행 컨텍스트 개념을 적용시켜 보면 함수가 호출되어 실행 컨텍스트가 생성되고, 그 함수 렉시컬 환경의

외부 렉시컬 환경에 할당할 참조값을 결정한다는 것과 같은 의미이다.

이 참조값은 함수가 평가되는 시점에서 함수의 위치(환경)에 따라 결정된다, 이것이 렉시컬 스코프

✔️ 자바스크립트 함수 내부 슬롯 [[Environment]]

렉시컬 스코프가 가능하려면, 함수가 호출되는 위치와 상관없이 상위 스코프에 대한 참조값을 기억하고 있어야 한다.

"함수 객체 내부슬롯 [[Environment]] 에 상위 스코프에 대한 참조값을 기억하고,

이 참조값은 현재 실행중인 실행 컨텍스트의 렉시컬 환경이다."

  • 중첩함수가 없는 경우
const x = 1;

function foo(){
    const x = 10;
    bar();
}
function bar(){
    console.log(x);
}

foo(); // 1
bar(); // 1

foo, bar 함수는 모두 전역환경에서 정의되었다.

따라서, 전역 코드가 실행되는 시점에 함수 코드가 평가되어 foo, bar 함수 객체가 생성된다.

전역 코드가 실행되는 시점에는 현재 실행중인 실행컨텍스트는 전역 실행 컨텍스트 이다

따라서 foo bar 함수 객체의 [[Environment]] 내부슬롯에는 전역 실행 컨텍스트의 렉시컬 환경이 할당된다.

 

foo bar 함수가 호출되어 실행컨텍스트 스택에 푸쉬되고 렉시컬 환경이 생성된다.

렉시컬 환경의 외부 렉시컬 환경 참조 에 평가되는 시점에 할당된 [[Environment]] 내부 슬롯의 참조값이 할당된다.

 

💡 함수 렉시컬 환경의 외부 렉시컬 환경 참조 == 평가되는 시점 함수 객체의 [[Environment]]

  • 중첩함수가 있는 경우
const x = 1;

function outer(){
    const x = 10;
    const inner = () => {
        console.log(x);
    }
    return inner;
}

const a = outer();
a(); // 10

inner 함수가 평가되는 시점은 outer 함수가 실행되는 이후이다.

따라서, 현재 실행중인 실행 컨텍스트는 outer 실행 컨텍스트 이고 inner 함수 객체의 [[Environment]] 내부 슬롯에는 outer 렉시컬 환경이 할당된다.

 

🧐 가비지 컬렉터

 

데이터를 참조하고 있는 변수, 식별자가 하나라도 존재하면 그 데이터는 가비지 컬렉터의 대상이 되지 않는다.”

 

outer 함수의 반환값을 식별자 a 에 할당해주고, 함수는 생명주기를 마감한다.

실행 컨텍스트 스택에서 pop 되고, 따라서 지역변수 x 도 생명주기를 마감하고 사라지는것 처럼 보인다..?

하지면 함수 a 를 실행시켜보면 10이 찍힌다.

outer 렉시컬 환경은 inner 함수 객체의 [[Environment]] 내부 슬롯이 여전히 참조하고 있기 때문에, 가비지 컬렉션의 대상이 되지 않고 사라지지 않는다.

따라서 함수 a 렉시컬 환경의 외부 렉시컬 환경 참조는 inner 함수 객체의 [[Environment]] 내부 슬롯과 같고, outer 함수의 렉시컬 환경을 참조할 수 있는 것이다.

 

📌 inner 중첩 함수 == 클로져

  • 외부함수의 식별자를 참조한다.
  • 외부함수보다 생명주기가 길다.

중첩함수가 모두 클로져인 것은 아니다. 2가지 조건을 충족해야 한다.

✔️ 클로져

📌 클로져의 활용

클로져를 언제 사용할 수 있고 왜 사용할까?

  1. 전역변수를 통해서 상태를 변경하게 되면, 의도치 않은 상태 변경이 발생할 위험이 있다.
  2. 모든 스코프에서 어떤 상태에 접근할 수 있게 하는 것은 굉장히 위험하다.

클로져를 사용해서 특정 함수만이 어떤 상태에 접근할 수 있고, 그 상태를 변경할 수 있도록 할 수 있다.

**<!-- desktop/develop/jsStudy/index.hmtl -->**

<body style="background-color: white">
    <h1>클로져 활용해서 박스 상태 변경하기</h1>
    <hr />
    <div class="box" style="width: 100px; height: 100px; background: blue;"></div>
    <button class="button">toggleShow</button>
</body>
**// desktop/develop/jsStudy/study.js**

const box = document.querySelector('.box')
const button = document.querySelector('.button')

const toggle = (() => {
    var isShow = false;

    return () => {
        box.style.display = isShow ? 'block' : 'none';
        isShow = !isShow
    }
})();

button.addEventListener('click', toggle)

toggle 식별자에 할당된 함수만이, box 가 보여지고 사라지는 상태를 변경할 수 있다.

전역변수의 위험성과, 모든 스코프가 한 상태에 접근할 수 있는 위험성을 해결 할 수 있다.

 

📌 함수형 프로그래밍과 클로져

 

고차함수

함수를 인자로 받거나, 함수를 반환하는 함수

const makeCounter = (arg) => {
    let number = 0;

    return () => {
        number = arg(number)
        return number;
    }
}

const increase = (num) => {
    return ++ num;
}
const decrease = (num) => {
    return -- num;
}

const increaser = makeCounter(increase); 1️⃣
console.log(increaser()); // 1
console.log(increaser()); // 2

const decreaser = makeCounter(decrease); 2️⃣
console.log(decreaser()); // -1
console.log(decreaser()); // -2

 

1️⃣ 에서 makeCounter 함수가 호출되면 실행 컨텍스트가 만들어지고, 함수를 반환하고 스택에서 pop 된다.

반환하는 함수는 [[Environment]] 내부 슬롯에 makeCounter 렉시컬 환경을 기억하는 함수이다

따라서 스택에서 pop 되더라도 makeCounter 렉시컬 환경은 사라지지 않는다.

2️⃣ 의 과정은 1️⃣ 과 같다

2개의 클로져 increaser decreaser 은 각각 독립된 렉시컬 환경을 같기 때문에 number 변수도 독립되어 있다.

 

즉시 실행함수로 하나의 number 을 공유하도록 할 수 있다.

const increase = (num) => {
    return ++ num;
}
const decrease = (num) => {
    return -- num;
}

const counter = (() => {
    let number = 0;

    return (arg) => {
        number = arg(number);
        return number;
    }
})();

console.log(counter(increase)) // 1
console.log(counter(increase)) // 2

console.log(counter(decrease)) // 1
console.log(counter(decrease)) // 0

 

'javascript > DeepDive' 카테고리의 다른 글

[Js 딥다이브] 15. let, const  (0) 2022.11.14
[Js 딥다이브] 14. 전역변수의 문제점  (0) 2022.11.10
[Js 딥다이브] 13. 스코프  (0) 2022.11.07
[Js 딥다이브] 12. 함수 (2)  (0) 2022.11.06
[Js 딥다이브] 12. 함수 (1)  (1) 2022.11.05