[33-js-concepts] 8. IIFE, Modules, Namespaces

IIFE(Immediately Invoked Function Expressions) - 즉시 호출 함수 표현식

 

(function () {
   // Do fun stuff
  }
)()

위의 예제가 가장 기본적인 형태입니다.

 

함수 선언(Declaration)과 표현(Expression)

  • 함수 표현식은 선언과 동일한 문법을 가지고 단지 표현식에서는 함수명이 생략될 수 있다고만 기술하고 있습니다.

  • 함수 선언(declaration)은 미리 자바 스크립트의 실행 컨텍스트(execution context)에 로딩 되어 있으므로 언제든지 호출할 수 있지만, 표현식(Expression)은 인터프리터가 해당 라인에 도달 하였을때만 실행이 됩니다.

    // [함수표현식] - 호이스팅관련 성공
    a(); // success!
    function a() {
       alert('a');
    }
     
    // [함수표현식] - 호이스팅관련 실패
    b(); // "b" is not defined.
    var b = function() {
       alert('b');
    };
     
    // [즉시호출함수]
    (function c () {});
    alert(c); // "c" is not defined.즉시 호출 함수 표현식 작동방법
    • 괄호쌍이 익명의 함수를 감싸서 함수 선언(declaration)을 함수 표현식(expression)으로 표현될 수 있습니다.

    • 단순한 익명의 함수를 global 스코프에 선언하지 않고 어디서든 익명의 함수 표현식을 가질 수 있습니다.

    // 괄호 사용 안함
    x = function () {};
    // 괄호 사용
    (x = function () {});
    // 변수 x에는 함수의 값이 할당됩니다. 괄호로 둘러쌓인 함수는 익명의 함수 표현식이 됩니다.

     

  • 이와 유사하게 이름을 부여하고, 즉시 호출된 함수 표현식으로 생성할 수 도 있습니다.

    (showName = function (name) {
     console.log(name || "No Name")
    }
    ) (); // No Name
    showName("Rich"); // Rich
    showName(); // No Name
     
  • 참조할 방법이 없기 때문에 익명의 표현식은 나중에 다시 호출할 수 없습니다. 

  • showName 함수는 선언과 동시에 실행이 되며, 이름이 있으므로 나중에 호출 할수도 있습니다.


    주로 언제 IIFE를 사용하는가?
  • 괄호쌍이 익명의 함수를 감싸서 함수 선언(declaration)을 함수 표현식(expression)으로 표현될 수 있습니다.

  • 단순한 익명의 함수를 global 스코프에 선언하지 않고 어디서든 익명의 함수 표현식을 가질 수 있습니다.

  • 필요한 경우 마지막 괄호안에 외부 객체나 변수를 넣어 익명의 함수에 전달할 수 도 있습니다.

 

 

Module

  • 모듈은 프로그램의 일부분, 프로그램은 하나 이상의 모듈의 조합으로 구성됩니다.

  • 모듈은 하나 또는 그 이상의 함수, 메서드, 프로시져 로 구성됩니다.

    모듈이 나온 배경
  1. 자바 스크립트 변수는 전역 범위안에 있어 캡슐화의 필요성을 느낌

  2. 한 프로그램에서 점점 늘어가는 코드를 정리 하기 위함

  3. 역할에 맞는 기능들을 묶기 위함

 

 

  • Module Pattern
  • Module Pattern은 클래스의 컨샙을 모방해서 private, public 메서드 모두를 포함시킬수가 있고 단일 객체 내의 변수를 포함할수있습니다.

  • 특정부분의 전역 스코프로 부터 보호하는 것이 가능해집니다.

  • 동일 페이지 내에서도 함수이름이나 변수가 충돌하는 것을 예방할수있습니다.

     

    var testModule = (function () {
     
       var counter = 0;
     
       return {
     
           incrementCounter: function () {
     
               return counter++;
     
          },
     
           resetCounter: function () {
     
               console.log( "counter value prior to reset: " + counter );     counter = 0;
     
          }
     
      };
     
    })();
     
    testModule.incrementCounter();
    testModule.resetCounter();

     

     

    대표적인 모듈 시스템
  •  
  • CommonJS

  • AMD (Asychronous Moudle Definition)

  • ES6 Module

     

    AMD 와 CommonJS 자세한 내용이 궁금 하면 참고

     

    CommonJS
  • 탄생 배경

  • 창시자인 Kevin Eangoor가 자바스크립트의 문제점을 지적

    1. 자바스크립트는 모듈 시스템이 없다.

    2. 자바스크립트는 표준 라이브러리가 없다. (API, data, math 등 기본라이브러리만)

    3. 웹 서버 또는 DB등을 위한 표준 인터페이스가 없다.

    4. 자바스크립트는 의존성을 관리하고 설치 할 수 있도록 하는 패키지 관리 시스템이 없다.

  • 이와 같은 문제들을 극복하기 위해 시작한 그룹

  • module.exports
  • // 📁 utils.js
    const PI = 3.14;
    module.exports.PI = PI;
     
     
    // 📁 app.js
    const utils = require('./utils');
    console.log(utils.PI); // 3.14

     

  • 위의 예제와 같이 export에 값이 들어가게 됩니다.

  • module.export에 값을 넣는경우
    • // 📁 utils.js
       
      module.exports = 3.14;
      console.log(module);
       
      Module {
       ...
       exports: 3.14, // exports 값은 3.14 입니다
       parent: Module {/*...*/},
       filename: '...',
       loaded: false,
       children: [],
       paths: [ /*...*/ ]
      }

       


    •  

