본문 바로가기
나의 개발일지/node.js

[nodejs | 노드js] 모듈(module), 내장 객체

by stella_gu 2022. 7. 27.

▼ 모듈

특정한 기능을 하는 함수나 변수들의 집합

 

  • 자바스크립트에서 코드를 재사용하기 위해 함수로 만드는 것처럼 모듈을 만들어 여러 프로그램에서 해당 모듈을 재사용 할 수 있다.
  • 여러 파일에 걸쳐 재사용 되는 함수나 변수를 모듈로 만들어 두어 편리하게 관리할 수 있지만, 모듈이 많아지고 모듈 간의 관계가 얽히게 되면 구조를 파악하기 어렵다는 단점도 있다.
  • require 함수나 module 객체는 따로 선언하지 않아도 사용할 수 있는 이유 : 노드에서 기본적으로 제공하는 내장객체이기 때문. 

 

cf) 자바스크립트 자체 모듈 시스템 문법 : 2015년 자바스크립트에서도 import/export라는 모듈 개념이 도입 되었지만, 브라우저에서는 구현 되지 않아 사용할 수 없었다가 크롬 60 버전부터 사용할 수 있게 되었다. 하지만 아직 노드의 require/module.exports와 완벽히 대응 되진 않는다.

  • const {odd,even} = require('./val');  →  import {odd, even} form './val';
  • modul.exports = {odd,even} ;  →  export default {odd,even} ; 

 

 

 

 

 

▼ 내장 객체

노드에서 기본적으로 제공하는 기능

 

1. global

브라우저의 window 객체와 비슷한 전역 객체 

위에서 require와 module을 따로 선언하지 않고 사용할 수 있는 이유는 global.require, global.module에서 global이 생략된 내장 객체이기 때문.

주의할 점은 global 객체는 남용하면 유지보수가 어려워지므로 global 객체에 속성을 대입하여 전역 객체를 만들기 보단, 모듈을 사용하자!

 

 

2. console

브라우저에선 window에 들어있던 console은 노드에선 global 객체 안에 들어 있으며, 브라우저의 console과 거의 비슷하다.

 

  • console.log(내용) : 평범한 로그를 콘솔에 표시.
  • console.dir(객체, 옵션) : 객체를 콘솔에 표시할 때 사용. 옵션의 colors를 true로 하면 콘솔에 색이 추가되어 보기 편해짐. depth는 객체 안의 객체를 몇 단계까지 보여줄지 결정(기본값은 2이다)
  • console.time(레이블) / console.timeEnd(레이블) : time과 timeEnd 사이의 시간을 측정.
  • console.error(에러 내용) : 에러를 콘솔에 표시함
  • console.trace(레이블) : 에러가 어디서 발생했는지 추적할 수 있음.
  • console.table(배열) : 배열의 요소로 객체 리터럴을 넣으면, 객체의 속성들이 테이블 형식으로 표현 됨.
const string = 'abc';
const number = 1;
const boolean = true;
const obj = {
  outside: {
    inside: {
      key: 'value',
    },
  },
};
console.time('전체시간');   // 전체시간 측정 시작
console.log('평범한 로그입니다 쉼표로 구분해 여러 값을 찍을 수 있습니다');
console.log(string, number, boolean);
console.error('에러 메시지는 console.error에 담아주세요');

console.table([{ name: '제로', birth: 1994 }, { name: 'hero', birth: 1988}]);

console.dir(obj, { colors: false, depth: 2 });
console.dir(obj, { colors: true, depth: 1 });

console.time('시간측정');   // 여기부터 시간측정 시작
for (let i = 0; i < 100000; i++) {}
console.timeEnd('시간측정');  // 시간측정 끝

function b() {
  console.trace('에러 위치 추적');
}
function a() {
  b();
}
a();

console.timeEnd('전체시간');  // 전체시간 측정 끝

console

 

3. 타이머

  • setTimeuot(콜백 함수, 밀리초) : 주어진 밀리초(1000분의 1초) 이후에 콜백 함수를 실행합니다.
  • setInterval(콜백 함수, 밀리초) : 주어진 밀리초마다 콜백 함수를 반복 실행합니다.
  • setImmediate(콜백 함수) : 콜백 함수를 즉시 실행합니다.

 

변수에 담아두면 clearTimeout()으로 타이머를 취소할 수 있다

const timeout = setTimeout(() => {
  console.log('1.5초 후 실행');
}, 1500);

const interval = setInterval(() => {
  console.log('1초마다 실행');
}, 1000);

