Javascript 모듈

The boxer·2022년 4월 5일

javascript

목록 보기
2/2
  • 개발을 하는 과정에서 어플리케이션의 크기가 커지면 공통적으로 사용되는 기능 혹은 파일을 여러개로 분리해야 합니다.
  • 파일 단위로 분리된 소스코드를 모듈이라고 합니다.
  • 자바스크립트에서 공통적인 기능 및 크기가 큰 파일을 여러개의 모듈로 분리하여 사용하는 소스코드에서 불러오는 방식으로 사용합니다.

모듈의 scope

  • 모듈 내에서 선언된 변수들은 해당 모듈 내에서 접근 범위를 가집니다.
  • 해당 변수들은 해당 모듈 내에서만 접근이 가능하며 다른 모듈에서 해당 변수에 접근하기 위해서는 모듈을 불러와야 합니다.

모듈의 평가

  • 모듈을 사용하는 쪽에서 모듈을 로드하는 것으로 평가한다고 합니다.
  • 여러 소스코드에서 하나의 모듈을 여러번 평가해도 모듈을 한번만 평가됩니다.
  • 모듈을 평가하는 순간 해당 모듈내 모든 소스코드가 실행되며, 함수의 실행 및 변수 할당등이 실행됩니다.

    module.js

// 해당 부분은 여러 소스코드에서 여러번 불러와도 한번만 출력됩니다.
console.log('foo')

모듈 내보내기 / 불러오기

  • 소스코드에서 다른 모듈에서 정의된 리소스(변수, 함수 등)에 접근하기 위해서는 해당 모듈에서 다른 소스코드에서 사용할 수 있도록 리소스를 내보내기 해줘야 합니다.
  • 오직 모듈에서 내보내기로 지정된 리소스만 다른 소스코드에서 접근이 가능합니다.

모듈 시스템

1. commonjs

  • 서버사이드 어플리케이션(nodejs) 환경에서도 모듈을 로드할 수 있게 하는 방식입니다.
  • require 키워드를 사용하여 모듈을 내보하며 module.exports 혹은 exports 키워드를 사용하여 모듈을 불러옵니다.
  • ES6 방식이 점점 늘어나는 추세지만 아직까지 일부 실행환경에서는 ES6 모듈 방식을 지원하지 않는 경우가 많습니다.

commonjs 모듈 내보내고 불러오기

  • module.exports 혹은 exports 키워드를 사용하여 모듈을 내보냅니다.
  • require 키워드를 사용하여 지정된 모듈을 불러오며, 이는 로드한 모듈에서 내보내기된 리소스들을 object 형식으로 반환합니다.

exports vs module.exports

  • exports: module.exports 의 숏컷 버전으로 사용하며, 실질적으로 module.exports를 가리킵니다.
  • 참조 방향은 다음과 같습니다.
exports -> module.exports -> {}
  • 실제로 exportsmoduel.exports 를 출력해보면 빈 object가 출력됩니다.
console.log(module.exports)
console.log(exports)

// 출력결과
{}
{}
  • exportsmodule.exports 를 call by reference로 참조합니다.
  • 즉, exports의 값을 수정하면, module.exports 의 값이 변하게 되지만, module.exports 의 값을 수정해도 exports 의 값은 변하지 않습니다.
let foo = 'foo';

module.exports = { 'foo': foo };
console.log(module.exports)
console.log(exports)

// 출력 결과
{ foo: 'foo' }
{}

let foo = 'foo';

exports.foo = foo;
console.log(module.exports)
console.log(exports)

// 출력 결과
{ foo: 'foo' }
{ foo: 'foo' }
  • 다만, 내보내기시 최종적으로 리턴되는건 module.exports 입니다.

module.js

let foo = 'foo';

module.exports = { bar: 'bar' }
exports.foo = foo;

main.js

const m = require('./module');
console.log(m);

// 출력 결과
{ bar: 'bar' }
  • 주의할 점으로 exports 를 직접 수정하면 exports 가 가리키는 module.exports가 수정되기 때문에 직접 수정하지 않습니다다.

  • 따라서 다음의 상황에 따라 둘의 사용을 구분해서 사용한다

여러개의 객체를 각각 내보내는 경우 exports 를 사용한다.

하나의 객체를 내보내는 경우 module.exports 를 사용한다.

불러오기한 객체의 값을 수정한다면

  • 모듈에서 내보내기된 리소스들은 require 를 통해 반환된다고 했습니다.
  • 이는 내보내기된 리소스들은 사실상 해당 값 자체가 반환되는게 아니라 복사된 값이 반환된다고 볼 수 있습니다.

module.js

let foo = 'foo';

exports.foo = foo;

console.log(`module.exports in module.js: ${JSON.stringify(module.exports)}`);
console.log(`exports in module.js: ${JSON.stringify(exports)}`);

