December 29, 2019
Node.js는 모듈 단위로 각 기능을 분할할 수 있다. 모듈은 파일과 1대1 대응 관계를 가지며 하나의 모듈은 자신만의 독립적인 실행 영역(Scope)를 가지게 된다. 따라서 Client-side JavaScript와 달리 전역변수의 중복 문제가 발생하지 않는다.
모듈은 독립적인 파일 스코프를 갖기 때문에 모듈 안에 선언한 모든 것들은 기본적으로 해당 모듈 내부에서만 참조 가능하다. 만약 모듈 안에 선언한 항목을 외부에 공개하여 다른 모듈들이 사용할 수 있게 하고 싶다면 module.exports
또는 exports
를 사용하면 된다.
그리고 공개된 모듈은 전역함수 require 함수를 사용하여 import할 수 있다.
module.exports
vs exports
Node.js가 모듈을 처리하는 방식을 간략하게 나타내보면 다음과 같다.
// wrapper → protects your code by having its own execution context
(function(exports, require, module, __filename, __dirname) { // your code (module)
module.exports = something; // initialize module.exports property
});
fn(module.exports, require, module, filename, dirname);
return module.exports; // require func's return value
모듈을 wrapper 함수로 감싸고 실행한뒤 최종적으로는 모듈에 들어있던
module.exports
의 값을 require 함수의 리턴값으로 돌려준다.
이 과정에서 module.exports
와 exports
사이에 다음과 같은 참조관계가 성립하게 되는데
exports ──참조──> module.exports ──참조──> { }
이 둘을 사용할때는 이러한 참조관계가 깨지지 않도록 염두하고 사용해야 한다.
이제 module.exports
와 exports
를 사용하는 방법을 알아보자.
module.exports
는
다음의 두 가지 방법으로 module.exports
를 사용할 수 있다.
방법 1. 공개(public) 프로퍼티/메서드 추가하기
방법 2. 직접 작성한 객체나 함수로 대체(replace)하기
calculate.js
const say = "hello";
module.exports.add = (a, b) => a + b;
module.exports.substract = (a, b) => a - b;
calculate.js은 독립적인 파일 스코프를 가지는 모듈이다. 기본적으로 내부의 코드는 모듈 내부에서만 참조할 수 있다. 하지만 add와 substract를 module.exports
의 메서드로 추가하면 add 함수와 substract 함수는 외부에 공개된다. (say는 여전히 calculate 모듈에서만 유효한 private 변수로 남아있다.)
이제 다른 파일에서 require 함수를 사용하여 임의의 이름으로 calculator 모듈을 import하고 calculator 모듈이 공개한 코드를 사용할 수 있다. require함수의 인자에서 .js와 같은 파일 확장자를 생략할 경우 자동으로 .js 확장자로 가정해서 처리한다. (따라서 .js 포맷이 아닌 파일의 경우 확장자를 반드시 명시해 주어야 한다.)
index.js
const { add, substract } = require("./calculator.js");
add(2, 3); // 5
substract(10, 1); // 9
module.exports
는 기본적으로 빈 객체를 참조하고 있다. ‘방법 1’이 이 빈 객체에 프로퍼티/메서드를 추가하는 방법이었다면 ‘방법 2’는 module.exports
가 참조하는 대상을 사용자가 정의한 새로운 객체 또는 함수로 바꾸는 것이다.
calculate.js
// 객체로 대체
module.exports = {
add(a, b) {
return a + b;
},
substract(a, b) {
return a - b;
},
};
// 함수로 대체
module.exports = function add(a, b) {
return a + b;
};
index.js
// 객체로 대체한 경우
const calc = require("./calculator");
calc.add(1, 2); // 3
calc.substract(10, 1); // 9
// 함수로 대체한 경우
calc(1, 1); // 2
module.exports
가 참조하는 대상을 클래스나 생성자 함수로 지정할 수도 있는데, 이렇게되면 다른 파일에서 해당 클래스/객체를 확장하여 재사용할 수 있다는 장점이 있다.
calculator-class.js
module.exports = class Calculator {
add(a, b) {
return a + b;
}
substract(a, b) {
return a - b;
}
};
upgrade-calc.js
const Calculator = require("./calculator-class");
class UpgradedCalculator extends Calculator {
multiply(a, b) {
return a * b;
}
devide(a, b) {
return a / b;
}
}
module.exports = new UpgradedCalculator();
index.js
const calc = require("./upgrade-calc");
calc.add(2, 2); // 4
calc.multiply(3, 3); // 9
exports ──참조──> module.exports ──참조──> { }
exports
는 module.exports
를 참조하고 있기 때문에 exports
를 사용해서 module.exports
를 좀 더 간결하게 표현할 수 있다는 장점이 있다.
module.exports.a = ... === exports.a = ...
module.exports
처럼 공개 프로퍼티/메서드를 추가하면 외부에서 require 함수를 통해 해당 모듈을 사용할 수 있는데
주의할 점은 module.exports
‘방법 2’ 처럼 참조대상을 직접 정의한 객체나 함수로 대체할 경우 기존의 참조관계가 깨지면서 더이상 모듈로 기능하지 않는다는 점이다.
다시 말해 외부에서 require 함수로 해당 모듈을 import할 수 없다.
// Right
exports.add = function(a, b) {
console.log(a + b);
};
exports.substract = function(a, b) {
console.log(a - b);
};
// Wrong!!
exports = function(a, b) {
console.log(a + b);
};
따라서 exports
를 사용할 때는 module.exports
와의 참조관계가 깨지지 않도록 주의해야 한다.
module.exports
에는 어떤 값이든 대입해도 되지만, exports
에는 반드시 객체처럼 속성명과 속성값을 대입해야 한다.