자바스크립트 문법/중고급(비동기_프라미스_this)

24장 클로저

자무카 2022. 9. 30.

준비 운동

js 엔진은 함수가 호출될 때,
1) 함수를 호출한 위치가 아니라 정의한 위치(렉시컬 스코프) 에 따라 상위 스코프를 결정하고,
2) 상위 스코프를 내부슬롯에 저장.

클로저? 어디에 써?

변수 직접 접근변경을 막는 것과 같이 상태를 은닉해서, 특정 함수에게만 상태 변경을 허용하기 위해 사용.

클로저(Closure)란?

  • 중첩 함수에서 함수가 종료된 이후에도, 내부 함수가 외부 함수의 변수를 참조하게 만든다는 거.
  • 몬소리야? 외부 함수가 실행 종료된 이후, 외부 함수의 변수에 어떻게 접근 가능한가?

몬소리야. 어떻게?

1) 실행 컨텍스트 2) 렉시컬 스코프 생성/기억

  • 상위 스코프(외부 함수)의 변수를 참조하던 내부 함수를 return  
  • 외부 함수는 .... 죽어도, 유산은 남아있는 거. 어디에? 내부 함수의 슬랏에.
  • 즉, 외부 함수의 종료는 실행 컨텍스트에서 제거되는 것일 뿐, 렉시컬 환경은 남아 있음. --> 중첩 함수에서 기억한 외부 렉시컬 환경을 이용해서, 외부 변수를 기억하고, 접근 가능.

console.dir(함수) 로 프로퍼티 들여다보기. : 내부슬롯에 [[Scope]] 항목에 저장된 Closure 확인 가능

24. 클로저 (DeepDive)


24.1 렉시컬 스코프

24.2 함수 객체의 [[Environment]]

24.3 클로저와 렉시컬 환경

24.4 클로저의 활용( ★★★ )

1) 전역 변수 변경 문제 -> 지역변수로

// 1. increase 함수만 num 변수를 참조하고 변경할 수 있게 만들기(전역변수 num X)
const increase = function () {
    let num = 0; // 카운트 상태 변수
    return ++num;
};
console.log(increase());
console.log(increase());
console.log(increase());

// 전역 변수는 지역 변수로 바꿨지만, num 값이 유지 안된다. ---> 함수가 종료되도, 변수값은 기억하고 있도록...가비지 컬렉터가 못지우도록-->클로저!

2. 클로저 만들기

 전역 변수는 지역 변수로 바꿨지만, num 값이 유지 안된다. ---> 함수가 종료되도, 변수값은 기억하고 있도록...가비지 컬렉터가 못지우도록-->클로저!

// 2. 클로저 만들기
// 클로저는 상위 실행 스코프인 *즉시 실행 함수의 *렉시컬 환경을 기억.
const increase = (function () { // 반환받은 함수: 상위 스코프의 렉시컬 환경을 기억하는 Closure
    let num = 0; // 함수가 종료되도, 내부 함수에서 num 을 참조하고 있어서, 렉시컬 환경은 유지(변수값 기억)
    return function () {
        return ++num; // 외부변수(상위스코프) num 참조
    };
}());
console.log(increase());
console.log(increase());
console.log(increase());


// 즉시 실행 함수는 클로저 반환 -> memo 에 할당
// 클로저는 상위 실행 스코프인 *즉시 실행 함수의 *렉시컬 환경을 기억.
const memo = (function() {
    let text = "메모";
    return function () {
        return (text = text + " 내용 추가");
    };
}());

console.log(memo());
console.log(memo());
console.log(memo());

3. 카운트 감소시키기 추가

// 3. 감산기능도 추가해서, counter 로 업글 
const counter = (function () { // 반환받은 함수: 상위 스코프의 렉시컬 환경을 기억하는 Closure
    let num = 0; // 함수가 종료되도, 내부 함수에서 num 을 참조하고 있어서, 렉시컬 환경은 유지(변수값 기억)
    // return function () {
    //     return ++num; // 외부변수(상위스코프) num 참조
    // };
    return {
        increase() {
            return ++num;
        },
        decrease() {
            return num > 0 ? --num : 0;
        }
    };
}());
console.log(counter.increase());
console.log(counter.increase());

console.log(counter.decrease());
console.log(counter.decrease());

4. 감산 함수를 생성자 함수로 표현

const Counter = (function() {
	let num = 0;
    
    function Counter() {
    } // this.num = 0; 안하는 이유? 프로퍼티는 public. 은닉X
    
    Counter.prototype.increase = function () {
    	return ++num;
    };
    Counter.prototype.decrease = function () {
    	return num > 0 ? --num : 0;
    };
    
    return Counter;
}());

const counter = new Counter();

console.log(counter.increase());
console.log(counter.decrease());

5-1. 함수형 프로그래밍 :  makeCounter 2번 호출 -> 렉시컬 환경 각각 생성. counter 공유 안됨

외부에서 상태 변경X. 불변성 지향. 고차 함수 ( 반환 뿐 아니라, 인수도 함수로 받기. ), 

function makeCounter(aux) {
	let counter = 0;
 	// 클로저 반환   
    return function () {
    	counter = aux(counter); // 인수로 전달받은 보조함수에 상태 변경을 위임.
        return counter;
    };
}

// 보조 함수
function increase(n) {
	return ++n;
}

function decrease(n) {
	return --n;
}

// 보조함수 increase, decrease 를 전달받아 함수를 반환한다.
const increaser = makeCounter(increase);
console.log(increaser());
console.log(increaser());

const decreaser = makeCounter(decrease);
console.log(decreaser());
console.log(decreaser());

6-2 자유 변수 공유

const counter = (function () {
    let counter = 0;  // 카운터 상태를 유지하기 위한 자유 변수
      // 인수로 함수를 전달 받는 클로저 리턴.
      return function (aux) {
        counter = aux(counter);
        return counter;
      };
}());
// 보조 함수
function increase(n) {
    return ++n;
}
function decrease(n) {
    return --n;
}

// 보조 함수 전달로 호출
console.log(counter(increase));
console.log(counter(increase));
// 자유 변수 공유
console.log(counter(decrease));
console.log(counter(decrease));

24.5 캡슐화와 정보 은닉

24.6 자주 발생하는 실수

 

* 코딩앙마 강의

// Closure 함수와 렉시컬 환경의 조합. 함수가 생성될 당시의 환경을 기억.
// 외부 함수의 소멸 이후에도, 외부 함수의 변수에 접근
// 자바스크립트 함수는 렉시컬/정적 스코프(생성되면서 정의된 위치를 기억)

function makeAdder(x){
    return function(y){ // 상위 함수인 makeAdder의 매개변수 x 에 접근 가능
        return x + y;
    }
}

const add3 = makeAdder(3);
console.log(add3(2)); // 5 , add3 함수가 생성된 이후에도 상위함수인 makeAdder의 x 에 접근 가능

const add10 = makeAdder(10);
console.log(add10(2)); // 15
console.log(add3(1)); // 4  상위 함수인 makeAdder(3) 함수가 생성될 당시의 <외부 변수>를 기억해서, 계속 접근 가능

은닉화

function makeCounter() {
	let num = 0;
    
    return function () {
    	return num++;
    };
}

let counter = makeCounter();

console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2

댓글