본문 바로가기

Node.js

[코드잇] Node.js 기본 개념

1. 모듈이란?

 

모듈(module)

- 전체를 이루는 부품 하나하나

- JavaScript 파일 하나 

- 다른 모듈에 있는 함수를 사용하려면 그 모듈을 가져와야 함 = 모듈을 로드(load)

- require는 모듈을 로드해서 객체 하나를 리턴

- require 함수가 리턴하는 객체를 가져다 쓸 수 있도록 변수에 대입

 

 

2. 모듈 내부의 함수 공개하기 

- 모듈 내부의 것들을 외부로 공개해줘야 다른 모듈에서도 그것을 사용할 수 있음

 

math-tools.js

function add(a,b) {
   return a+b;
}

exports.add = add;

 

main.js

let m = require('./math-tools.js');

console.log(m.add(1,2));

 

3. 모듈 더 알아보기 

 

1) 모듈의 확장자인 .js를 꼭 붙이지 않아도됨 

let m = require('./math-tools');

2) 모듈의 경로를 정확하게 작성해야 함

 

3) require 함수가 리턴하는 객체는 상수로 대입하는 것이 좋음 

const m = require('./math-tools.js');

 - 모듈이 리턴한 객체를 변수로 받으면, 나중에 본인 또는 다른 개발자가 변수 m에 다른 값을 실수로 다시 저장하게 될 수도 있기 때문

 

4) 모듈 안의 모든 것들을 공개할 수 있음

 

4. 하나의 객체로 모아서 외부에 공개하기 

 

- exports = 모듈 안에 있는 것들을 하나씩 공개 

- module.exports = 공개하고 싶은 것들을 모은 객체를 외부에 공개 

 

math-tools.js

let calculator = {
    PI :3.14,
    add: (a,b) => a+b,
    subtract: (a,b) => a-b,
    multiply: (a,b) => a*b,
    divide : (a,b) => a/b,
};

module.exports = calculator;

 

main.js

const me = requir('./math-tools');

console.log(m.PI);
console.log(m.add(2,4));
console.log(m.subtract(1,2));
console.log(m.multiply(1,2));
console.log(m.divide(1,2));

 

5. exports와 module.exports(심화)

 

1) Module wrapper function 

 

- Node.js가 모듈을 로드할 때는 모듈 내의 전체 코드를 감싸주는 작업인 Module wrapper function을 해줘야 함 

(function (exports, require, module, __filename, __dirname) {
  // 모듈 코드
});
(function (exports, require, module, __filename, __dirname) {
  function add(a, b) {
    return a + b;
  }
  exports.add = add;
});

- Module wrapper function의 다섯 가지 인자 exports, require, module, __filename, __dirname에는 Node.js가 각각 알맞은 것들을 전달함

 -> 이 때문에 이 다섯가지 인자는, 우리가 직접 정의해준 적이 없더라도, 모듈 안에서 항상 자유롭게 접근 가능

function add(a, b) {
  return a + b;
}

console.log('exports ------------------------->');
console.dir(exports);
console.log('require ------------------------->');
console.dir(require);
console.log('module ------------------------->');
console.dir(module);
console.log('__filename ------------------------->');
console.dir(__filename);
console.log('__dirname ------------------------->');
console.dir(__dirname);

- 5가지 인자에 자유롭게 접근 가능 

 

exports 객체 

- 안에 아무 프로퍼티(속성)도 없는 텅 비어있는 객체 

module 객체 

- exports라는 프로퍼티가 있고, 그 프로퍼티가 빈 객체 하나를 가리키고 있음

- exports 객체와 module 객체의 exports 프로퍼티가 가리키는 객체는 '동일한' 객체. 그리고 우리가 모듈 내부의 것들을 외부로 공개하기 위해 exports나 module.exports를 썼던 것은 바로 이 객체에 접근하기 위해서 였던 것

 

- 이 객체가 다른 모듈에서 require 함수로 이 모듈을 로드할 때 리턴되는 객체 

 -> 다른 모듈에서 이 math-tools.js 모듈을 로드하면 require 함수는 'exports 객체 (= module 객체의 exports 프로퍼티가 가리키는 객체)'를 리턴 

const m = require('./math-tools');

 

2) 정리 및 주의할 점

 

- exports. 속성 = 값

- module.exports = 객체 

 

- require 함수는 'module 객체의 exports 프로퍼티가 가리키는 객체'를 리턴하기 때문에, exports 객체를 아예  새로 설정해버리면 안됨

 -> exports 키워드로는 'exports.속성 = ~'이런 식으로 해당 객체에 프로퍼티를 추가하는식으로만 사용 가능

 

 

