[JS] 클로저와 모듈패턴 이해하기

Jiheon Kim·2023년 2월 27일
3

Javascript

목록 보기
2/6
post-thumbnail

자바스크립트의 핵심적인 기능인 클로저의 동작 원리와 어떻게 쓰이는지 알아보고, 다양한 모듈패턴과 클로저와의 관계를 이해 해보자 🔎

클로저 (Closure)

함수가 외부 범위의 변수에 접근할 수 있게 하는 기능으로
이를 통해 외부에서 접근할 수 없는 변수와 메소드를 만들 수 있다.

💡 클로저는 중요한 개념이므로 자세히 알아보자

1) 렉시컬 스코프란❓ (Lexical scope)

자바스크립트는 렉시컬 스코프를 채택하고있다 (정적 스코프 라고도 한다)
렉시컬 스코프는 변수나 함수가 코드상에서 정의된 위치에 따라 결정하는 것을 의미한다. 그래서 자바스크립트에서 함수의 유효범위는 그 함수를 어디서 호출하냐에 따라 정해지는 것이 아닌 그 함수를 어디서 정의했느냐에 따라서 유효범위가 달라진다

클로저란 결국 함수가 선언될때, 자신이 선언된 시점의 스코프에 정의된 변수를 기억하는 것 이다. 그래서 함수를 호출할 떄, 해당 유효범위에서 변수를 참조할 수 있게 해준다
따라서 클로저는 렉시컬 스코프를 활용하여 함수가 실행될때 생성된 변수를 기억하고 참조할 수 있도록 해준다

2) 간단한 예제로 알아보는 클로저

const createPassword = () => {
    const password = "qweasd1!";
    return (code) => code === password;
  };
  const validation = createPassword();

  console.log(validation("abc1232@")); // false
  console.log(validation("qweasd1!")); // true

createPassword() 함수가 반환한 함수는 클로저를 이용하여 password 변수에 접근할 수 있다. 따라서 createPassword() 함수가 호출된 이후에도 password 변수의 값을 유지하고 기억한다 createPassword() 함수가 호출된 이후에 함수는 종료가 되었지만, 함수 내부의 변수에 접근이 가능하고 이를 이용하여 validation 함수가 항상 동일한 password를 비교한다

3) 클로저하면 빠질 수 없는 커링(Currying)

// Before 
  const clickHandler = (param) => {
    console.log(param);
  };

  return <button onClick={(event) => clickHandler("inital")}>버튼</button>;
// After 
  const clickHandler = (param) => (event) => {
    console.log(param);
  };

  return <button onClick={clickHandler("inital")}>버튼</button>;       

예제는 React 에서 이벤트 핸들링을 추가하는 코드이다
함수의 파라미터와 callback함수의 인자가 같은 경우 생략이 가능하지만
clickHandler 함수에 기본적인 clickEvent 이외의 파라미터를 받아야 하는 경우 인자를 넘겨줘야 하는데 이경우, 파라미터와 전달인자가 달라지기 때문에 생략이 불가능 해서
() => clickHandler("inital") 이런 표현식으로 전달을 해줘야한다. 이럴때 커링을 활용하면 좀더 깔끔하게 코드를 작성할 수 있다.

4) 리액트훅이 동작하는 핵심은 클로저에 있다

// 실제 useState훅 구현체 
function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

실제로 useState훅을 분석해보면 외부에서 주입 받는다✨

React의 함수형 컴포넌트는 props를 받아서 jsx를 반환하기 때문에 렌더링을 할 때마다 함수를 다시 호출한다. 하지만 useState()은 렌더링이 되어서 컴포넌트가 다시 호출되었을때 useState()도 다시 실행되면서 이전 상태값이 사라져야 할것 같지만 이를 해결 하기위해 클로저를 사용하여 useState() 바깥쪽에서 state를 관리하여 이전상태를 기억한다.

모듈 (Module)

코드를 재사용하고 유지보수할 수 있도록 구성하는 설계 패턴 또는 방법

그래서 클로저와 모듈은 어떤 관계인데?

클로저는 전용 범위를 만들고 내부 상태와 동작을 비공개로 만들기 위해 모듈의 일부로 사용될 수 있어서 모듈 패턴은 종종 클로저를 사용하여 모듈의 상태 및 동작에 대한 전용 범위를 생성하는 데 사용된다

모듈패턴의 몇가지 종류를 알아보자

여러 가지 모듈 패턴에 대해 알아보면서 모듈 패턴에서 클로저가 어떻게 쓰이는지 알아보고 클로저가 적용된 모듈 패턴과 클로저가 적용되지 않은 모듈 패턴을 비교해보면서 클로저와 모듈 패턴의 관계를 이해 해보자

1) 클로저를 사용하는 Revealing(노출) 모듈 패턴

// index.js
const student = () => {
  let name = "Izreal";
  let age = 20;
  
  return {
    getName: () => name,
    getAge: () => age,
    setName: value => name = value,
    setAge: value => age = value,
  };
};

student().getAge(); // 20
student().name // undefined

