블록체인 sw개발자

[JS] 모듈 (module)

sang969 2023. 8. 9. 16:42

 

모듈

데이터가 커질수록 파일을 여러 개로 분리해야하는 경우가 생긴다. 이때 분리된 파일 각각을 '모듈(module)' 이라고 부른다. 그런 모듈을 언제든지 불러올 수 있게 해준다거나 코드를 모듈 단위로 구성해 주는 방법을 만드는 등 다양한 시도를 하게 된다.

<script type="module" src="index.js"></script>

모듈 시스템

  • AMD - 가장 오래된 모듈 시스템 중 하나로 require.js 라는 라이브러리를 통해 처음 개발되었습니다.
  • CommonJS - Node.js 서버를 위해 만들어진 모듈 시스템입니다.
  • UMD - AMD 와 CommonJS와 같은 다양한 모듈 시스템을 함께 사용하기 위해 만들어졌다.

이런 모듈 시스템은 오래된 스크립트에서 발견 할 수 있지만 이제는 없다.

 

모듈은 단지 파일 하나에 불과합니다. 스크립트 하나는 모듈 하나입니다.

 

모듈의 장점 

그렇다면 모듈을 쓰면 어떤 장점을 가질 수 있을까?

  1. 유지보수 용이함
  2. 네임스페이스
  3. 재사용

※유지보수 용이함

 

모듈이란 기능적으로 구분될 수 있는 코드들을 높은 독립성을 가직 수 있도록 묶어놓은 코드묶음이라고 할 수 있습니다.

이는 곧 모듈간 결합도가 낮다는 의미가 됩니다. 이로 인해서 구현된 프로그램에서 수정이 필요할 때 연관있는 모듈만 업데이트를 함으로서 최소한의 작업을 통해 유지보수가 가능하게 됩니다.

 

네임스페이스

 

Javascript 에서 top-level function의 scop밖 변수들을 global 변수로서 어디서든 접근이 가능하게 되어있습니다. 이는 프로그램 규모가 커짐에 따라 디버깅을 어렵게 하는 요인이 되는데, 모듈을 사용함으로서 변수들을 위한 private 공간을 만들수 있게 됩니다.

*private(객체필드) 객체 내부에서만 접근할수 있으며 내부 인터페이스를 구성 할 때 쓰인다.

 

재사용

 

모듈은 유사한 기능적 코드모음이라고 하였습니다. 이런 모듈을 통해 여기저기서 사용되는 동일한 기능들에 대해서는 별도로 모듈화를 하여 어디서는 필요에 의한 재사용이 가능하게 됩니다.

모듈 스코프

모듈 내부의 가장 바깥 스코프에서 이름을 선언하더라도, 전역 스코프가 아니라 모듈 스코프에서 선언됩니다.

 모듈 스코프에 선언된 이름은 (export 해주지 않는다면) 해당 모듈 내부에서만 접근할 수 있습니다.

// variables.js
const foo = 'bar';

// 이 파일이 모듈로서 사용되고 있다면, 'undifined' 가 출력됩니다.
console.log(window.foo);

따라서 여러 모듈의 가장 바깥쪽에서 같은 이름으로 변수, 함수 , 클래스를 선언하더라도, 서로 다은 스코프에서 선언되기 때문에 이름의 충돌이 생길 일이 없습니다.

 

export & import

 

모듈에 특수한 지시자 exportimport를 적용하면 다른 모듈을 불러와 모듈에 있는 함수를 호출하는 것과 같은 기능 공유가 가능합니다.

export

지시자를 변수나 함수 앞에 붙이면 외부 모듈에서 해당 변수나 함수에 접근할 수 있습니다(모듈 내보내기).

모듈에서 export 가능한 것은 var/ let/ const/ function/ class 들이 export 가능합니다. export 문을 쓰는 방식은 크게 두가지가 있다.

첫번째는 함수를 정의 할때 export 문을 사용하는 방법입니다.

// calc.js 
export function Add(num1, num2) {
  return num1 + num2;
}

두번째는 함수 정의 후, 마지막에 한번 export 할 것들을 정의 하는 방법입니다.

// calc.js 
function Add(num1, num2) {
  return num1 + num2;
}
function Multiply(num1, num2) {
  return num1 + num2;
}

export {
  Add,
  Multiply,
};

import

지시자를 사용하면 외부 모듈의 기능을 가져올 수 있습니다.(모듈 가져오기)

즉 export 한 모듈을 사용하기 위해서는 import 구문을 사용하면 된다.

// named

import * as name from "module-name"; //모듈 전체 가져오기
import name from "module-name"; // default export 값 가져오기
import { member } from "module-name"; // 하나의 멤버가져오기
import { member as alias } from "module-name"; //member 이름이 길 경우 as 별명 가능 
// (다른 이름으로 멤버 가져오기)
import { member1, member2 } form "module-name"; // 여러개의 멤버 가져오기
import { member1, member2 as alias2, [...] } from "module-name";