exports

  • // 📁 utils.js
     
    exports.PI = 3.14;
    console.log(module.exports === exports); // true
     
    console.log(exports); // { PI: 3.14 }

     

  • 차이점 : exports의 리턴 값은 module.exports 입니다.

  • module.exports 와 exports 는 완전히 동일합니다.

  • module.exports 처럼 같은 동작을 하는 exports 키워드가 있습니다.

  • require
  • require 는 다른 모듈 파일을 볼러옵니다.

  • 공식 문서에 따르면 대표적으로 모듈을 불러오는 방법이 4가지 있습니다.

    1. File Modules
    2. Folders as Modules
    3. node_modules
    4. Global Directory

 

  • file Modules// 📁 data.json
    { "data": "Hello World!" }
     
    // 📁 utils.js
    module.exports.PI = 3.14;
     
    // 📁 app.js
     
    const data = require('./data');
    const utils = require('./utils');
     
    console.log(data); // { data: "Hello World!" }
    console.log(utils.PI); // 3.14

     

  • 상대경로에 있는 파일 중 확장자가 .js.json.node 인 파일을 모듈로써 불러옵니다.

  • 확장자는 생략할 수 있습니다.

 

  • Folders as Modules
  • 폴더를 상대 · 절대 경로의 형태(/, ./, ../) 로 require 할 때는 다음과 같은 순서로 탐색합니다.
    1. package.json 파일에 정의된 name 과 main 의 값을 활용
    2. 1번에서 탐색에 실패하면, 해당 폴더에 index.js 또는 index.node 파일이 있는지 확인
    3. 모두 실패하면 Cannot find module 에러 throw

 

  • node_modules
  • 만약 상대, 절대 경로에 대한 표시 없이 (/, ./, ../) require 를 호출하면 Node.js 는 현재 모듈의 최상위 디렉토리에서 시작하여 /node_modules 라는 경로를 앞에 붙이고 탐색합니다.

  • 만약 require('a') 를 호출하면 아래와 같은 순서로 탐색합니다.

    • /home/사용자이름/project/node_modules/a.js

    • /home/사용자이름/node_modules/a.js

    • /home/node_modules/a.js

    • /node_modules/a.js


 

  • GLOBAL_DIRECTORIES
  • Node.js 는 위 3 가지 방법으로 찾지 못하면 OS의 글로벌 파일 경로를 탐색합니다.

  • Node.js 는 기본적으로 다음 GLOBAL_DIRECTORIES 를 탐색합니다.

    • $HOME/.node_modules

    • $HOME/.node_libraries

    • $PREFIX/lib/node

  • $PREFIX의 경우는 Node.js 가 설정한node_prefix 경로입니다

 

 

  • AMD(Asynchronous Module Definition)
  • AMD 그룹은 비동기 상황에서도 JavaScript 모듈을 쓰기 위해 CommonJS에서 함께 논의하다 합의점을 이루지 못하고 독립한 그룹입니다.
  • CommonJS가 JavaScript를 브라우저 밖으로 꺼내기 위한 노력의 일환으로 탄생했기 때문에 브라우저 내에서의 실행에 중점을 두었던 AMD와는 합의를 이끌어 내지 못하고 결국 둘이 분리되었습니다.
  • AMD가 목표로 하는 것은 필요한 모듈을 네트워크를 이용해 내려받아야 하는 브라우저 환경에서도 모듈을 사용할 수 있도록 표준을 만드는 것입니다. 
  • 따라서 현재 JavaScript 모듈화에 대한 논의는 크게 CommonJS 진영과 AMD 진영으로 나뉘게 되었습니다.

 

 

 

  • ES6 Module
  • ES6 이전까지는, 브라우저 환경에서 사용 가능한 표준 모듈 시스템은 없었습니다.

  • ES6에서는 정식으로 모듈 시스템이 도입되었습니다.

 

  • export// 1. 변수 선언 즉시 내보내기
     
     export let name1;
     
     export const name2;
     
     export var name3;
     
     export function name4 () {/*...*/}
     
     export class MyClass {/*...*/}
     
     
     
     // 2. 변수 먼저 정의하고, 모아서 내보내기
     
     const var1;
     
     let var2;
     
     var var3;
     
     export { var1, var2, var3 }
     
     
     // 3. 먼저 정의된 함수를 별칭으로 변경해서 내보내기
     
     let var4;
     
     export { var4 as var5 } // 다른 모듈에서 import 할 때에는 var5 로 import 해야 함

     

    • named export는 모듈 내에 여러개 존재 할 수 있습니다.

    • 변수 선언과 동시에 내보내는 것과 정의된 변수들을 모아서 내보내는 방법이 있습니다.

 

 

  • default export  export default expression;

     export default function () {/*...*/} // 익명함수

     export default function myFunction () {/*...*/} // 기명함수

     export default class {/*...*/}

     export default class MyClass {/*...*/}

     export default function* () {/*...*/} // 제너레이터도 동일

     

     // 🙅♂️ Uncaught SyntaxError: Unexpected token const

     export default const test = /*...*/

     

     // named export 처럼 묶어 내보내기도 가능합니다

     const myModule = {/*...*/}

     const var1 = () => {}

     export { myModule as default, var1 } // as 를 이용해 default export 하였습니다

     

    • default export 는 모듈에서 하나만 존재할 수 있습니다.

    • export default { left, const, var } 도 안됩니다.

       

 

  • export - from// math 모듈에서 일부만 import 한 뒤 다시 export 합니다
     
     export { add, subtract } from './math';
     
     
     
     // 같은 파일 내에서 사용하고 내보낼 수 없습니다
     
     add(1, 2); // 🙅♂️ Uncaught ReferenceError: add is not defined
     
    • import 와 export 를 한 번에 처리 할 수 있습니다.

    • 패키지의 다른 모듈을 모아서 일관된 형태로 내보내거나 관리하고자 할 때 주로사용 합니다.

    • export - from 을 처리하는 파일 스코프에 식별자를 바인딩 하지 않습니다.

 

 

  • import  import name from "module-name";
     
     import * as name from "module-name";
     
     import { member } from "module-name";
     
     import { member as alias } from "module-name";
     
     import { member1 , member2 } from "module-name";
     
     import { member1 , member2 as alias2 , [...] } from "module-name";
     
     import defaultMember, { member [ , [...] ] } from "module-name";
     
     import defaultMember, * as alias from "module-name";
     
     import defaultMember from "module-name";
     
     import "module-name";
     
    • 다른 파일에서 모듈을 불러 올때 씁니다.

    • default export, named export를 불러 올 수 있습니다.

    • as를 통해 alias 처리 하여 부분 및 전체를 불러 올 수도 있습니다.

 

