24장. 클로저

유준상·2022년 2월 11일
1

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

* 함수가 선언된 렉시컬 환경

const x = 1;
function outerFunc() {
    const x = 10;
    function innerFunc() {
        console.log(x); // 10
    }
    innerFunc();
}
outerFunc();

 innerFunc 함수는 outerFunc 함수 내부에서 중첩된 함수이다. 따라서, 외부 함수 outerFunc의 x 변수에 접근이 가능하다.
--> 중첩되지 않는다면 outerFunc 함수는 전역에서 선언된 변수를 참조한다.

자바스크립트가 렉시컬 스코프를 따르는 프로그래밍 언어이기 때문

  • 렉시컬 스코프

    --> 자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디서 정의했는지에 따라 상위 스코프를 결정 (정적 스코프)
    ex) 전역에서 선언된 전역 함수의 상위 스코프는 전역

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

    --> 자신이 정의된 환경, 상위 스코프의 참조를 저장 -> 외부 렉시컬 환경에 대한 참조

  • 클로저와 렉시컬 환경

    const x = 1;
    function outer() {
       const x = 10;
       const inner = function () { console.log(x); };
       return inner;
    }
    const innerFunc = outer(); // outer 함수 호출하면 inner 함수 반환
    // 이는 중첩 함수가 외부 함수보다 더 오래 유지된다는 뜻이다.
    innerFunc(); // 10 --> 외부값 참조

     클로저: 외부 함수보다 더 오래 유지되는 중첩 함수 -> 생명 주기가 종료된 외부 함수의 변수를 참조할 수 있다.
    실행 컨텍스트에서 제거된다고 해서 렉시컬 환경까지 소멸하는 것은 아니다!!
    --> 가비지 컬렉터는 누군가가 참조하고 있는 메모리 공간을 함부로 해제하지 않는다.
    * 이론적으로 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 클로저이다.
    But, 상위 스코프의 어떤 식별자도 참조하지 않으면 클로저가 아니다.

    클로저 조건

    1) 상위 스코프의 식별자를 참조
    2) 외부 함수보다 생명 주기가 길다.

    function foo() {
       const x = 1;
       function bar() {
           debugger;
           console.log(x); // foo 함수의 변수 x 참조 (조건 1)
       }
       bar();
    }
    foo(); // foo 함수 호출 시 bar 함수 반환 x --> 생명 주기 짧다 (조건 2 x)
    function foo() {
       const x = 1;
       const y = 2;
       function bar() {
           debugger;
           console.log(x); // 상위 스코프 변수 참조 (조건 1)
       }
       return bar;
    }
    const bar = foo();
    bar(); // 호출 시 bar 함수 반환 --> 생명 주기 더 김 (조건 2)

    자유변수: 클로저에 참조되는 상위 스코프의 변수

  • 클로저의 활용

    --> 클로저는 상태를 안전하게 변경하고 유지하기 위해 사용
    1) 상태를 안전하게 은닉
    2) 특정 함수에게만 상태 변경 허용

    let num = 0;
    const increase = function () {
       return ++num;
    };
    console.log(increase()); // 1
    console.log(increase()); // 2
    console.log(increase()); // 3

     위 코드의 문제점
    카운트 변수가 전역 변수를 통해 관리 --> 언제든지 누구나 접근 및 변경이 가능

    해결 1. 카운트 변수를 전역에서 지역 변수로 변경
    --> 함수가 호출될 때마다 초기화되므로 카운트 변수 값이 변경되지 않음.
    해결 2. 클로저 사용

    const increase = (function () { // 즉시 실행 함수
       let num = 0;
       return function () {
           return ++num; // increase 함수의 변수 참조, 생명 주기 더 김 -> 클로저
       };
    }());
    const counter = (function () { // 즉시 실행 함수
       let num = 0;
       return { // 객체 리터럴을 반환 --> 객체 리터럴은 스코프 생성 x --> 메서드들의 상위 스코프는 외부 함수
           increase() {
               return ++num;
           },
           decrease() {
               return num > 0 ? --num : 0;
           }
       };
    }());
    console.log(counter.increase()); // 1
    console.log(counter.increase()); // 2
    console.log(counter.decrease()); // 1
    console.log(counter.decrease()); // 0
  • 캡슐화와 정보 은닉

     캡슐화: 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것
     정보은닉: 객체의 특정 프로퍼티나 메서드를 감추는 것

    * 자바스크립트는 접근 제한자를 제공하지 않는다. 자바스크립트의 객체의 모든 프로퍼티와 메서드는 기본적으로 외부에 공개되어 있다. (public)

    function Person(name, age) {
       this.name = name; // public
       let _age = age; // private (지역 변수)
       this.sayHi = function () {
           console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
       }; // 객체 생성될 때마다 중복 생성 --> 프로토타입 메서드로 변경하기
    }
    const me = new Person('Lee', 20);
    me.sayHi; // Hi! My name is Lee. I am 20.
    console.log(me.name); // Lee
    console.log(me._age); // undefined

    --> 메서드의 생성 중복을 방지하기 위해서 프로토타입 메서드로 바꾸면 let 변수를 참조할 수 없는 문제가 발생한다. -> 즉시 실행 함수로 생성자 함수와 메서드 모으기

    * 생성자 함수가 여러 개의 인스턴스를 생성할 경우 private 변수의 상태가 유지되지 않는다.
    --> 클래스에 private 필드 정의하는 새로운 표준 제안

  • 자주 발생하는 실수

    var funcs = [];
    for (var i = 0; i < 3; i++) { // i 는 전역 변수**
       funcs[i] = function () { return i };
    }
    for (var j = 0; j < funcs.length; j++) {
       console.log(funcs[j]());
    }

    --> 클로저를 사용하여 바르게 동작하도록 변경

    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]());
    }

    --> var 키워드로 선언한 변수는 전역 변수가 된다 -> let 키워드를 사용
    --> let 키워드를 사용하면 반복 실행될 때마다 독립적인 렉시컬 환경 생성

profile
웹사이트 개발과 신발을 좋아하는 학생입니다.

0개의 댓글

관련 채용 정보