[JavaScript] 클로저

Hannahhh·2022년 7월 11일
0

JavaScript

목록 보기
15/47

🔍 클로저

함수와 함수가 선언된 어휘적 환경의 조합을 의미한다.

자바스크립트는 함수가 호출되는 환경과 별개로 기존에 선언되어 있던 환경(어휘적 환경)을 기준으로 변수를 조회하려고 한다. 이런 이유로, 외부 함수의 변수에 접근할 수 있는 내부 함수클로저 함수라고 한다.


👀 클로저 함수의 특징


✔ 함수를 리턴하는 함수

함수와 함수가 선언된 형태로, 리턴 값이 함수의 형태이다.


//화살표 함수
const adder = x => y => x + y;
adder(5)(7);       // 12
typeof adder(5)    // 'function'
adder(5)           // y => x + y


// 위의 코드와 동일하게 작동하는 코드
// 클로저 함수의 기본 형태
const adder = function (x){
  return function(y){
    return x + y;
  }
}

✔ 외부 함수의 변수에 접근 가능한 내부 함수

클로저는 리턴하는 함수에 의해 스코프(변수의 접근 범위)가 구분된다. 핵심은 스코프를 이용해서 변수의 접근 범위를 닫는 데에 있다.


아래 예제를 보면, 변수 x와 y가 선언된 곳이 다르다. x가 선언된 함수는 바깥쪽에 있으니 외부함수, y가 선언된 함수는 안쪽에 있으니 내부함수라고 부르며 이 클로저 함수는 스코프가 분리되어 있다.

// 클로저 함수의 기본 형태
const adder = function (x){  // 외부 함수
  return function(y){        // 내부 함수
    return x + y;
  }
}
  • 바깥 스코프에서 안쪽 스코프로의 접근이 불가능하므로, 외부 함수는 y에 접근이 불가능하다.

  • 안쪽 스코프는 바깥 스코프에서 선언된 변수에 접근이 가능하므로, 내부함수는 x에 접근이 가능하다.



👀 클로저 함수의 장점


✔ 데이터를 보존하는 함수

일반적인 함수는 실행이 끝나면 함수 내부의 변수를 사용할 수 없지만 클로저는 외부 함수 내 변수가 메모리 상에 저장되기 때문에 외부 함수의 실행이 끝나도 외부함수 내 변수를 사용할 수 있다.

//Example1
const adder = function (x){  // 외부 함수, 변수 x
  return function(y){        // 내부 함수, 변수 y
    return x + y;
  }
}

const add5 = adder(5);   
add5(7);     //12,  (5+7)
add5(10);    //15,  (5+10)

변수 add5에는 클로저를 통해 리턴한 함수가 담겨 있다.
외부함수의 실행이 끝났음에도 adder 함수에서 인자로 넘긴 5라는 값을 x변수에 계속 담은 채로 남아있으며, add5(7), add5(10)의 예시처럼 x 값(5)을 계속 사용할 수 있다.


아래는 클로저를 이용해 HTML문자열을 만드는 예제로, divMaker함수는 div라는 문자열을 tag라는 변수에 담아두고, anchorMaker 함수는 a라는 문자열을 tag에 담아두고 있다. 이렇게 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있다.

//Example2 
const tagMaker = tag => content => `<${tag}>${content}</${tag}>`

const divMaker = tagMaker('div');
divMaker('hello');   // `<div>hello</div>`

const anchorMaker = tagMaker('a');
anchorMaker('go');   // '<a>go</a>'


//위와 같이 동작되는 코드
const tagMaker = function(tag){
  return function(content){
    return `<${tag}>${content}</${tag}>`;
  }
}

✔ 정보의 접근 제한(캡슐화)

✔ 클로저 모듈 패턴

클로저를 이용해 내부 함수를 하나만 리턴하는 것을 넘어, 객체에 담아 여러 개의 내부 함수를 리턴하도록 만든다.

//Example
const makeCounter = () => {
    let value = 0;

    return {
        increase: () => {        //내부 함수1
            value = val ue + 1
        },
        decrease: () => {        //내부 함수2
            value = value - 1
        },
        getValue: () => value    //내부 함수3 
    }
}

const counter1 = makeCounter();
counter1  // { increase: f, decrease: f, getValue: f }