const timeout2 = setTimeout(() => {
  console.log('실행되지 않습니다');
}, 3000);

setTimeout(() => {
  clearTimeout(timeout2);
  clearInterval(interval);
}, 2500);

const immediate = setImmediate(() => {
  console.log('즉시 실행');
});

const immediate2 = setImmediate(() => {
  console.log('실행되지 않습니다');
});

clearImmediate(immediate2);

console

  • setImmediate(콜백)과 setTimeout(콜백, 0)의 차이
    • 두 함수에 담긴 콜백 함수는 모두 이벤트 루프를 거친 뒤 즉시 실행된다.
    • 단, 파일 시스템 접근, 네트워킹 같은 I/O 작업의 콜백 함수 안에서 타이머를 호출하는 경우 setImmediate(콜백)이 먼저 실행된다.
    • 헷갈리지 않도록 setTimeout(콜백, 0)은 사용하지 말자!

 

 

 

4. _ _filename, _ _dirname

  • _ _filename : 현재 파일 경로
  • _ _dirname : 현재 폴더(디렉토리) 경로
  • cf) _ _ : double underscore, 줄여서 dunderscore/dunder로 부름

 

노드에서는 파일 사이에 모듈 관계가 있는 경우가 많아 현재 파일의 경로나 파일명을 알아야 할 때가 많다. 

console.log로 찍어보면 현재 파일 명과 파일 경로가 콘솔에 찍힌다.

console.log(__filename);
console.log(__dirname);

 

하지만 경로가 문자열로 반환되기도 하고, 운영체제에 따라 경로 구분자도 달라 보통은 path 모듈과 조합하여 쓴다.

 

※ 노드에서는 브라우저와 달리 컴퓨터의 파일에 접근할 수 있어서 바이러스가 심긴 폴더를 실행했다간 해커에게 정보를 다 빼앗길 수도 있으니 주의하자.

 

 

 

5. module, exports, require

  • module, exports
const odd = '홀수입니다';
const even = '짝수입니다';

module.exports = {
  odd,
  even,
};
exports.odd = "홀수입니다";
exports.even = "짝수입니다";

위와 아래는 동일하다.

모듈을 만들 때 module.exports 말고도(module 객체 말고도), exports 객체로도 모듈을 만들 수 있다.

동일하게 동작하는 이유는 module.exports와 exports가 같은 객체를 참조하기 때문이다.

실제 console.log(module.exports === exports)를 하면 true가 반환 된다.

 

 

exports.odd = "홀수입니다";
exports.even = "짝수입니다";

module.exports = {};

위의 경우 module.exports에 새로운 객체를 대입해버려 기존 exports와 참조 관계가 끊겨버림.

 

※ exports 객체 사용 시 module.exports와의 참조 관계가 깨지지 않도록 주의해야 함.

module.exports에는 어떤 값이든 대입이 되지만, exports에는 반드시 객체처럼 속성명과 속성값을 대입해야 한다.

exports에 다른 값을 대입하면 객체의 참조 관계가 끊겨 모듈로서의 기능을 잃음.

→ 한 모듈에 exports 객체와 module.exports를 동시에 사용하지 않는 것이 좋다. 

 

const odd = '홀수입니다';
const even = '짝수입니다';

this.odd = odd;
this.even = even;
  • this : 노드의 경우 전역 스코프의 this는 exports이자 module.exports이자 {}이다. 단, 전역 스코프 제외하곤 기존의 자바스크립트 this와 같다.
  • 전역 스코프의 this === exports이므로 위와 같이 써도 되지만 실제 저렇게 쓰지는 않음.

 

  • require
console.log('require가 가장 위에 오지 않아도 됩니다.');

module.exports = '저를 찾아보세요.';

require('./var');

console.log('require.cache입니다.');
console.log(require.cache);
console.log('require.main입니다.');
console.log(require.main === module);
console.log(require.main.filename);

console

  • require은 반드시 파일 최상단에 위치할 필요가 없다
  • module.exports도 반드시 파일 최하단에 위치할 필요가 없다.
  • require 함수를 변수에 대입하지 않고 불러오면, var.js 파일을 실행만 하고, 모듈로 exports 된 건 사용하지 않음 (해당 파일의 변수들을 가져오진 않음)
  • 한 번 require한 파일은 require.cache에 저장되어 다음 번에 사용할 때 새로 불러오지 않고 require.cache에 있는 것이 재사용 된다. (더 빠름)
  • require.cache의 속성을 제거하면 저장된 것 삭제 가능. but, 프로그램의 동작이 꼬일 수 있으므로 권장하지 않음
  • require.main은 노드 실행 시 첫 모듈을(실행한 파일을) 가리킨다. 위의 경우 node require.js로 실행했으므로 require.js가 require.main이 된다.
  • 현재 파일이 첫 모듈인지 알아보려면 require.main === module을 해보면 된다.
  • 첫 모듈의 이름을 알아보려면 require.main.filename으로 확인해보면 된다.

 

 

  • 순환 참조 (circular dependency)

