5. 노드 기능 알아보기 (1)

수원 개발자·2023년 12월 10일

Node.js 교과서

목록 보기
5/13
post-thumbnail

이 장에서는 노드와 처음으로 상호 작용을 해보고, 노드로 자바스크립트 파일을 실행하는 방법을 알아본다. 또한, 노드가 기본적으로 제공하는 객체와 모듈 사용법도 알아본다. 모듈을 사용하면서 중요한 개념인 버퍼와 스트림, 동기와 비동기, 이벤트, 예외 처리도 배울 것이다.

1. REPL 사용하기

입력한 코드를 읽고 (read), 해석하고(eval), 결과물을 반환하고(print), 종료할 때까지 반복한다(loop)고 해서 REPL이라고 한다.

터미널을 열고 node를 입력한다.

> const str = "Hello World, hello node";
undefined
> console.log(str);
Hello World, hello node
undefined // return 값이 undefined
>

여러분이 입력한 코드를 REPL이 읽고 해석한 뒤 바로 결과물을 출력했다.
그리고 종료되기 전까지 입력을 기다린다. REPL을 종료하려면 터미널을 종료하면 된다.

REPL은 한두 줄짜리 코드를 테스트해보는 용도로는 좋지만 여러 줄의 코드를 실행하기에는 불편하다. 긴 코드는 코드를 자바스크립트 파일로 만든 후 파일을 통째로 실행하는 것이 좋다.

2. JS 파일 실행하기

// helloWorld.js

function helloWorld() {
    console.log('Hello World');
    helloNode();
}

function helloNode() {
    console.log('Hello Node');
}

helloWorld();

이 파일을 만들어서 node [자바스크립트 파일 경로]로 실행한다.

$ node helloWorld

Hello World
Hello Node

파일로 실행하면 이렇게 잘 출력할 수 있다.

3. 모듈로 만들기

노드는 코드를 모듈도 만들 수 있다는 점에서 브라우저의 자바스크립트와는 다르다.
모듈이란 특정한 기능을 하는 함수나 변수들의 집합을 말한다.
모듈로 만들어두면 여러 프로그램에 해당 모듈을 재사용할 수 있다.
자바스크립트에서 코드 재사용을 위해 함수로 만드는 것과 비슷하다.

노드에서는 CommonJS와 ECMAScript 모듈을 사용한다.

3.1 CommonJS 모듈

표준 자바스크립트 모듈은 아니지만 노드 생태계에서 가장 널리 쓰이는 CommonJS 모듈이다. 표준 이전부터 쓰여서 표준보다 널리 쓰인다.

var.js , func.js , index.js를 같은 폴더에 만든다.

// var.js

// var.jsconst odd = 'CJS 홀수입니다';
const even = 'CJS 짝수입니다';

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

// func.js

const { odd, even } = require('./var');

function checkOddOrEven(num) {
  if (num % 2) { // 홀수면
    return odd;
  }
  return even;
}

module.exports = checkOddOrEven;

// index.js

const { odd, even } = require('./var');
const checkNumber = require('./func');

function checkStringOddOrEven(str) {
  if (str.length % 2) { // 홀수면
    return odd;
  }
  return even;
}

console.log(checkNumber(10));
console.log(checkStringOddOrEven('hello'));

module.exports에 변수들을 담은 객체를 대입했다. 이제 이 파일은 모듈로서 기능한다.

require 함수 안에 불러올 모듈을 적고 var.js에 있던 값들을 불러오고 있다. 또한 이를 이용해 숫자의 홀짝을 판별하는 함수를 선언했다. 그리고 다시 module.exports에 함수를 대입했다.

마지막으로 index.js에서 var.js와 func.js를 모두 참조한다. 모듈 하나가 여러 개의 모듈을 사용할 수 있다. 모듈로부터 값을 불러올 때 checkNumber라는 이름으로 checkOddOrEven을 사용했다.

index.js를 실행하면 결과는 다음과 같다.

$ node index
CJS 짝수입니다
CJS 홀수입니다

이번에는 모듈을 불러오는 require에 대해 알아보자.
require는 함수고 함수는 객체이므로 require는 객체로서 속성을 몇 개 갖고 있다.
그 중에서 require.cache와 require.main을 알아보겠다.

// require.js

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);

// require가 가장 위에 오지 않아도 됩니다.
//     require.cache입니다.
//     [Object: null prototype] {
//     '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/require.js': {
//         id: '.',
//             path: '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3',
//             exports: '저를 찾아보세요.',
//             filename: '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/require.js',
//             loaded: false,
//             children: [ [Object] ],
//             paths: [
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/node_modules',
//             '/Users/chaesuwon/node_modules',
//             '/Users/node_modules',
//             '/node_modules'
//         ]
//     },
//     '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/var.js': {
//         id: '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/var.js',
//             path: '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3',
//             exports: { odd: 'CJS 홀수입니다', even: 'CJS 짝수입니다' },
//         filename: '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/var.js',
//             loaded: true,
//             children: [],
//             paths: [
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/ZeroCho/node_modules',
//             '/Users/chaesuwon/PROGRAMMER/node_modules',
//             '/Users/chaesuwon/node_modules',
//             '/Users/node_modules',
//             '/node_modules'
//         ]
//     }
// }
// require.main입니다.
//     true
// /Users/chaesuwon/PROGRAMMER/ZeroCho/Node.js/Ch.3/3.3/require.js