// default
import defaultMember, { member [,[...]] } from "module-name";
import defaultMember, * as alias from "module-name";
import defaultMember from "module-name";
import "module-name";

/* 
   name : 가져온 값을 받을 객체 이름
   member, memberN : export 된 모듈에서 멤버의 이름
   defaultMember: export 된 모듈의 default 이름
   alias, aliasN : export된 멤버의 이름을 지정한 이름
   module-name: 가져올 모듈 이름 (파일명)
*/

 

// variables.js
const foo = 'bar';
const spam = 'effs'

// foo, spam을 다른 파일에서 사용할 수 있도록 export 해주었습니다.
export {foo, spam};

위에서 export  된 이름을 다른 파일에서 import 구문을 통해 가져온 뒤 사용할 수 있습니다.

// main.js

// variables 모듈에서 선언된 이름을 사용하기 위해 import 해주었습니다.
import { foo, spam } from './variables.js';

consoled.log(foo);
consoled.log(spam);

단순히 값을 저장하고 있는 변수뿐만 아니라 클래스도 export를 통해 여러 모듈에서 재사용할 수 있습니다.

// function.js

function add(x,y) {
 return x + y;
}

class Person {
//...
}

export {add, Person};

다른 모듈에 있는 이름을 사용하려면, 반드시 해당 모듈에서 이름을 export  해주어야 합니다.  export 해주지 않은 이름을 다른 모듈서 import 하면 의도대로 동작하지 않습니다. (모듈 실행 환경에 따라 에러가 날숟호 있고, 이름에 undifined 가 들어있을 수도 있습니다.)

// variables.js

const foo = 'bar'
// main.js
import { foo } from './variable';

console.log(foo); //에러가 나거나 , `undefined` 가 출력됨

 

엄격 모드

 

모듈은 할상 엄격 모드(use strict)로 실행됩니다. 선언되지 않은 변수에 값을 할당하는 등의 코드는 에러를 발생시킵니다.

<script type="module">
   a = 10; //에러
</script>

 

단 한 번만 평가된다

동일한 모듈이 여러 곳에서 사용되더라도 모듈은 최초 호출 시 단 한 번만 실행됩니다. 실행 후 결과는 이 모듈을 가져가려는 모든 모듈에 내보내집니다,

 

이런 작동 방식은 중요한 결과를 초래합니다.

alert 함수가 있는 모듈(alert.js) 을 여러 모듈에서 가져오기로 해봅시다. 얼럿 창은 단 한 번만 나타납니다.

// alert.js

alert("모듈이 평가 되었습니다!");

 

//동일한 모듈을 여러 모듈에서 가져오기

// first.js
import `./alert.js`; // alert 창이 출력된다

// second.js
import `./alert.js`; // 아무일도 발생하지 않는다

최상위 레벨 모듈을 대개 초기화나 내부에서 쓰이는 데이터 구조를 만들고 이를 내보내 재사용하고 싶을때 사용합니다.

객체를 내보내는 모듈을 만들어 봅시다.

// admin.js
export let admin = {
  name: "Gildong"
};

이 모듈을 가져오는 모듈이 여러 개이더라도 앞서 설명한 것처럼 모듈은 최초 호출 시 단 한 번만 평가됩니다. 이때 admin 객체가 만들어지거 이 모듈을 가져오는 모든 모듈에 admin 객체가 전달됩니다.

 

각 모듈에 동일한 admin 객체가 전달되는 것이죠.

// first.js
import {admin} from './admin.js'
admin.name = "Pete";

// second.js
import {admin} from './admin.js';
alert(admin.name); // Pete

// first.js 와 second.js 모두 같은 객체를 가져오므로
// first.js 에서 객체에 가한 조작을 second.js에서도 확인할 수 있습니다.

다른 이름으로 export & import 하기

export 혹은 import 하는 이름의 뒤에 as를 붙여서, 다른 이름이 대신 사용되게 할 수 있습니다.

const foo = 'bar';

export {foo as FOO}; // FOO 라는 이름으로  export 됩니다.
import {first as SECOND} from 'react'; // SECOND 라는 이름으로 import 됩니다.

 

모듈 사용시 주의할 점

주의할 점이 있다면. import 구문과 export  구문은 모듈 간 의존 관계를 나타내는 것일 뿐, 코드를 실행시키라는 명령이 아니라는 것입니다.

  • 같은 모듈을 여러 다른 모듈에서 불러와도, 모듈 내부의 코드는 단 한 번만 실행됩니다.
  • import  구문과 export 구문은 모듈의 가장 바깥쪽 스코프에서만 사용할 수 있습니다.
  • ECMAScript 공식 명세에는 모듈을 불러오는 방법에 대한 내용이 포함되어있지 않고, 이와 관련된 내용을 전적으로 모듈 구현체에 맡겼습니다. 따라서, 모듈을 어떤 환경에서 실행하느냐에 따라서 구체적인 로딩 순서나 동작 방식이 조금씩 달라질 수 있습니다.