JS_Deep_Dive_chapter24 : 클로저

조용환·2024년 3월 5일
0

JS_Deep_Dive

목록 보기
9/21

chapter 24 : 클로저

JS 고유 개념이 아닌, 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어(예: 하스켈, 리스프, 얼랭, 스칼라 등)에서 사용되는 중요한 특성이다.

클로저 정의 : 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합

렉시컬 스코프

렉시컬 스코프 : JS엔진은 함수를 얻이서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정

함수 객체의 내부 슬롯 [[Environment]]

함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의 된 환경, 즉 상위 스코프의 참조를 저장한다.
함수 객체의 내부 슬롯 [[Environment]]에 저장된 현재 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 바로 상위 스코프
또한 자신이 호출되었을 때 생성될 함수 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장될 참조값이다.
함수 객체는 내부 슬롯 [[Environment]]에 저장한 렉시컬 환경의 참조, 즉 상위 스코프를 자신 이 존재하는 한 기억한다.

클로저와 렉시컬 환경

외부 함수 보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다.
이러한 중첩 함수를 클로저라고 부른다.

  • 함수의 실행 컨텍스트는 그 종료 시 실행 컨텍스트 스택에서 제거되지만 outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다.
  • JS의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저이다. 다만 상위 스코프의 식별자를 차조하지 않는 함수는 클로저가 아니다.
  • 상위 스코프의 식별자를 참조하더라도, 외부 함수보다 중첩 함수가 일찍 소멸되는경우에는 일반적으로 클로저라고 하지 않는다.
function foo() {
    const x =1;
    
    function bar() {
        debugger;
        
        console.log(x);
    }
    bar();
}
foo();

이런식이라면 bar()가 바깥에서 호출하지 않았으므로 클로저 x

function foo() {
    const x =1;
    const y =2;
    function bar() {
        debugger;
        
        console.log(x);
    }
    return bar;
}
const bar = foo();
bar();

여기서는 bar는 상위 스코프의 식별자를 참조하며 더 오래 살아 남으며 클로저이다.
클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적이다.

  • 위의 예시에서는 y는 참조하고 있지 않기 때문에 클로저에 저장하지 않는다. 이렇 듯 참조하는 변수(여기서는 x)를 자유 변수라 한다.
  • 클로저란 "함수가 자유 변수에 대해 닫혀있다"라는 의미이다.

클로저의 활용

클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.
ex)

const increase = (function () {
    let num = 0;

    return function () {
        return ++num;
    }
}());

console.log(increase());
console.log(increase());
console.log(increase());
  • 이처럼 클로저는 상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.

캡슐화와 정보 은닉

캡슐화는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것을 말한다.
이를 정보 은닉이라 한다.
대부분의 객체지향 프로그래밍 언어는 클래스를 정의하고 그 클래스를 구성하는 멤버(프로퍼티와 메서드)에 대하여 public, private, protected 같은 접근 제한자를 선언하여 공개 범위를 한정할 수 있다.
JS는 접근 제한자를 지원하지 않는다. 즉 항상 public 하다.

자주 발생하는 실수

다음은 그 예시이다.

var funcs = [];

for (var i = 0; i <3; i++) {
    funcs[i] = function () {return i};
}

for (var j = 0 ; j <funcs.length ; j++) {
    console.log(funcs[j]());
}
  • var는 함수 스코프이기 때문에 뒤의 return i부분은 반복문이 끝나고 평가 됨. 따라서 그 때의 i값은 3이므로 funcs[0], funcs[1], funcs[2]의 값은 3임
  • funcs[i]에 있는 i는 그 즉시 평가되기 때문에 0, 1, 2 값이 제대로 들어감

바르게 고치면 다음과 같다.

var funcs = [];

for (var i = 0 ; i <3 ; i++) {
    funcs[i] = (function (id) {
        return function () {
            return id;
        };
    }(i));
    
}

for (var j = 0 ; j<funcs.length; j++) {
    console.log(funcs[j]());
}

let을 사용하면 이렇게 할 필요 없이 깔끔하게 해결된다.

profile
practice react, javascript

0개의 댓글