반디북
자바스크립트 + 리액트 디자인 패턴
Ch11
도은
  • 네임스페이스 패턴에 대해 알아본다.
  • 네임스페이스는 코드 단위를 고유한 식별자로 그룹화한 것
  • 자바스크립트는 다른 언어들처럼 네임스페이스를 기본적으로 지원하지는 않지만, 객체와 클로저를 활용하여 비슷한 효과를 얻을 수 있다.

11.1 네임스페이스의 기초

이번 장에서 살펴볼 네임스페이스 패턴의 종류는 다음과 같다.

  • 단일 전역 변수
  • 접두사 네임스페이스
  • 객체 리터럴 표기법
  • 중첩 네임스페이스
  • 즉시 실행 함수 표현식
  • 네임스페이스 주입

11.2 단일 전역 변수 패턴

전체 애플리케이션에서 딱 하나의 전역 변수만 만들어 모든 기능을 담는 방식

const myUniqueApplication = (() => {
  function myMethod() {
    // 코드
    return;
  }
  return {
    myMethod,
  };
})();
 
// 사용법
myUniqueApplication.myMethod(); // myUniqueApplication 객체에서 외부로 노출된 myMethod 메서드 호출

11.3 접두사 네임스페이스 패턴

전역 변수를 줄이기 위해 접두사를 붙여 네임스페이스를 만드는 방식

const myApplication_propertyA = {};
const myApplication_propertyA = {};
function myApplication_myMethod() {
  // ...
}

11.4 객체 리터럴 표기법

객체 리터럴 표기법을 사용하여 네임스페이스를 만드는 방식

const myApplication = {
  getInfo() {
    // ...
  },
  // 객체 안에 추가로 객체 네임스페이스를 만들어 필요한 요소들을 담을 수도 있다.
  models: {},
  views: {
    pages: {},
  },
  collections: {},
};

11.5 중첩 네임스페이스 패턴

객체 리터럴 표기법을 중첩 사용하여 네임스페이스를 만드는 방식

YAHOO.util.getElementsByClassName('test');

11.6 즉시 실행 함수 표현식

즉시 실행 함수 표현식을 사용하여 네임스페이스를 만드는 방식

  • 자바스크립트에서는 즉시 실행 함수로 정의된 내부의 변수와 함수 모두 외부에서 접근 불가능
  • 따라서 함수를 호출하는 것만으로도 쉽게 코드의 은닉성을 구현 가능
const namespace = {};
 
// 함수 매개변수로 네임스페이스 객체를 전달하고, 공용 메서드와 속성을 할당
((o) => {
  o.foo = 'foo';
  o.bar = () => 'bar';
})(namespace);
 
console.log(namespace); // {foo: 'foo', bar: ƒ}

아래는 더 유용하게 기능을 확장하는 방식이다.

/**
 * 네임스페이스의 이름과 undefined를 인자로 전달한다.
 * 1. 네임스페이스를 지역적으로 변경할 수 있고, 함수 컨텍스트 밖에서 덮여쓰여지지 않는다.
 * 2. undefined 값이 정말로 undefined임을 보장한다. ES5 이전 버전의 undefined는 변경될 수 있었기 때문에 필요한 과정이다.
 */
((namespace, undefined) => {
  // 비공개 속성들
  const foo = 'foo';
  const bar = 'bar';
 
  // 공개 메서드와 속성
  namespace.foobar = 'foobar';
  namespace.sayHello = () => {
    speak('hello world');
  };
 
  // 비공개 메서드
  function speak(msg) {
    console.log(`You said: ${msg}`);
  }
})(namespace);

11.7 네임스페이스 주입

함수 내에서 this를 네임스페이스의 프록시로 활용하여 특정 네임스페이스에 메서드와 속성을 주입

const myApp = {};
myApp.utils = {};
 
(function () {
  let val = 5;
 
  this.getValue = () => val;
  this.setValue = (newVal) => {
    val = newVal;
  };
 
  // utils 하위에 새로운 하위 네임스페이스인 tools를 생성
  this.tools = {};
}).apply(myApp.utils);
 
// 위에서 utils를 통해 정의한 tools 네임스페이스에 새로운 동작을 추가한다.
(function () {
  this.diagnose = () => 'diagnosis';
}).apply(myApp.utils.tools);
 
console.log(myApp.utils.getValue()); // 5
myApp.utils.setValue(25); // 25
console.log(myApp.utils.getValue()); // 25

11.8 고급 네임스페이스 패턴

중첩 네임스페이스 자동화 패턴

  • 중첩 구조를 계속해서 사용하다보면 중첩의 깊이가 커질수록 매우 번거로워질 수 있다.
  • 하나의 문자열 인자를 받아서 파싱한 뒤에 필요한 객체를 기반 네임스페이스에 자동으로 추가하는 방법
// 최상ㅟ 네임스페이스에 객체 리터ㄹ을 할당한다.
const myApp = {};
 
// 문자열 형식의 네임스페이스를 파싱하고 자동으로 중첩 네임스페이스를 생성해주는 간편한 함수
function extend(ns, ns_string) {
  const parts = ns_string.split('.');
  let pl;
 
  pl = parts.length;
 
  for (let i = 0; i < pl; i++) {
    // 프로퍼티가 존재하지 않을 경우에만 생성
    if (typeof ns[parts[i]] === 'undefined') {
      parent[parts[i]] = {};
    }
    parent = parent[parts[i]];
  }
  return parent;
}
 