이 예제에서 알아야 할 것은 require가 반드시 파일 최상단에 위치할 필요가 없고 module.exports도 최하단에 위치할 필요가 없다는 것이다.

require.cahce 객체에 require.js나 var.js 같은 파일 이름이 속성명으로 들어있는 것을 볼 수 있다. 속성값으로는 각 파일의 모듈 객체가 들어 있다. 한번 require한 파일은 require.cache에 저장되므로 다음 번에 require할 때는 새로 불러오지 않고 require.cache에 있는 것이 재사용된다. 만약 새로 require하기 원한다면 require.cache의 속성을 제거하면 된다.

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

function whatIsThis() {
  console.log('function', this === exports, this === global);
}
whatIsThis();

// {}
// true
// true
// function false true

this는 빈 객체가 나온다. 전역스코프의 this만 module.exports로 취급한다.

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

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

// 이렇게 dep1과 dep2가 서로를 참조하여 무한 순환참조를 하면 빈 객체를 생성해버린다.

3.2 ECMAScript

ES 모듈은 공식적인 자바스크립트 모듈 형식이다. 점점 ES 모듈을 사용하는 비율이 늘어나고 있다. 브라우저에서도 ES 모듈을 사용할 수 있어 브라우저와 노드 모두에 같은 모듈 형식을 사용할 수 있다는 것이 장점이다.

// var.mjs

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

// func.mjs

import { odd, even } from './var.mjs';

function checkOddOrEven(num) {
  if (num % 2) { // 홀수면
    return odd;
  }
  return even;
}

export default checkOddOrEven;

// index.mjs

import { odd, even } from './var.mjs';
import checkNumber from './func.mjs';

function checkStringOddOrEven(str) {
  if (str.length % 2) { // 홀수면
    return odd;
  }
  return even;
}

console.log(checkNumber(10));
console.log(checkStringOddOrEven('hello'));

// console
$ node index.mjs
MJS 짝수입니다
MJS 홀수입니다

표로 두 모듈 형식의 차이를 정리해봤다.

3.3 다이내믹 임포트

다이내믹 임포트는 CommonJS에서만 가능하다. 동적 불러오기라고도 부른다.

const a = false;
if (a) {
    require('./func');
}
console.log('성공');

if문이 false라서 require('./func')는 실행되지 않는다. 이렇게 조건부로 모듈을 불러오는 것을 다이내믹 임포트라고 한다.

ES 모듈에서 사용하려면 다음과 같이 import라는 함수를 사용해서 모듈을 동적으로 불러올 수 있다.

const a = true;
if (a) {
    const m1 = await import('./func.mjs');
    console.log(m1);
    const m2 = await import('./var.mjs');
    console.log(m2);
}

3.4 filename, dirname

노드는 파일 사이에 모듈이 있는 경우가 많다. 그로 인해 현재 파일의 경로나 파일명을 알아야 하는 경우가 있다. 노드는 filename, dirname이라는 키워드로 경로에 대한 정보를 제공한다.

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

ES 모듈에서는 이 대신 import.meta.url로 경로를 가져올 수 있다.

console.log(import.meta.url);
console.log('__filename은 에러');
console.log(__filename);

4. 노드 내장 객체

외우지는 말고 필요하면 다시 보는 식으로 공부하자.

4.1 global

global 객체는 브라우저의 window와 같은 전역 객체이며, 전역 객체이므로 모든 파일에서 접근할 수 있다.

이 객체 안에는 매우 많은 속성이 들어 있다.

4.2 console

브라우저의 console처럼 노드에서도 console이 있다.
이는 주로 디버깅을 위해 사용한다. 개발 중 변수에 값이 잘 들어있는지 위해 사용하기도 하고 에러 발생시 확인을 위해 사용한다.

4.3 타이머

타이머 기능을 제공하는 함수인 setTimeout, setInterval, setImmediate는 노드에서 window 대신 global 객체 안에 들어있다. 이는 타이머 기능을 제공한다.

4.4 process

process 객체는 현재 실행되고 있는 노드 프로세스에 대해 정보를 담고 있다.
process.env는 많은 시스템의 환경변수를 알 수 있다.
process.nextTick은 setTimeout이나 setImmediate보다 먼저 실행된다. 이벤트 루프가 다른 콜백 함수보다 nextTick의 콜백 함수를 우선으로 처리하도록 만든다.
process.exit은 실행중인 노드 프로세스를 종료한다.

1개의 댓글

comment-user-thumbnail
2023년 12월 10일

노드 js를 통해 풀스택 개발자가 되려고 하시는 모습 응원합니다! :)

답글 달기