6. 코어 모듈

 

- 모듈 중에는 가져다 쓸 수 있도록 이미 준비된 것들도 있음

- Node.js에서 모듈은 내가 직접 만든 모듈과 이미 만들어져 있는 모듈 두가지로 나눌 수 있음

- 이미 만들어져 있는 모듈은 코어 모듈서드파티 모듈로 나뉨

 

코어모듈

 - Node라는 실행 프로그램 안에 이미 포함되어 있는 모듈

 - Node만 설치하면 바로 사용 가능

 

 ex) fs는 파일이나 디렉토리에 관한 작업을 할 때 필요한 코어 모듈

const fs = require('fs');

let fileList = fs.readdirSync('.');
console.log(fileList);

ex) os는 컴퓨터에 설치된 운영체제 관련 정보들을 가져오는 코어 모듈

const os = require('os');

console.log(os.cpus());

 

서드파티 모듈

 - 여러 개발자 또는 회사들이 만들어서 인터넷의 공개 저장소에 제공하는 모듈들

 - 내가 만든 모듈도 아니고 노드에 내장된 모듈도 아닌 제 3자가 만든 모듈

 

 

7. Node.js와 브라우저의 차이 

 

1) 제공하는 API가 다름

 

 API

  - Application Programming Interface

  - 어떤 플랫폼이나 실행환경 등에서 제공하는 인터페이스 

  - 특정 환경에서 자유롭게 가져다 쓸 수 있는 함수나 객체 등을 의미 

 

- 브라우저와 달리 Node.js에서는 시각적으로 보여주는 UI 부분을 구현할 필요 없음

- UI에 관한 API들이 브라우저에는 존재하지만 Node.js에는 존재하지 않음

 

- window, document 같은 객체들도 Node.js에는 존재하지 않음

 

- Node.js에만 있는 API

 ex) 컴퓨터 제어 API (파일 생성, 컴퓨터 정보 확인,,,,)

 

2) 자바스크립트를 실행하는 엔진의 차이 

 

- Node.js의 경우 구글 크롬 브라우저에서 사용되는 v8 엔진을 자바스크립트 실행 엔진으로 사용

- 브라우저는 그 종류와 버전에 따라 엔진이 다름

 

 

8. 서드파티 모듈

 

서드파티 모듈(3rd party)

- 다른 개발자나 회사들이 만들어서 인터넷상의 공개 저장소에서 제공하는 모듈

- 서드파티 모듈을 사용하려면 그 서드파티 모듈을 인터넷상의 공개 저장소에서 내 컴퓨터로 가져와야 함

 

ex) cowsay 모듈 설치 

const cowsay = require('cowsay');

console.log(cowsay.say({
	text: "I love javascript",
}));

 

 

9. 서드파티 모듈 설치 후 생겨난 것들 

 

1) package-lock.json 파일

 

 - package-lock.json 파일에는 nodeStudy 디렉토리 안에 설치된 모든 서드파티 모듈들에 관한 정보가 기록되어 있음 

- dependencies라는 필드에는, 현재 nodeStudy 디렉토리에 설치된 서드파티 모듈들의 정보가 담겨 있음 

- dependencies는 '의존하고 있는 것들'이라는 뜻인데 , 이 프로젝트가 이 서드파티 모듈들에 의존하고 있기 때문에 이 필드 안에 그 정보들이 담긴 것

 

- 하나의 서드파티 모듈이 설치될 때는 그것이 의존하는 다른 서드파티 모듈들도 함께 설치됨

 cowsay 모듈 -> string-width 모듈 -> strip-ansi 모듈 -> ansi-regex 모듈, 이렇게 여러 단계에 걸쳐 

cowsay 모듈은 결국 ansi-regex라는 모듈에 의존  

 

2) node_modules 디렉토리 

 

 - node_modules 디렉토리는 서드파티 모듈들이 실제로 설치되는 공간 

- 모든 서드파티 모듈들은 각각 하나의 디렉토리 

- 모듈은 하나의 파일일 수도 있고, 하나의 디렉토리일 수도 있음

 

 

10. 비동기 프로그래밍과 콜백

 

동기 실행 

const fs = require('fs');
console.log('Start');

let content = fs.readFileSync('./new', 'utf8'); //동기 실행
console.log(content);

console.log('Finish');

- 첫 번째로 Start가 출력되고 나면 readFileSync 함수는 파일의 내용을 읽기 시작

- 파일 읽기가 끝나기 전까지는 절대 다음 코드로 넘어가지 않음