[dep1.js]

const dep2 = require('./dep2');
console.log('require dep2', dep2);
module.exports = () => {
  console.log('dep2', dep2);
};

[dep2.js]

const dep1 = require('./dep1');
console.log('require dep1', dep1);
module.exports = () => {
  console.log('dep1', dep1);
};

[dep-run.js]

const dep1 = require('./dep1');
const dep2 = require('./dep2');

dep1();
dep2();
  • dep-run.js 파일을 실행하면, 코드가 위에서부터 실행되므로 require('./dep1')이 먼저 실행된다.
  • → 그럼 dep1.js에서는 require('./dep2')가 실행되고
  • → 다시 dep2.js에서는 require('./dep1')이 실행된다
  • → 그럼 다시 dep1.js로 가서 require('./dep2')가 실행되고 무한 반복 되는 것일까?

console

  • 실제 실행되는 걸 보면 dep1의 module.exports가 함수가 아니라 빈 객체로 표시된다.
  • 서로 참조하며 무한 반복 되는 현상을 순환 참조라 하며, 무한 사이클이 돌다가 컴퓨터가 멈춰버리는 걸 막기 위해 노드가 순환참조를 발견하면 이를 자동으로 빈 객체로 바꿔 버린다.
  • → 에러가 발생하지 않고 조용히 빈 객체로 변경되므로 예기치 못한 동작이 발생할 수 있어 순환 참조가 일어나지 않도록 구조를 잘 잡는 것이 중요하다.

 

 

6. process

  • 현재 실행 중인 노드 프로세스에 대한 정보를 담고 있다.
  • process.version : 설치된 노드의 버전
  • process.arch : 프로세서 아키텍처 정보 (arm, ia32 등)
  • process.platform : 운영체제 플랫폼 정보 (win, linux, darwin, freebsd 등)
  • process.pid : 현재 프로세스의 아이디 (프로세스를 여러 개 가질 때 구분할 수 있게 해줌)
  • process.uptime() : 프로세스가 시작된 후 흐른 시간 (단위 : 초)
  • process.execPath : 노드의 경로
  • process.cwd() : 현재 프로세스가 실행되는 위치
  • process.cpuUsage() : 현재 cpu 사용량

darwin : mac

 

▼ 아래는 process 객체 중 중요한 속성들이다.

 

· process.env : 시스템 환경 변수 (UV_THREADPOOL_SIZE, NODE_OPTIONS), 직접 환경 변수를 저장할 수 있어 서버나 DB의 비밀번호와 API 키 등 서비스의 중요한 키를 저장하는 공간으로 사용 됨.

 

 

· process.nextTick(콜백) : 이벤트 루프가 다른 콜백 함수들보다 nextTick의 콜백 함수를 우선 처리하도록 만듦.

setImmediate(() => {
  console.log('immediate');
});
process.nextTick(() => {
  console.log('nextTick');
});
setTimeout(() => {
  console.log('timeout');
}, 0);
Promise.resolve().then(() => console.log('promise'));

console

  • process.nextTick은 setImmediate나 setTimeout보다 먼저 실행된다.
  • 참고로 Promise 콜백도 다른 콜백들보다 우선시 되어 nextTick 다음으로 실행 됨.
  • process.nextTick과 Promise 콜백은 마이크로태스크(microtask)에 속함

 

 

· process.exit() : 실행 중인 노드 프로세스를 종료함. 

  • process.exit(0) : 정상 종료 (error 없이 꺼짐)
  • process.exit(1) : 비정상 종료 (error 발생으로 꺼짐) → 서버에서 에러 생겼을 때 에러 났다는 걸 알리며 종료하기 위해 사용

 

 

 

 

[참고] Node.js 교과서 https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=246391275&start=slayer

 

Node.js 교과서

프런트부터 서버, 데이터베이스, 배포까지 아우르는 광범위한 내용을 다룬다. 군더더기 없는 직관적인 설명으로 기본 개념을 확실히 이해하고, 노드의 기능과 생태계를 사용해보면서 실제로 동

www.aladin.co.kr