此文章系列不是教學文章,因為我覺得我的能力不配去教其他人,但如果剛好你也在學習NodeJS,你可以參考我的學習歷程。

去年開始我因要寫寫Discord 機器人而需要使用NodeJS,
那個時候只學過Pyhon ,Discord JS又基於js,NodeJs, 誤打誤撞就開始學習這門語言了。

寫了一段時間後發現代碼寫的很糟,可讀性很差,於是想要重新編寫,並且這次打算使用TypeScript,即是Js的超集來重寫。

可是啟動一陣子後發現,使用TypeScript的寫法後出現了一堆沒看過的問題,要一一解決,
最後我發現我對NodeJS的理解太過膚淺,於是打算靜下心,
一步一步踏實地學習。

NodeJS不是一個編程語言

是一個可以運行JavaScript的環境,其他語言通常都不會混餚這個概念,因為通常一個程式語言下載後會自動安裝能運行該語言的環境。而JS因為早期是應用在網頁遊覽器,發展開來後才有自己的運行環境。

與NodeJS相似的有BunJs, Deno等。

官网

https://nodejs.org/en

安裝的時候建議下載左邊的版本,左邊的版本是穩定版,右邊的是最新版

以前我下載各種程式語言的時候都喜歡下載最新版,但是有一天碰到鐵壁的時候才發現要解決好麻煩,因為可能這個語言的庫還沒支持到最新版本。

安裝的話我就不寫了,應該有點動手能力吧。(笑

架構圖

這個圖是從稀土掘金挖過來的,我現在也沒打算深入的了解他的運作。

簡單來說

NodeJS 是建構在V8之上的,V8又是 C/C++ 編寫的,所以我們的代碼最終會轉化成C/C++後再執行

NodeJS 使用異步I/O 和 事件驅動的設計理念,可以高效處理大量並發請求,這些最終都是由上圖的libuv事件循環庫去實現的

NodeJS 適合幹一些IO密集型應用,不適合CPU密集型應用,非要做的話可以使用C++插件編寫,或者使用NodeJS提供的 cluster。
(CPU密集型指的是圖像的處理或者音頻處理需要大量數據結構+算法)

Three npm packages found opening shells on Linux, Windows systems | ZDNETNPM

NPM(全稱 Node Package Manager) NodeJS的包管理工具,
類似於

PHPComposer

JavaMaven,

Pythonpip,

RustCargo

NPM指令

下面收集了常用的指令

https://blog.dowouwu.com/archives/npm-pnpmzhi-ling-xue-xi

npm run 在干嘛?

他會從node_modules裡的.bin文件夾尋找執行指令所需的文件

尋找順序

  1. 當前項目的node_modules/.bin裡面去

  2. 全局npm/npm_modules裡面去找

  3. 去到系統環境變量

  4. 再没有就会报错

npm run 執行 package.json下的"scripts"

以下是package.json的一部分

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "prestart": "ts-node ./src/utils/register-command.ts",
    "start:build": "tsc-watch",
    "start:run": "nodemon ./dist/app.js",
    "start": "concurrently npm:start:*",
	"poststart": "blahblahblah"
  },

npm run <script> 可以自定義執行你想要執行的腳本命令

npm run <script> 的生命週期

pre<script>

post<script>

假如要執行的指令是npm run start

那麼在start執行前會自動執行prestart 指令

然後在start執行後會自動執行poststart指令

NPX

自从npm 5.2.0 版本以来,npx 就被预先捆绑在npm 中

npx是一個命令行工具,他的用途是

在不安裝全局包的情況下,運行npm裡的命令行,並且使用後會自動刪除。

模塊化

導入模塊

導入模塊有五種方式

1.引入自己寫的.js文件

require('./test.js');

2.引入第三方模塊

const md5 = require('md5');

3.引入NodeJS內置模塊

const fs = require('node:fs');

4.引入C++ 擴展 addon napi node-gyp .node

5.引入json文件

const data = require('./data.json');

ES6解構方式

這裡假設index.js裡面有data這個attribute或者函數。

index.js文件

module.exports={
	data: '我是夜羽'
}

文件b

const { data } = require('./index.js');
console.log(data);
// output {data: 我是夜羽}

導出模塊

CommonJS

module.exports={
	sucess:1,
	error:0
}

如果直接導出123,引入的話會直接是123

index.js文件

module.exports=123

文件b

const data = require('./index.js');
console.log(data);
// output 123

或者直接導出函數,這裡用的是箭頭函數

如果直接導出123,引入的話會直接是123

index.js文件

module.exports =()=>{
	return 123
}

文件b

const data  = require('./index.js');
console.log(data());
// output 123

以上都是commonjs規範,可以在package.json修改"type"進行更改。

{
  "name": "node-examples",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "commonjs"
  "scripts": {
    "test:unit": "jest --coverage",
    "test:onlychanged": "jest --onlyChanged --coverage",
    "test:markdown": "markdownlint . --ignore-path .gitignore",
    "lint": "standard",
    "test": "npm run lint && npm run test:unit && npm run test:markdown"
  },
 
}

ES Module

下面是使用esmodule的寫法

{
	"name": "blahblah",
	"type": "module"
}

如果使用module,就不能使用require來導入,會報錯
要換成import

file_a.js

export default {
	sucess: true,
	name: '夜羽owo'
}

file_b.js

import x from './file_a.js';
console.log(x);
// {sucess: true, name:'夜羽'}

export default 在同個文件只能存在一個

假如一個文件裡有好幾個函數,變量,可以使用 import * as

as 後面的all是可以自定義的,可以為那個文件取一個別名。

import * as all from 'file_a.js';
console.log(all);
// 展示文件裡所有的函數和變量

CommonJS和 ES Module的區別

1.在cjs如果使用this會指向模塊本身, 而ES Module會指向undefined,因為開啟了嚴格模式

2.cjs是運行時同步加載,esm而是編譯時異步加載

if(true){
	require('./a.js');
}
if(true){
	import xxx from 'xxx';
}
// XXX 無法運行

ES Module是不不支持同步引入,只能在頂部引入

import xxx from 'xxx';
if(true){
	return;
}
// XXX 無法運行

3.cjs引過來的值是可以修改的,esm的值是只讀,不可以改

4.cjs不支持 tree shaking,esm支持tree shaking
Tree Shaking 是個優化的方式,在 JavaScript 中用來表示移除沒用的程式碼的一個常見術語,之所以叫做 Tree Shaking 的由來似乎是指說「當你大力搖晃一棵樹的時候,樹上就只會留著綠色的葉子,其他枯葉都會落到地上」,而那些綠色的葉子就是打包過後的文件中,真正有用到的程式碼。

如果Import就想動態帶入怎麼辦?

是可以的,要使用import 函數模式。

if(true){
	import('./test.js').then(res=>{
		console.log(res)
	})
}