- 파일 읽기가 다 끝나고 content라는 변수에 파일 내용이 리턴되면 실행이 다음 코드로 넘어가고 Finish가 출력됨

 

비동기 실행

- 특정 작업이 완료되었을 때 실행할 콜백을 등록해두고 바로 다음 코드로 실행을 넘기는 것

const fs = require('fs');

console.log('Start');

fs.readFile('./new', 'utf8', (error, data) => { //비동기 실행
	console.log(data);
});

console.log('Finish');

- Start가 출력되고 나면 그 다음 readFile 함수가 실행

- readFile 함수가 실행되면 new 파일을 읽기 시작

- new 파일을 다 읽었을 때 콜백함수 등록

 콜백 : 어떤 작업이 완료되었을 때 실행될 함수 

- console.log 함수로 실행 흐름이 넘어감

- new 파일에 대한 읽기 작업이 완료되면 그제서야 readFile 함수에서 등록해줬던 콜백이 실행되는 것

 

- readFileSync 함수는 동기 실행 함수, readFile 함수는 비동기 실행 함수 

- Node.js는 비동기 실행 함수를 사용하는 것이 권장되는 실행 환경

 

 

11. 비동기 프로그래밍 정리 

 

const fs = require('fs');

let test = 1; // '첫 번째'

fs.readFile('./new', 'utf8', (err, data) => {
  test = 2; // '세 번째'
});

console.log(test); // '두 번째'

- readFile 함수가 비동기 실행되는 함수이기 때문에 일단 console.log(test)가 먼저 실행되고, 그 후에야 test 변수에 2가 대입

 

1) 비동기 실행에 자주 쓰이는 setTimeout 함수 

 

setTimeout(callback, milliseconds)

- milliseconds(1000분의 1초) 후에 callback 인자에 설정한 함수를 실행

setTimeout(() => {
  num = 2;
}, 1000);

-  1초 후에 num 변수에 2를 대입하라는 뜻 

- setTimeout 함수는 개발자가 직접 비동기 실행을 구현하고 싶을 때 사용할 수 있는 유용한 함수 

let num = 1; // 첫번째

setTimeout(() => {
  num = 2; // 세번째
}, 1000);

num = 3; // 두번째

console.log(num); //3

- num 변수에 1을 대입

- setTimeout 함수 실행 (1초 후에 실행될 콜백 설정)

- num 변수에 3을 대입

- num 변수의 값 출력 

- setTimeout에서 설정해둔 콜백 실행

 

2) Node.js에서의 비동기 실행

 

- 프로세스 (Process)가 하나의 실행 흐름이라면, 스레드(Thread)는 그 안에 있는, 더 작은 단위의 실행 흐름

 

-프로그램 실행 과정 

 (1) 하드디스크나 SSD에 저장되어 있던 프로그램의 내용을,

 (2) 메모리(memory)에 올려서 

 (3) CPU(Central Processing Unit)가 실행되도록 만듬

 

- 프로그램을 실행하면 실행흐름으로 하나의 '프로세스'가 생성되고 그 안에서 하나의 '스레드'가 실행중인 상태가 됨

ex)

 크롬 브라우저에서 영화 파일 하나를 다운로드 하고 (Thread-2), 최신 유행 음악을 재생하면서 (Thread-3),  구글 검색(Thread-4)을 하는 등 새로운 작업을 시작할 때 마다, 크롬 프로세스 안의 스레드 수는 하나씩 늘어남

- Node.js 내부에서의 비동기 실행 구현 방법 중 한가지 

 

(1) 스레드 1개 (메인 스레드)

 

 - 자바스크립트 코드 실행

 - 이때 오래 걸리는 작업A은 다른 스레드에 넘기기

 - 그리고 일단 그 다음 작업 B를 시작하기 

 - 작업 A가 완료되었다는 알림과 그 작업 결과를 받으면 

 - 작업 결과를 가지고 콜백 실행하기 

 

(2) 스레드 10개 

 

 - 메인 스레드가 요청한 작업 처리 하기 

 - 작업이 완료되면 끝났다고 메인 스레드에 알려주고, 작업 결과 전달하기 

 

- 메인 스레든느 빠르게 처리할 수 있는 작업들을 집중해서 '혼자'처리하고,

- 파일 읽기와 같이 시간이 오래 걸리는 작업들은 다른 스레드에 맡김

 

 

12. 비동기 프로그래밍과 이벤트 

 

- 이벤트를 사용하여 비동기 프로그래밍

- Node.js의 이벤트 = 어떤 일이 발생했음을 알리는 신호 

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => {
    console.log('Success!');
});

