-크로스 플랫폼 실행, 제한 없는 동작, 다양한 어플리케이션 개발.
-고성능 Js 발달과 V8 엔진의 등작으로 탄생.
vs. Js
-브라우저에서 실행, 웹 내부 제한된 동작, 웹 프론트 개발자의 언어
1)싱글 쓰레드
2)비동기
3)이벤트 기반
-비동기 동작의 완료를 처리하는 방법으로 특정 동작 실행 후 신경 쓰지 않음.
-대신 해당 동작이 완료될 경우 실행할 함수를 미리 등록, 동작 완료시 함수 실행.
요약 : 싱글 쓰레드이므로 비동기 동작 필요하고 이를 구현하기 위해 이벤트 기반으로 돌아감.
-기본적으로 동작방식에 둘 사이 큰 차이 없음.
1)콜스택 작동 순서
2)메시지큐 작동 순서
3)잡큐 작동 순서
-이벤트 루프는 비동기 동작의 실행 타이밍을 이해해야 한다.
-setTimeout은 콜스택이 비어있을 때 실행 된다.
-Promise는 상위함수가 종료되기 전에 실행 됨.
어플리케이션 유형
-플랫폼에 따라 다름 : 브라우저, 모바일 OS, 데스크탑 OS< IoT기기용 OS
-각 플렛폼에서 허용하는 언어를 사용해서 실행가능한 어플리케이션 개발
-혹은 중간다리 역할을 하는 실행환경(Runtime, JRE, Node.js)등을 두어서 원하는 언어로 어플리케이션 개발
-어플리케이션은 항상 시각적으로 보이진 않음. 보이지 않는 어플리케이션도 많음
-Node.js의 경우 보이지 않는 어플리케이션 개발을 할 때 사용
GUI와 TUI(CLI) : 컴퓨터와의 소통창 -1
-GUI(Graphical User Interface) : 그래픽/키보드/마우스/기타 IO 디바이스로 OS 또는 앱을 조작할 수 있도록 해주는 인터페이스. 일반인을 위한 인터페이스
-TUI/CLI(Text User INterface, Command-line Interface) : 오직 키보드(간혹 마우스)로 명령어를 타이핑해서 OS 또는 앱을 조작할 수 있도록 해주는 인터페이스. 개발자들이 사용해야하는 인터페이스.
-웹/모바일 FE는 GUI용 앱을 개발 : 시각적 프로그램 제작
-웹 BE는 TUI/CLI용 앱을 개발 : 유저에게 보이지 않아도 되는 로직을 수행하는 프로그램 제작(BE는 CLI 환경에 익숙해야한다)
Node.js 이전의 Js
-브라우저의 전유물, 웹페이지 작성에만 사용, 웹페이지 interactive하게 만들어주는 언어.
서버 사이드 Js
-Js를 브라우저가 아닌 OS에서 실행시키고자 하는 시도
-Isomorphic Js를 향한 목표.
Chromium의 V8 엔진 출현
-Js 엔진 중 오픈소스로 개발된 엔진
-OS에서 앱에 제공하는 다양한 기능(파일 시스템 접근, 네트워크 접근, 시스템 접근)들을 사용해서 브라우저에서는 불가능한 것들을 할 수 있게 됨.
Ryan Dahl의 Node.js
-V8엔진과 libuv(event loop), Node binding(c++라이브러리 집합)로 이루어진 자바스크립트 실행환경(Runtime)
-비동기 실행 방식을 기본적으로 지원하여 IO(input/output)작업 시 최고의 성능을 보여줌
-적절한 시점과 적절한 성능 그리고 NPM(Node.JS Package Manager)으로 구성된 생태계로 인기 얻음
-서버 모듈을 가져 자체적으로 서버 역할을 할 수 있어 간단한 서버 구축에 좋다
V8 엔진
-Js 코드 해석 후 작업 실행, 코드를 실행하는 것만 담당
libuv(event loop)
-c로 작성된 이벤트 루프(브라우저에 탑재된 것과는 다름)
-Node.js API 또는 C/C+ addon으로 실행된 비동기 작업이 마친 후 실행되어야 하는 콜백함수들을 각종 큐에서 정해진 순서에 맞게 꺼내와서 자바스크립트 엔진에게 전달해줌.
-싱글 스레드로 작동
-Node.js를 위해 개발되었으나 현재는 다른 언어들도 사용
Node.js API : Js/C/C++ 기반 라이브러리
-Node.js에서 제공하는 독자적인 API들
-브라우저가 아닌 일반 OS 환경에서 실행되기 때문에 브라우저의 Web API와는 다른 라이브러리들이 포함되어있음.
-Js에서 사용 가능한 API와 Node.js에서 사용 가능한 API들 구분이 필요함
-fs, path, crypto, Stream, zlib, child㏄process, EventEmitter
(1)Node.js API - fs
-비동기와 동기 메소드가 모두 구비되어있다.
-Promise를 반환하는 fs.promises 모듈이 별도로 있다.
-특정 파일을 읽어서 가공한 후 다른 파일에 쓰는 작업을 단계 별로가 아닌 모두 연결해서 공장 파이프라인처럼 처리할 수 있다.
이러한 것을 스트림 용어로 파이프라이닝(다른 곳에서도 많이 사용되는 용어)이라고 한다
// fs와 pipeline은 모두 promise 버전도 있으니 참고할 것!
const fs = require("fs");
const pipeline = require("stream").pipeline;
// 읽어오는 파일의 stream
const readStream = fs.createReadStream("./cities.txt", { encoding: "utf8" });
readStream.on("close", () => {
console.log("read stream이 닫혔습니다.");
//스트림 읽기, 다 읽고 나면 콘솔찍기
});
// 쓰는 대상 파일의 stream
const writeStream = fs.createWriteStream("./new_cities.txt", {
encoding: "utf8",
});
writeStream.on("close", () => {
console.log("write stream이 닫혔습니다.");
//스트림 사용, 다 쓰고 나면 콘솔찍기
});
async function main() {
// 두 stream을 연결해서(pipeline) 읽고 쓰기를 한 꺼번에 진행한다.
pipeline(readStream, writeStream, (error) => {
if (error) {
console.log(error);
return;
}
console.log("데이터 이전을 완료!");
});
}
main();
//작업이 완료되면 cities.txt를 복사한 new_cities.txt가 생성.
(2)Node.js API - path
-복잡할 수 있는 상대/절대 경로를 용이하게 생성할 수 있도록 도와준다
const path = require("path");
console.log("현재 파일 경로:", __dirname);
console.log("현재 파일 이름:", __filename);
console.log("현재 운영체제 경로 구분자:", path.sep);
// Mak , Linux => /
// window => \
console.log("현재 파일에 대한 정보:", path.parse(__filename));
//현재 파일 정보는 잘 안씀
console.log("현재 파일 이름+확장자:", path.basename(__filename));
console.log("현재 파일 이름만:", path.basename(__filename, ".js"));
console.log("현재 파일의 확장자:", path.extname(__filename));
console.log("현재 파일이 있는 디렉토리(폴더)", path.dirname(__filename));
console.log();
// 절대경로인지 체크
console.log(
`현재 경로${__dirname}가 절대 경로인가?`,
path.isAbsolute(__dirname)
);
console.log(
`현재 경로${".." + __dirname}가 절대 경로인가?`,
path.isAbsolute(".." + __dirname)
//..을 붙여 상대경로가 됐으니 false가 뜸
);
const someImage = "some_image.jpg";
console.log("윈도우에서만 유효한 경로:", __dirname + "\\" + someImage); // 안좋은 방식
console.log("모든 OS에서 유효한 경로1:", __dirname + path.sep + someImage);
console.log("모든 OS에서 유효한 경로2:", path.join(__dirname, someImage)); // 더 편한 방식
-각 소스 코드를 독립적인 개체(모듈)로 묶어서 관리하는 방식
-각 소스 코드 간의 scope 간섭을 없애고 각자의 소스 코드가 독립적인 scope을 갖도록 해줌
-ex) a.js와 b.js 사이 변수를 막 가져다 쓰지 못하게 함.
-용도/책임/역할 등에 따라서 코드를 분리해서 관리할 수 있음
(1) CommonJs
1)각 디렉토리의 index.js는 디렉토리 이름을 대표한다
-예를 들어 /src/sample/index.js 경로에 있는 파일에서 module.exports = { a: 1 }을 해놓으면 다른 파일(/src/app.js)에서
require("./sample");만으로 index.js에 있는 모듈을 사용할 수 있다
// ../sample/a.js
function sayA() {
console.log("This is A");
}
module.exports = {
sayA,
};
// ../sample/b.js
function sayB() {
console.log("This is B");
}
module.exports = {
sayB,
};
// ../sample/index.js
const a = require("./a");
const b = require("./b");
/**
* a 모듈과 b모듈을 index.js에서 export해줌으로서
* 해당 모듈들을 require하는 파일이 편하게 require할 수 있도록 할 수 있다.
*/
module.exports = {
a,
b,
};
// 위는 아래와 같다
// const a = require("./a");
// const b = require("./b");
// module.exports = {
// a: a,
// b: b,
// };
// sample 상위 디렉토리 index.js
const { a, b } = require("./sample");
a.sayA();
b.sayB();
// 위는 아래와 같다
// const sample = require("./sample");
// sample.a.sayA();
// sample.b.sayB();
const fs = require("fs");
const test = require("./test.json");
//require는 근처 파일 가져오기, fetch는 원격으로 가져오기.
//req가 더 제한적임. node.js는 axios 쓰기. fetch는 기능이 전부 작동x
console.log("require로 읽어온 json 파일");
console.log(test);
// fs로 파일을 읽으면 일반 string으로 읽어온다.
const testString = fs.readFileSync("./test.json", "utf8");
console.log("fs로 읽어온 json파일");
console.log(testString);
// JSON.parse로 해주어야 JS의 객체로 다룰 수 있다.
console.log(JSON.parse(testString));
2)index.js를 잘 활용하면 간결하게 require문을 작성할 수 있다
-예를 들어 /src/sample/index.js에서 sibling 파일들을 다 require하고 다시 module.exports로 export를 해주면 다른 파일 (/src/app.js) 에서 한 줄로 sample 디렉토리 아래에 있는 모듈들을 가져올 수 있다
3)json파일을 require해서 일반 객체처럼 사용이 가능하다. fs로 파일을 읽어서 JSON.parse를 쓰는 것보다 편하다
(2) 모듈 시스템 존재
(3) 모듈의 재사용 : package
const fs = require("fs");
const fsp = require("fs").promises;
const path = require("path");
const filename = "cities.txt";
const filePath = path.join(__dirname, filename); // 절대 경로
//__dirname : 예약어
// home/user/document/elice_sw/0328/JS/cities.txt 슬래쉬로 시작하면 절대 경로.
// ./../ <~ 상대경로
const newFilename = "web_101.txt";
const newFilePath = path.join(__dirname, newFilename);
//파일을 만들기 위한 코드
//home/user/document/elice_sw/0328/JS/web_101.txt 이런식으로 나옴.
function callCallbackStyles() {
// 콜백 방식으로 파일을 읽어오기
//readFile, writeFile은 비동기로 실행됨.
fs.readFile(filePath, "utf8", (error, data) => {
if (error) {
console.log(error);
return;
}
console.log(data);
});
// 파일 생성(overwrite), 콜백 방식
fs.writeFile(newFilePath, "javascript\n", "utf8", (error) => {
if (error) {
console.log(error);
return;
}
console.log("파일 생성 성공!");
});
}
async function callPromiseStyles() {
// 프로미스 방식으로 파일 읽어오기
await fsp
.readFile(filePath, "utf-8")
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
// 파일 생성(overwrite), 프로미스 방식
await fsp
.writeFile(newFilePath, "html\n", "utf8")
.then(() => console.log("파일 생성 성공!!"))
.catch((error) => console.log(error));
// 파일 이어쓰기
await fsp
.appendFile(newFilePath, "javascript\ncss", "utf8")
.then(() => console.log("파일 이어쓰기 성공!!"))
.catch((error) => console.log(error));
}
async function main() {
// callCallbackStyles();
callPromiseStyles();
}
main();