Node.js-2 (22/12/06)

nazzzo·2022년 12월 6일
0

1. 내장객체


브라우저의 전역객체는 window입니다
노드에도 이와 비슷한 기능을 하는 global이라는 이름의
전역객체가 존재합니다

이 전역객체들에는 특수목적의 함수(자주 사용되는)들이
담겨 있는데 이를 내장객체라고 부릅니다

내장객체에 대해 공부하려면
공식문서의 예제코드를 읽어보는 것이 가장 좋습니다
(https://nodejs.org/docs/latest-v17.x/api/)


내장객체는 다음과 같은 형태의 구조로 이루어져 있습니다


// 전역객체인 global
const global = {
    AbortController:class {
        // 괄호 안에는 내장객체의 기능이 담겨있습니다
    },
    Buffer:class {

    },
    __dirname: '',
    __filename: '',
    console: {
        log:function(){}
    },
    module 
}

우리가 자바스크립트를 배우면서
자주 사용해온 console.logglobal에 속한 내장객체이므로
global.console.log로 쓸 수 있습니다
(당연히 브라우저에서는 window.console.log가 됩니다)

<console 객체의 다른 속성>

  • console.dir() : 괄호안의 객체 정보를 출력합니다
    첫 번째 인자로 표시할 객체를 넣고, 두 번째 인자로 옵션을 넣습니다
    (colors, depth 등)
  • console.table() : 배열 정보를 테이블(표) 형태로 출력합니다

다음은 노드의 내장객체인 global.module에 대해 알아보겠습니다



1-1. require()


먼저 js파일을 생성합니다

[1.js]

const a = 100


module.exports = {
    name:'a'
}

module.exports = {
    name:'joo',
    age:32
}

// global > module > exports > { name:'joo',age:32 }

[2.js]

const a = require('./1.js')

console.log(a)

노드에서는 require() 메서드를 통해 외부 모듈을 불러올 수 있습니다
(*require()의 리턴값은 module.exports입니다)

그러면 이 경우에 2.js의 console.log(a)의 출력값은 어떻게 될까요?

답은 { name: 'joo', age: 32 }가 출력됩니다
(제일 하단에 쓰인 module.exports의 객체 데이터가
객체 틀을 덮어씁니다)

그리고 이 때 두 파일에 같은 변수명(const a)을 선언하고도
에러가 나지 않은 것은 requre()가 변수의 공유를 의미하지 않기 때문입니다

[1.js]와 [2.js]는 프로세스처럼 각각이 독립(분리)되어 있습니다,
대신 전역객체인 global은 속에 담긴 정보를 공유합니다

그래서 넘겨받은 모듈을 이런 식으로 사용하는 것이 가능합니다

[1.js]

const a = 100
module.exports = a

[2.js]

const a = 200
console.log(require('./1.js')+a)


// > 300
// 1.js의 변수 a와 2.js의 변수 a는 각기 다른 영역을 참조합니다



다른 케이스를 살펴보겠습니다

[1.js]

const b = require('./2.js')

module.exports = {
    name:'joo',
    age:32,
    b:b
}

[2.js]

const a = require('./1.js')

console.log(a)

// 이 경우 a의 출력값은?

이 때의 출력값은 서로가 서로를 호출하여 무한루프를 돌게 됩니다
(2.js가 1.js를 호출 > 1.js가 2.js를 호출....)

이러한 코드 구조를 '순환참조'라고 합니다

하지만 노드는 이 경우에도 경고문구를 덧붙일 뿐,
로그값은 제대로 출력해줍니다

왜일까요?

캐싱

메모이제이션 기법 :
연산한 결과물을 메모리에 저장하여 필요할 때 '결과값만'을 불러와
재사용합니다 (연산의 중복실행을 막기 위해 사용합니다)

require()도 메모이제이션와 비슷한 방식으로 구동됩니다
한번 실행한 연산 결과물을 캐시에 저장하여 불러오기 때문에
위의 순환참조 코드가 출력된 것도 마찬가지 이유입니다



1-2.module.exports


앞에서 살펴본 대로 module.exports
require() 메서드를 사용했을 때 반환 받는 변수입니다

module.exports는 생성될 시 기본값으로 하나의 빈 객체{}가 생성됩니다
그리고 module.exports에 대입한 변수는 이 빈 객체 틀 안에 담기게 됩니다

const a = {
    name:'joo',
    age:32,
}  

module.exports = a
console.log(module.exports)
// > { name: 'joo', age: 32 }

이런 식으로 노드에서는 다른 모듈을 가져올 때 require()메서드를,
모듈을 해당 스코프 밖으로 보낼 때에는 module.exports를 사용합니다
이것을 모듈화한다고 부르기도 합니다


그리고 module.exprotsexports로 생략이 가능합니다

이런 식으로 생략적 문법이 가능한 이유는

```js
const a = {}
const b = a
console.log(a === b) // true

console.log(exports === module.exports) // true

module.exprotsexports는 둘 다 같은 객체를 참조하고
있기 때문입니다. (정확히는 exports > module.exports,
module.exports는 빈 객체를 참조합니다)

따라서 exports 객체에 특정 함수를 집어넣은 뒤
module.exprots를 호출하더라도 같은 결과를 불러옵니다



예제 코드를 통해 module.exprots의 사용법을 더 알아보겠습니다

[3.js]

module.exports = {
    a:1,
    b:2,
}

[4.js]

const data = require("./3.js")
console.log(data)


// node 4
// > { a: 1, b: 2 }

여기서 [3.js]에 담긴 코드를
exports.a = 1
exports.b = 2

이렇게 바꾸어도 같은 결과값이 출력되는데요


보통 아래의 형태는 함수를 축약해서
require()로 불러올 파일에 넘길 경우에 주로 사용합니다

exports.a = (a,b) => a+b
exports.b = (a,b) => a-b



1-3. dirname, filename

console.log(__filename) 
// 코드를 실행한 파일이 담긴 절대경로를 출력합니다 (파일명까지)
// /Users/[유저명]/Documents/workspace/Node/221206/6.js

console.log(__dirname) 
// 코드를 실행한 파일이 담긴 디렉토리를 절대경로로 출력합니다
// /Users/[유저명]/Documents/workspace/Node/221206

주로 __dirname을 사용합니다



1-4. process


console.log(process)

파일을 실행한 프로세스의 정보를 출력합니다
(노드의 버전, 프로세스를 실행중인 플랫폼(OS), pid 등의
수많은 정보가 출력됩니다)

process 객체에서 주로 사용하는 속성들은 다음과 같습니다

console.log(process.env) // 특히 중요! 환경변수(OS변수)를 출력합니다 
console.log(process.pid)
console.log(process.arch)
console.log(process.platform)


// echo $path

console.log(process.env)echo $path와 사용상의 의미가 유사합니다

소스코드에는 알리고 싶지 않은 변수들을 담겨있는 경우가 있습니다
(암호, 키값 등의 데이터)

이러한 변수들은 주로 환경변수에 담기게 되는데,
이를 찾으려면 환경변수를 출력할 수 있어야 합니다

export name='joo'
// 이 때 export는 shell 명령어입니다 (module.exports와 무관)
// 위 변수는 환경변수에 저장됩니다 (띄어쓰기 X!)

echo $name
// 환경변수에 저장된 name을 불러옵니다
// NODE_ENV

// product (배포용)
// test (테스트용)
// development (개발용)
// 보통 이러한 목적에 따라 버전을 분리합니다

const ip = process.env.NODE_ENV === 'development' ? "a" : "b"
//development 파일이 있을 경우 a코드를, 아니면 b코드를 사용합니다

const env = process.env.NODE_ENV || 'development';
// NODE_ENV라는 환경변수가 없다면 'development' 파일을 사용합니다

일단 알아만 두기로...



2. 내장 모듈 ~ path


노드가 기본적으로 제공하는 모듈을 말합니다
내장 모듈을 적재적소에 잘 사용할수록 코드의 양도 크게 줄일 수 있습니다

Buffer, Stream, fs, path 등 여러가지 내장모듈이 있지만
이 중에서는 path를 특히 많이 사용합니다
(path : 폴더와 파일 경로를 쉽게 조작하도록 돕는 역할을 합니다)


require('path')

내장모듈인 path를 사용할 준비를 마칩니다
내장모듈은 경로를 적지 않고 모듈의 이름만 불러와서 사용합니다


  • path를 사용하는 이유에 대해

우선 윈도우와 리눅스는 경로의 형태가 다릅니다
(윈도우의 시작 디렉토리는 C:\\Users\, 리눅스는 home/[유저명]
심지어 경로를 구분할 때 /를 쓰는지, \를 쓰는지조차 다릅니다)

if (window) {
    path = `C:\\`
} else {
    path = `/`
}

따라서 각 운영체제에 맞게 경로설정을 달리 할 필요가 있는데
path는 이러한 경로설정의 귀찮음으로부터 해방시켜주는
고마운 모듈입니다

const path = require('path')
// 내가 실행한 위치를 출력 + 9.js


const newPath = path.join(__dirname, '9.js')
console.log(newPath)
// join()의 첫번째 인자값은 출력할 디렉토리 경로, 두번째 인자값은 파일명
// 변수(newPath)에 담아 리턴값을 출력합니다

path는 이런식으로 윈도우 환경과 리눅스 환경의 경로를
자동으로 변환시켜줍니다



3. 외장 모듈 ~ express


사용자가 커스터마이징한 모듈을 말합니다

전에 apt, brew라는 패키지 매니저에 대해 알아본 적이 있는데요
노드에도 위와 비슷한 기능을 가진 툴이 있습니다

npm: 자바스크립트 파일을 쉽게 다운받을 수 있도록 돕습니다
(주로 'http://npmjs.com' 에 자바스크립트 파일을 올려놓습니다)

npm이라는 툴은 노드를 설치할 때 기본적으로 같이 제공됩니다


server.js라는 파일을 만들어보겠습니다

npm init 
// 명령어를 입력하면 package.json 파일이 생겨납니다

// {
//   "name": "221206",
//   "version": "1.0.0",
//   "description": "",
//   "main": "server.js",
//   "scripts": {
//     "test": "echo \"Error: no test specified\" && exit 1",
//     "start": "node server.js"
//   },
//   "author": "kjh",
//   "license": "ISC"
// }


// npm install [패키지명]
npm install express
// 외장 모듈인 express를 설치합니다

// package.json파일과 함께 node_modules라는 디렉토리가 생겨납니다
// > 221206 > node_modules > express

// node_modules 디렉토리는 깃으로 관리할 때 반드시 .gitignore에 포함시켜야 합니다!

express?

노드에서 웹서버를 만들 수 있도록 돕는 마이크로 프레임워크입니다
express를 사용하면 간단하게 웹서버를 구축할 수 있습니다


(프레임워크와 라이브러리의 개념적 차이에 대해서는
다른 포스트에서 다뤄야 할 듯...)


[server.js]에 아래 코드를 적은 뒤
터미널에 명령어 node server를 입력합니다

const express = require('express')
// express 변수를 선언하고 'express' 함수값을 불러옵니다
const app = express()
// express 함수를 실행시켜서 나온 결과물을 객체인 app에 담습니다



app.get('/', (req,res) => {
    console.log(req.query)
    res.send('hello server!')
})
// (/) 하나는 ip하나만 적었을 때를 의미합니다
// 두번째 인자값에는 콜백함수를 호출합니다


app.listen(3000, ()=> {
    console.log('express server')
})
// 3000번 포트로 시작하면서 콜백함수를 호출합니다

터미널에 node server를 입력하면 서버가 실행됩니다

브라우저를 켜서 url에 'http://localhost:3000/'를 입력하면
실행중인 서버를 확인할 수 있습니다

+) 주소창에 쿼리스트링을 입력하면 터미널에 그 값이 반환됩니다

http://localhost:3000/?name=joo
// > { name: 'joo' }

(*서버를 종료시키려면 Ctrl + C 입력)




번외

잠시 지금까지 배운 내용을 정리합니다

1)
정적인 프로그램을 실행하면 동적인 상태인 프로세스가 됩니다
그리고 노드js는 사용자의 pc를 조작할 수 있는 프로그램입니다

노드 js에는 여러 내장객체(procses, console...)와
내장모듈(path, net...)가 담겨있습니다

그리고 이러한 내장객체와 내장모듈을 활용해서
사용자가 만든 외장모듈(express가 대표적)들도 존재합니다

외장모듈은 노드가 자체 제공하는 npm이라는 툴을 통해
쉽게 설치할 수 있습니다

2)
우리가 노드를 배우는 목적은 백엔드 작업 및
웹서버를 만들고 활용하기 위해서입니다

단순히 명렁어의 사용법만 알아도 당장의 활용은 가능하지만
응용을 위해 잠시 네트워크의 개념을 짚고 가는 것이 좋아보입니다
(선실행 > 개념습득, 선실행 > 문법공부)


네트워크

굳이 우리말로 번역하자면 관계 혹은 상호작용 쯤..

네트워크에 관련된 예를 하나 들어보겠습니다

A는 한국에, B는 미국에 거주합니다
A와 B가 소통하려면 두가지 조건이 필요합니다

  • 통신
  • 언어정립 (번역)

여기서 서로 이해가 가능한 언어로 대화할 수 있게끔
언어정립의 역할을 하는 것이 바로 프로토콜입니다
프로토콜이란 데이터의 통신을 할 때 그 규격을 정의하는 것
(텍스트 정렬)을 말합니다

그리고 이렇게 통신이 이루어지는 단계를 구조화한 것을 OSI 7계층이라고 부릅니다

(Physical > Date link > Network > Transaction > Session > ... )
아래에서부터 모든 조건을 만족해야 네트워크 통신이 이루어집니다

1게층은 물리접촉의 단계
2계층 'H/W' ~ Ethernet
3계층 'S/W' ~ Internet,
4계층 TCP/UDP : TCP는 순차적으로 데이터를 받습니다,
UDP는 무작위로 데이터를 받습니다 (우리는 보통 TCP만 사용합니다)
5계층 'Kernel' ~ SSL
7계층
Process ~ 어플리케이션 단계

각 단계들 중에서 우리가 알아야할 부분은
4계층의 TCP와 7계층의 프로세스입니다

그런데 통신을 위해 010101의 기계어를 텍스트로 번역하는 것은
생각보다 쉽지 않습니다

대문자 A는 10진수로 나타내면 65로 ,
소문자 a는 97로 표현합니다 (아스키(ASCII) 코드)

아스키코드는 1바이트(1byte=8bit)로
모든 문자를 표현하기 위해 만들어진 규약입니다
(대문자 A를 10진수인 65로,
다시 이진법인 1000001으로 변환하는 과정을 인코딩이라고 말하고
반대의 과정을 거칠 때는 디코딩이라고 합니다)

하지만 한글을 표현하려면 아스키 코드가 아닌 다른 글자셋이 필요합니다
그것이 유니코드입니다
유니코드의 각 문자들은 2바이트로 이루어져 있습니다
(프로그래밍에서 영어사용을 반강제하는 주된 이유...)

다음 시간에는 Buffer에 대해 알아보도록 하겠습니다
(raw 바이너리 데이터(2진수 코드)를 1바이트씩 나누어 저장합니다,
사용자가 읽기 어려운 2진수 코드를 16진수로 변환할 때 유용합니다)

0개의 댓글