student가 실행되면 실행 context를 생성하고 내부에 name age를 생성한다. 반환된 메소드는 클로저 때문에 함수 외부에 있는 context에 접근할 수 있다. 그래서 student 함수가 호출될 때마다 새로운 실행 context에 변수가 생성되므로 새롭게 정의된 컨텍스트 변수를 참조하는 새 인스턴스를 생성한다.
student 함수는 클로저를 사용하여 객체의 상태와 동작을 캡슐화하는 방법을 사용해서 외부에서 반환된 메소드를 통해서만 접근하거나 수정할 수 있다.

즉, nameage를 마치 private 하게 사용하고 있는 것 👍
클로저를 사용한 모듈의 상태 및 메소드에 대한 전용 범위를 생성하는 일반적인 모듈 패턴이다.


2) 클로저를 사용하지 않는 객체 리터럴 패턴

// index.js
const student = {
  name: "Kogmow",
  age: 20,
  
  getName() {
    return this.name;
  },
  getAge() {
    return this.age;
  },
  setName(name) {
    this.name = name;
  },
  setAge(age) {
    this.age = age;
  },
};

student.getAge() // 20
student.age // 20

반면 클로저를 적용하지 않은 모듈 패턴을 사용하면 적용한 패턴과 마찬가지로 객체의 상태를 캡슐화하여 객체에 접근하고 수정하는 방법을 제공하고 있지만 이 방식은 private 하지 않아서 객체 외부에서 접근하고 값을 수정할 수 있다.

3) 클로저를 사용하는 IIFE(즉시 실행함수) 모듈 패턴

// index.js
const student = (() => {
    let name = "Yasuo";
    let age = 20;
  
    return {
      getName: () => name,
      getAge: () => age,
      setName: (value) => (name = value),
      setAge: (value) => (age = value),
    };
  })();


  student.getName() // Yasuo
  student.name // undefined

IIFE (Immediately Invoked Function Expression)

클로저를 적용하여 전용 범위를 만들고 IIFE 표현식을 사용하여 모듈을 생성하는 패턴이다. 이 패턴은 캡슐화 하여 외부에서의 접근을 막고 이름 충돌을 방지하며 재사용성을 높일 수 있다.
하지만 노출 모듈 패턴과 매우 흡사한데 실제로 노출 모듈 패턴은 이 IIFE 패턴의 변형된 형태로 즉시실행되는 IIFE과는 다르게 선택적으로 노출 시킬수 있다. 즉시 실행함수를 사용하여 스크립트가 로드되는 즉시 인스턴스를 만들기 때문에 노출 모듈 패턴처럼 함수가 실행될 때마다 인스턴스를 반환하는 패턴과 다르게 객체의 단일 인스턴스를 생성한다

또한 IIFE를 이용하면 싱글톤 패턴을 만들 수 있다 (예제 코드는 싱글톤 패턴)
하지만 IIFE을 사용한 모듈 패턴이 반드시 싱글톤 패턴이지는 않다
모듈이 다시 인스턴스를 만들 수 없는 단일 인스턴스를 반환하는 경우는 싱글톤 패턴이지만, 모듈이 생성자 함수를 반환하는 경우 여러 인스턴스를 만드는 데 사용될 수 있어서 싱글톤 패턴이 아닐 수 있다


4) CommonJS Module Pattern

  • JSM은 Node환경에서 모듈 관리에 쓰이는 모듈 시스템
module.exports = jQuery

const $ = ruquire("jQeury")

5) ES6 Module Pattern

  • ESM은 Javascript에서 표준화된 모듈 시스템
export default jQeury 

import $ from "jQeury" 

표준 모듈 시스템으로 쓰이는 이 2가지 CJSESM은 IIFE, Closure같은 개념으로 유사 모듈처럼 쓰였던 이전 방법과는 다르게 어플리케이션이 복잡해짐에 따라 파일 단위 모듈화를 위해 등장했다

💡이외에도 AMD, UMD 모듈 패턴이 있다


마무리

모듈 패턴은 클로저를 사용하여 모듈에 대한 전용 범위를 만들고 모듈에 접근하거나 모듈과 상호 작용할 수 있는 공용 API를 제공한다.
따라서 클로저는 모듈 패턴의 핵심 특징이며, 클로저가 없다면, 모듈을 캡슐화하기는 어려울 것이기 때문에 모듈 패턴은 클로저의 사용에 크게 의존한다. 하지만, 어플리케이션의 규모가 커지면서 CommonJS ES6 모듈 패턴과 같이 파일 단위로 모듈을 관리하는 방법이 등장했고, 이에 따라 클로저를 사용하는 모듈 패턴은 단순 모듈을 만드는 경우를 제외하고는 자주 사용되지 않는다.

참고 자료

React 톺아보기 - 03. Hooks_1
자바스크립트 디자인 패턴 — 모듈 패턴

profile
누군가는 해야하잖아

1개의 댓글

comment-user-thumbnail
2023년 4월 3일

배웠습니다

답글 달기