// 사용법: myApp에 깊게 중첩된 네임스페이스를 확장
const mod = extend(myApp, 'modules.module2');
console.loog(mod == myApp.modules.module2); // true
세민

네임 스페이스 패턴

주요 특징

  1. 전역 변수 최소화
  • 네임스페이스 객체 하나만 전역에 선언하고, 모든 속성과 메서드를 그 객체의 하위로 배치한다.
  1. 코드 구조화
  • 관련된 함수와 변수를 논리적으로 그룹화하여 코드의 가독성과 유지보수성을 높인다.
  1. 이름 충돌 방지
  • 동일한 이름의 변수나 함수가 여러 곳에서 선언되어도 네임스페이스로 구분되므로 충돌이 발생하지 않는다.

단일 전역 변수 패턴

var name = "book";
function showName() { ... }

위 코드를 단일 전역 변수 패턴으로 보완하면 다음과 같다.

var MyApp = MyApp || {};
 
MyApp.name = 'book';
MyApp.showName = function () {
  // ...
};

접두사 네임스페이스 패턴

  • 변수나 함수 이름 앞에 특정 접두사를 붙여서, 네임스페이스처럼 사용하는 방식
    • 이 방법은 객체를 사용하지 않고, 이름 자체로 구분하는 전통적인 방식이다.
var MYAPP_name = 'book';
function MYAPP_showName() {
  console.log(MYAPP_name);
}

객체 리터럴 표기법 패턴

  • 객체 리터럴을 사용하여 네임스페이스를 정의하고, 관련된 변수와 함수를 그 객체의 속성으로 추가하는 방식
    • 이 방법은 코드의 구조화와 전역 변수 오염 방지에 효과적이다
var MyApp = {
  name: 'book',
  showName: function () {
    console.log(this.name);
  },
};
 
MyApp.showName(); // "book"

중첩 네임스페이스 패턴

  • 중첩 네임스페이스 패턴은 네임스페이스 객체 안에 또 다른 네임스페이스(객체)를 계층적으로 정의하는 방식
    • 이렇게 하면 관련된 기능을 더 세분화하여 그룹화할 수 있고, 대규모 프로젝트에서 코드의 충돌을 더욱 효과적으로 방지할 수 있다.
var MyApp = MyApp || {};
MyApp.utils = MyApp.utils || {};
 
MyApp.utils.string = {
  trim: function (str) {
    return str.replace(/^\s+|\s+$/g, '');
  },
};
 
MyApp.utils.number = {
  isEven: function (num) {
    return num % 2 === 0;
  },
};
 
console.log(MyApp.utils.string.trim('  hello  ')); // "hello"
console.log(MyApp.utils.number.isEven(4)); // true

즉시 실행함수 표현식 패턴

  • 즉시 실행함수 표현식(IIFE) 패턴은 함수를 정의하자마자 즉시 실행하는 방식
    • 이 패턴을 사용하면 함수 내부에 선언된 변수나 함수가 외부(전역)로 노출되지 않아, 전역 네임스페이스를 오염시키지 않고도 필요한 기능을 캡슐화할 수 있다.
    • 주로 모듈화, 프라이빗 변수/함수 구현, 네임스페이스 보호 등에 활용된다.
var MyApp = MyApp || {};
 
MyApp.counter = (function () {
  // private 변수
  var count = 0;
 
  return {
    increment: function () {
      count++;
      return count;
    },
    decrement: function () {
      count--;
      return count;
    },
    getCount: function () {
      return count;
    },
  };
})();
 
console.log(MyApp.counter.increment()); // 1
console.log(MyApp.counter.increment()); // 2
console.log(MyApp.counter.getCount()); // 2
console.log(MyApp.counter.decrement()); // 1

네임스페이스 주입 패턴

  • 네임스페이스 주입 패턴은 외부에서 정의된 네임스페이스(객체)를 함수의 인자로 전달하여, 내부에서 해당 네임스페이스에 속성이나 메서드를 동적으로 추가하는 방식
    • 이 패턴을 사용하면 여러 모듈이나 기능을 하나의 네임스페이스에 유연하게 결합할 수 있고, 테스트나 확장성, 의존성 관리에도 유리하다.
var MyApp = MyApp || {};
 
(function (ns) {
  ns.sayHello = function () {
    console.log('안녕하세요!');
  };
  ns.version = '1.0.0';
})(MyApp);
 
console.log(MyApp.version); // "1.0.0"
MyApp.sayHello(); // "안녕하세요!"

고급 네임스페이스 패턴

  • 중첩 네임스페이스 패턴: app.utils.drawing.canvas.id 같은 형식으로 체계적으로 계층화된 구조로 사용하는 패턴
    • DOM 구조가 이에 해당되는 것 같다.
  • 의존성 선언 패턴: 객체에 대한 로컬 참조가 전체적인 조회 시간을 단축한다는 점을 이용한 패턴
    • const obj = someObj.someProperty.ref 와 같이 객체 일부를 미리 참조하는 것
  • 심층 객체 확장 패턴: deepmerge 와 같은 객체를 병합하여 확장하여 사용하는 것

개인적인 생각

패턴의 코드들을 보면 실제 작성하는 메서드나 일부 속성들이 위 패턴을 기반으로 만들어져있다고 생각한다. DOM 등 하지만 일반적인 작업에서 이러한 패턴을 직접 다루기엔 조금 애매하고, 모듈로 정의하고 사용하는게 더 낫지 않나는 생각이다.