'외부 스코프에서는 내부 스코프 변수에 접근할 수 없다'는 스코프 규칙에 의해 value 변수에 값을 재할당하는 것이 불가능하다. (리턴하는 객체가 제공하는 메서드를 통해 값을 간접적으로 조작할 수는 있다.)

이것을 정보의 접근 제한(캡슐화)이라고 하며, 만일 스코프로 value값을 감싸지 않는다면 value값은 전역 변수여야만 한다.

전역 변수의 많은 사용은 side effect를 초래하므로, 클로저를 통해 전역변수의 사용을 줄이고 스코프를 이용해 값을 안전하게 다룰 수 있다.


✔ 모듈화

함수 재사용성을 극대화하고 함수를 독립적인 부품 형태로 분리한다. 클로저를 통해 데이터와 메서드를 같이 묶어서 다룰 수 있기 때문에 모듈화에 유리하다.

아래의 예시를 보면, 여러개의 counter를 만드는 것이 가능하다. makeCounter에 의해 리턴된 객체는 makeCounter를 실행할 때 선언되는 value 값을 독립적으로 가지기 때문에 counter1 에서의 value와 counter2에서의 value는 서로 영향을 끼치지 않고 각자의 값을 보존할 수 있다.

//Example
const makeCounter = () => {
    let value = 0;

    return {
        increase: () => {        //내부 함수1
            value = val ue + 1
        },
        decrease: () => {        //내부 함수2
            value = value - 1
        },
        getValue: () => value    //내부 함수3 
    }
}

const counter1 = makeCounter();
counter1.increase();
counter1.increase();
counter1.decrease();
counter1.getValue();   // 1

const counter2 = makeCounter();
counter2.decrease();
counter2.decrease();
counter2.decrease();
counter2.getValue();   // 3



👀 클로저 이해하기

Q1. 아래의 코드에서 클로저로 간주되는 함수는?

let seenYet = function() {
  let archive = {};
  return function(val) {   // <--클로저, seenYet에 선언된 변수 archive에 접근할 수 있음
    if (archive[val]) {
      return true;
    } 
    archive[val] = true;
    return false;
  }
}

Q2. total의 값은?

let add = function(x) {
  let sum = function(y) { // <--클로저, add의 변수 x에 접근이 가능
    return x + y;
  }
  return sum;
}

let foo = add(1);
foo(3);              // 4,    add(1)(3)
let total = foo(6);  // 7,    add(1)(6)

Q3. 두 코드 중 클로저를 사용하는 코드는?

let multiplyByX = function(x) {
  return function(y) {  //<--클로저,  multiplyByX의 변수 x에 접근 가능
    return x * y;
  }
}

let multiplyBy5;
multiplyBy5 = multiplyByX(5);

multiplyBy5(4);
let multiplyByFive = function() {
  return function(y) {  //<-- 클로저 X, multiplyByFive의 변수가 존재하지 않으므로 접근 X
    return 5 * y;
  }
}

let multiplyBy5 
multiplyBy5 = multiplyByFive();
multiplyBy5(4);

Q4. f1~f2 각각의 출력

var a = 0;
function foo() {
    var b = 0;
    return function() {   // 클로저
        console.log(++a, ++b);
    };
}

var f1 = foo();
var f2 = foo();

// a는 전역변수이므로 카운트가 계속 올라감
// b는 지역변수이므로 f2()실행시 카운트 리셋
f1(); // 1 1
f1(); // 2 2
f2(); // 3 1
f2(); // 4 2

Q5. 다음 코드는 클로저가 유용하게 사용되는 상황으로, 버튼을 클릭하면 나타나고 사라지는 토글을 구현한 예제이다.

<!DOCTYPE html>
<html>
<body>
  <button class="toggle">toggle</button>
  <div class="box" style="width: 100px; height: 100px; background: red;"></div>

  <script>
    var box = document.querySelector('.box');
    var toggleBtn = document.querySelector('.toggle');

    var toggle = (function () {
      var isShow = false;

      // TODO: ① 클로저를 반환
      return function () {
        box.style.display = isShow ? 'block' : 'none';
        //if(isShow===true){}else{}
        
        // TODO: ③ isShow 변수의 상태를 변경
        isShow = !isShow;
      };
    })();

    // ② 이벤트 프로퍼티에 클로저를 할당
    toggleBtn.onclick = toggle;
  </script>
</body>
</html>




Reference: 코드스테이츠

0개의 댓글