네임스페이스

  • 네임스페이스(namespace)는 구분이 가능하도록 정해놓은 범위나 영역을 뜻합니다.

  • 말 그대로 이름 공간을 선언하여 다른 공간과 구분하도록 합니다.

  • 결국 네임스페이싱(namespacing)은 객체나 변수가 겹치지 않는 안전한 소스코드를 만드는 개념입니다.

  • JavaScript는 아직까지 네임스페이싱을 위한 기능을 지원하지 않기 때문에 다음의 특성을 통해 네임스페이스와 비슷한 효과를 얻을 수 있습니다.

    • JavaScript의 모든 객체는 프로퍼티를 가집니다.

    • 그 프로퍼티는 다시 다른 객체를 담을 수 있습니다.

  • 이러한 네임스페이싱 코딩 기법들을 네임스페이스 패턴 이라고 합니다.

  • 객체 리터럴 네임스페이싱
    • 가장 기본적인 네임스페이스 패턴은 객체 리터럴 네임스페이싱 방식 입니다.

    • 하나의 전역 객체(global object)를 만든 후, 모든 함수, 객체, 변수를 여기에 추가하여 구현합니다.

     

    // 하나의 전역 객체
     
     var MYAPP = {};
     
     
     
     MYAPP.Parent = function() { console.log('Parent'); };  
     
     MYAPP.Child = function() { console.log('Child'); };
     
     
     
     MYAPP.variable = 1;
     
     
     
     // 객체 컨테이너
     
     MYAPP.modules = {};
     
     
     
     // 객체들을 컨테이너 안에 추가합니다.
     
     MYAPP.modules.module1 = {};  
     
     MYAPP.modules.module1.data = {a: 1, b: 2};  
     
     MYAPP.modules.module2 = {};
     
     
     
     MYAPP.Parent();                               // Parent 출력  
     
     console.log(MYAPP.modules.module1.data.a);    // 1 출력  
     
     MYAPP.Child();                                // Child 출력

     

    • 코드 안, 같은 페이지에 존재하는 JS라이브러리의 이름 충돌도 방지해 주며 체계적이라는 장점이 있습니다.

    • 단점

      1. 모든 변수와 함수에, 상위 객체 명을 모두 붙여야 하기 때문에 소스코드량, 다운로드량이 늘어납니다.

      2. 전역 인스턴스가 단 하나뿐이기 때문에 코드의 어느 한 부분이 수정되어도 전역 인스턴스를 수정하게 됩니다.

      3. 매번 객체에 접근하는데다, 이름이 중첩되고 길어지므로 검색이 느려지게 됩니다.

 

 

  • 범용 네임스페이스 함수
    • 프로그램이 복잡해짐에 따라, 코드의 각 부분들이 별개의 파일로 분리되어 선택적으로 문서에 포함되는 경우가 많다.

    • 네임스페이스로 사용할 객체를 선언할 때나, 이미 있는 것을 재정의하는 일도 생길 수 있습니다.

    // 위험
     
     var MYAPP = {};
     
     
     
    // 개선안
     
     if (typeof MYAPP === "undefined") {  
     
         var MYAPP = {};
     
    }
     
    // 짧게 작성
     
    var MYAPP = MYAPP || {};  
     
    • 네임스페이스가 존재하면 덮어쓰지 않기 때문에 기존 코드를 망가 뜨리지 않습니다.

    • 네임스페이스 함수를 구현한 함수입니다.

    var MYAPP = MYAPP || {};
     
     MYAPP.namespace = function (ns_string) {
     
         var parts = ns_string.split('.'), parent = MYAPP, i; // 처음에 중복되는 전역 객체명은 제거
     
         if (parts[0] === 'MYAPP') {
     
             parts = parts.slice(1);
     
        }

         for (i = 0; i < parts.length; i += 1) {
     
             if (typeof parent[parts[i]] === 'undefined') {
     
                 parent[parts[i]] = {};
     
            } parent = parent[parts[i]];
     
        }
         return parent;
    };
     

     

     

    var module2 = MYAPP.namespace('MYAPP.modules.module2');
     
     console.log(module2);
     
     console.log(module2 === MYAPP.modules.module2); // true 가 기록
     

     // 첫 부분의 'MYAPP' 을 생략하고도 사용할 수 있습니다.
     
     MYAPP.namespace('modules.module10');
     
     // 매우 긴 네임스페이스를 만들 수 있습니다.
     
     MYAPP.namespace('a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z');
     
     console.log(MYAPP);

     

 

 

  • 샌드박스 패턴
    • 코드들이 전역 범위를 더럽히지 않고 마음껏 쓰일 수 있도록 유효범위를 정해줍니다.

    • 생성자를 유일한 전역으로 사용

    • 생성자에게 콜백 함수를 전달해 모든 기능을 샌드박스 내부 환경으로 격리 시키는 방법

       

    • 장점

    1. 단 하나의 전역변수에 의존하는 네임스페이스 패턴의 단점을 여러 개의 샌드박스 객체를 생성으로 극복할수있다.

    2. .으로 연결된 긴 이름을 쓸 필요 없다.

    3. 런타임때 탐색 작업을 거치지 않게 해준다.

 

 

 

 

 

댓글(0)

Designed by JB FACTORY