setTimeout(() => {
    console.log(`foo in module.js: ${foo}`);
}, 1000);

main.js

const m = require('./module');

console.log(`before change: ${JSON.stringify(m)}`);

m.foo = 'testing';
console.log(`after change: ${JSON.stringify(m)}`);
  • foo 라는 변수를 정의하여 module.js 에서 내보내기 합니다.
  • 그리고 1초 뒤에 foo를 출력합니다. 이는 main.js 에서 모듈을 평가하고 변수를 변경하는 시간을 기다리고 나서 출력하기 위함입니다.
  • 그리고 main.js 에서는 모듈을 로드하고 불러오기한 변수를 수정합니다.
  • 출력 결과는 다음과 같습니다.

  • exportmodule.exports에는 foo 라는 이름으로 지정된 변수 foo가 내보내기로 지정됩니다.
  • 이후 main.js 에서 foo 변수를 변경하면 main.js 에서는 변경된 값이 출력되지만, 원본 모듈에서는 변경되기 이전의 값이 출력되는 것을 볼 수 있습니다.
  • 즉, 불러오기한 값은 내보내기된 값과 다른 값이며, 불러오기 하는 과정에서 해당 값이 복사되어 불러오기 된다는 것을 생각할 수 있습니다.

2. ES6

  • exports 키워드를 사용하여 내보내고, import 키워드를 사용하여 불러오기를 하는 모듈 시스템입니다.
  • commonjs 에서는 module.exports 라는 객체에 내보내기할 리소스를 할당하는 방법을 사용했지만, ES6 에서는 해당 키워드로 내보내기할 리소스를 지정합니다.
export const foo = "foo";
  • 내보내기된 리소스는 불러오는 소스측에서 import 키워드를 사용하여 불러오며, 내보내기된 리소스 중 사용할 리소스만 선택하여 불러옵니다.
import { foo } from './module.js'

이름을 지정하여 내보내고 불러오기

  • 내보내기 하는 리소스 명을 지정하여 내보내는 방법입니다.
  • 리소스 명을 지정하며 해당 변수에 export 키워드를 붙입니다.
export const foo = "foo";
export const bar = () => "bar";
  • 혹은 여러 리소스 를 선언 후 한번에 export 할 수도 있습니다.
const foo = "foo";
const bar = () => "bar";

export { foo, bar };
  • 내보내기된 리소스는 import 키워드를 사용하여 불러올 리소스만 불러오기 할 수 있습니다.
import { bar, foo } from './module.js';

console.log(foo);
console.log(bar());

// 출력 결과
foo
bar
  • 불러오기시 모든 리소스를 가져오는 경우 * 을 사용하며 해당 모듈에 이름을 지정하여 불러옵니다.
  • 이러한 방법으로 불러오기시, 내보내기된 모든 리소스는 object 형식으로 반환됩니다.
import * as t from './module.js';

console.log(t.foo);
console.log(t.bar());

// 출력 결과
foo
bar

익명으로 내보내고 불러오기

  • default 키워드를 사용하며, 하나의 리소스를 반환하고자 하는 경우 사용합니다.
export default function () {
    return "foo";
};
  • 내보내기된 리소스는 import시 이름을 지정하여 불러와 사용합니다.
import foo from './module.js';
console.log(foo());

// 출력 결과
foo
  • 오브젝트 형식으로 내보내고 불러오기
export default {
    foo: () => "foo",
    bar: () => "bar",
}
import baz from './module.js';
console.log(baz.foo());
console.log(baz.bar());

// 출력 결과
foo
bar
  • 이 때, 내보내기 하는 모듈에서의 네이밍은 상관 없으며, 불러오기 하는 측에서 이름을 지정하여 사용합니다.
let foo = "foo";
export default foo;
import bar from './module.js'
console.log(bar);

// 출력 결과
foo

섞어서 사용하는 방법

  • 두 방식을 섞어서 하나의 리소스는 익명으로 나머지 리소스는 이름을 지정하여 내보낼 수 있습니다.
let foo = "foo";
let bar = () => "bar";

export {
    foo,
    bar
}

export default "baz";
import testing, { bar, foo } from './module.js'

console.log(foo);
console.log(bar());
console.log(testing);

// 출력 결과
foo
bar
baz
  • foo, bar 는 네이밍을 지정하여 내보내기 후 불러오기 하였으며, "baz" 라는 리소스는 익명으로 내보내기 후 testing 이란 네이밍으로 불러오는 상황입니다.
  • 모든 리소스를 불러오는 경우 마찬가지로 * 을 사용하며, 이름을 지정하여 object 형식으로 불러옵니다.
  • 이 때, 익명으로 반환된 리소스는 default 키워드로 접근합니다.
import * as t from './module.js'

console.log(t.foo);
console.log(t.bar());
console.log(t.default);

참고

profile
안녕하세요

0개의 댓글