myEmitter.emit('test');

 - events라는 이름의 코어 모듈을 가져옴

 - events 코어 모듈은  EventEmitter 클래스 하나를 외부에 공개 

 - EventEmitter 클래스가 있어야 이벤트를 사용할 수 있음

 - EventEmitter 클래스로 myEmitter라는 객체 생성

 

 - on 메서드는 어떤 이벤트가 발생했을 때 실행할 콜백을 등록하는 함수

 - test라는 이벤트가 발생하면  Success라는 문자열이 출력되도록 콜백을 등록

 

 - emit 메서드로 test라는 이벤트를 발생

 

- Node,js에서 제공하는 주요 API들은 이벤트 기반 구조 위에서 작성되었고 그 구조에서는 특정 객체가 이벤트를 발생시키면 그 이벤트에 관한 콜백이 실행됨

 

 

13.  EventEmitter 객체 사용법

 

- EventEmitter 객체를 사용할 때는 이벤트를 발생시키기 전에 콜백 설정을 먼저 해줘야 함

- 하나의 이벤트에 대해 여러 개의 콜백을 설정할 수 있음

 -> 설정된 콜백들은 그 이벤트가 발생했을 때 설정했던 순서대로 하나씩 진행

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => {
    console.log('1');
});

myEmitter.on('test', () => {
   console.log('2');
});

myEmitter.on('test', () => {
   console.log('3');
});

myEmitter.emit('test');

- 이벤트를 발생시키고 그 이벤트에 대한 콜백이 실행되는 현상은 하나의 EventEmitter 객체 내에서만 이루어짐

const EventEmitter = require('events');

const myEmitter = new EventEmitter();
const myEmitter2 = new EventEmitter();

myEmitter.on('test', () => {
    console.log('1');
});

myEmitter.on('test', () => {
    console.log('2');
});

myEmitter2.on('test', () => {
    console.log('3');
});

myEmitter.emit('test');

 -> 1,2만 출력됨 

 

 

14. EventEmitter 객체 사용법 더 알아보기 

 

1) on 메소드 

 

 - on 메소드는 이벤트 핸들러를 설정하는 메소드 

 - on 메소드와 같은 용도를 가진 addListener라는 메소드도 있음

 

2) emit 메소드 

 

- emit 메소드는 인위적으로 이벤트를 발생시키기 위해 쓰는 메소드 

 

3) once 메소드 

 

- once 메소드는 특정 이벤트에 대한 이벤트 핸들러를 등록한다는 점에서 on 메소드와 유사한 메소드

- 그 이벤트 핸들러가 해당 이벤트에 대해서 딱 한번만 반응해서 실행되도록 함 

- once 메소드로 등록된 이벤트 핸들러는 한번 실행된 후에는 삭제됨

- 특정 이벤트가 어차피 한 번밖에 발생하지 않는 경우나, 이벤트 핸들러가 딱 한번만 실행되기를 바라는 경우 사용

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.once('test', () => {
  console.log('Success!');
});

myEmitter.emit('test');
myEmitter.emit('test');
myEmitter.emit('test');

- 'Success!'가 딱 한번만 출력됨

 

4) listeners 메소드 

 

- 특정 이벤트에 대한 이벤트 핸들러들을 출력해주는 메소드 

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.once('test', () => {
  console.log('A');
});

myEmitter.once('test', () => {
  console.log('B');
});

myEmitter.once('test', () => {
  console.log('C');
});

console.log(myEmitter.listeners('test'));

- test 이벤트의 총 3개의 이벤트 핸들러가 설정돼있다는 걸 알 수 있음

 

5) off 메소드 

 

- 이벤트 핸들러를 해제하는 메소드 

- 해제할 이벤트 핸들러를 정확히 지정해줘야 함

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => { // --- (A)
  console.log('Success!');
});

myEmitter.off('test', () => { // --- (B)
  console.log('Success!');
});

myEmitter.emit('test');

 

 

15. 이벤트에 추가 정보 함께 전달하기 

 

- 전달하고 싶은 내용들을 인자로 추가 

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => {
    console.log('Success!');
});

myEmitter.emit('test','apple', 'banana', 'pear');

- 콜백에서 추가 정보를 받는 방법

myEmitter.on('test', (arg1, arg2, arg3) => {
    console.log(arg1);
    console.log(arg2);
    console.log(arg3);
});

- 실무적으로 이벤트에 여러 인자를 주는 것보다는 여러 정보를 담고 있는 객체 하나만 전달하는 게 더 깔끔

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

const obj = {type: 'text', data: 'Hello Codeit', data: '2020-09-01'};

myEmitter.on('test', (info) => {
    console.log(info);
});

myEmitter.emit('test', obj);