一文入門NodeJS
之前講ES6的時候有提過一部分Node的知識,簡單回顧下: ofollow,noindex">一文讀懂ES6
npm國內映象: https://npm.taobao.org
配置國內源: npm install -g cnpm --registry=https://registry.npm.taobao.org
然後就可以把 cnpm
當作 npm
來用了,比如之前的 React
元件案例:
-
cnpm install react
-
cnpm install react-dom
-
cnpm i babel-core@old
解除安裝安裝的包: npm uninstall -g uuid
用 npm
而不是 cnpm
常用引數說明:
-
i
是install
的簡寫 -
-g
是安裝到全域性環境中(預設是當前目錄) -
-D
新增為開發依賴(-D ==> --save-dev
開發環境) -
-S
新增為生產依賴(-S ==> --save
生產環境)- eg:
cnpm i express -S
- eg:
eg: cnpm init
之後:
PS:你把依賴包刪了也沒事,執行cnpm i就會會根據package.json自動安裝依賴包
課外閱讀:
npm如何管理依賴包的版本 https://www.jianshu.com/p/1470c5d7b8c3 禁止npm自動升級依賴包以及所有下級依賴包版本的方法 https://www.jianshu.com/p/f481cf9b0817
這個之前也說過,可以看看: VSCode and NoteBook for JavaScript/">JavaScript | NodeJS ,簡單說下:
每次F5執行的時候選一下 NodeJS
,或者新增一下除錯的配置檔案
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "啟動程式", "program": "${workspaceFolder}/${relativeFile}" } ] }
nodejs
用法和 js
基本一樣,只是多了些伺服器的模組,eg:
如果想同時執行 nodejs
和 html
再新增下配置就行了,比如:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:8080/${relativeFile}", "webRoot": "${workspaceFolder}" }, { "type": "node", "request": "launch", "name": "啟動程式", "program": "${workspaceFolder}/${relativeFile}" } ] }
想執行 nodejs
的 js
檔案就選擇啟動程式( 現在執行JS檔案的時候,F5即可自動切換成node )
-
${workspaceRoot}
VS Code當前開啟的資料夾 -
${file}
當前開啟的檔案 -
${relativeFile}
相對於workspaceRoot
的相對路徑 -
${fileBasename}
當前開啟檔案的檔名 -
${fileDirname}
所在的資料夾,是絕對路徑 -
${fileExtname}
當前開啟檔案的副檔名
1.3.Jupyter NoteBook
ijavascript
依賴於 Python2.7
,詳細過程: Jupyter NoteBook IJavaScript 配置
# 如果nodejs和npm沒安裝可以先安裝下 sudo apt-get install nodejs npm # 把ijavascript安裝到全域性環境中 sudo npm install -g ijavascript #安裝 ijsinstall
然後就和Python一樣用了:
這塊官方文件寫的很詳細,我就簡單說說,後面用到再詳細說
中文文件: http://nodejs.cn/api
官方文件: https://nodejs.org/api
文件: http://nodejs.cn/api/http.html
or https://nodejs.org/api/http.html
NodeJS既然作為伺服器,那得先有個伺服器的樣子,我們來個簡單的案例:
node // ES6語法:import http from "http"; (現在還沒能完全支援) const http = require("http") // 建立一個伺服器 let server = http.createServer((request, response) => { // 每次請求都會執行這個方法 console.log(request.url); response.write("<h1>Test NodeJS</h>"); response.end() // 告訴瀏覽器響應結束 }); // 伺服器啟動並監聽指定埠 server.listen(8080); // 這個和其他語言不一樣,直接監聽對應的埠
效果:
其他內容需要結合其他模組一起講解
文件: http://nodejs.cn/api/fs.html
or https://nodejs.org/api/fs.html
這個是IO對應的模組,推薦使用非同步方法,簡單看看:( xxxFileSync
是同步方式,不建議使用)
In [1]:
const fs = require("fs");
In [2]:
// 檔案讀取 fs.readFile("test.txt",(ex,data) => { // 如果檔案不存在就輸出錯誤資訊 if(ex){ console.log(ex); }else{ console.log(data.toString()); } }); console.log("[ReadFile非同步驗證]我出現就是非同步");
[ReadFile非同步驗證]我出現就是非同步 { Error: ENOENT: no such file or directory, open 'test.txt' errno: -2, code: 'ENOENT', syscall: 'open', path: 'test.txt' }
In [3]:
// 建立一個檔案 fs.writeFile("test.txt","文字內容",ex => { if(ex){ // 出錯就輸出info console.log(ex); } }); console.log("[WriteFile非同步驗證]我出現就是非同步");
[WriteFile非同步驗證]我出現就是非同步
In [4]:
// 檔案追加 fs.appendFile("test.txt","追加內容",ex => { if(ex){ // 出錯就輸出info console.log(ex); } }); console.log("[AppendFile非同步驗證]我出現就是非同步");
[AppendFile非同步驗證]我出現就是非同步
In [5]:
// 現在再讀著看看 fs.readFile("test.txt",(ex,data) => { // 如果檔案不存在就輸出錯誤資訊 if(ex){ console.log(ex); }else{ console.log(data.toString()); } });
文字內容追加內容
PS:如果檔案不是文字檔案,就不能 toString
了( data
預設是 buffer
型別)
node fs.readFile("知識星球.png", (ex, data) => { if (ex) { console.log("讀取錯誤:", ex); } else { console.log(data); // 看看buffer是啥樣的 fs.writeFile("test.png", data, ex => { if (ex) { console.log("複製錯誤:", ex); } }); } });
效果:
<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 02 10 00 00 02 61 08 06 00 00 00 10 7e 89 ed 00 00 20 00 49 44 41 54 78 01 ec bd 07 98 64 57 79 ... >
圖片照常開啟:
簡單小結一下:
- 讀取檔案:
fs.readFile("xxx", (ex, data) => { });
- 建立檔案:
fs.writeFile("xxx", data, ex => {});
- 追加檔案:
fs.appendFile("xxx", data, ex => {});
-
data
是buffer
型別,內建了:toString() toJSON()
In [6]:
// 看個案例 data = { "name": "小明", "age": "23" }; fs.writeFile("to.txt", data, ex => { if (ex) { console.log(ex); } }); fs.readFile("to.txt", (ex, data) => { if (ex) { console.log(ex); } else { console.log(data); console.log(data.toJSON()); console.log(data.toString()); console.log(data.toLocaleString()); } });
<Buffer 5b 6f 62 6a 65 63 74 20 4f 62 6a 65 63 74 5d> { type: 'Buffer', data: [ 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93 ] } [object Object] [object Object]
上面幾個方法(eg: readFile
)都是先把資料都快取到記憶體中,然後才回調,這樣比較浪費記憶體,對於大檔案不友好,so ==> 流走起
用法比較簡單,看個案例:
node const fs = require("fs"); let rs = fs.createReadStream("知識星球.png"); let ws = fs.createWriteStream("test.png"); // 可以這麼理解,rs是水龍頭防水的地方,寫反了也就出不了水了 rs.pipe(ws); // 建立一個管道,流從r端到w端
還有一些類似於監聽的事件:
node const fs = require("fs"); let rs = fs.createReadStream("知識星球.png"); let ws = fs.createWriteStream("test.png"); rs.pipe(ws); // 建立一個管道,流從r端到w端 // 可以理解為錯誤觸發的事件 rs.on("error", ex => { console.log("讀取失敗", ex); }); rs.on("end", () => { console.log("讀取完成"); }); ws.on("error", ex => { console.log("寫入失敗", ex); }); // 注意,寫入流完成不叫end ws.on("finish", () => { console.log("寫入完成"); });
文件: http://nodejs.cn/api/url.html
or https://nodejs.org/api/url.html
說 url
模組之前得先說下 querystring
模組
文件: http://nodejs.cn/api/querystring.html
or https://nodejs.org/api/querystring.html
這個是專門針對引數進行解析的,來個案例:
In [1]:
const querystring = require("querystring");
In [2]:
let jd_qs = "keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2"; // 咋一看,好像挺方便,但是有坑:看下一個demo let str = querystring.parse(jd_qs); console.log(str)
{ keyword: '空氣淨化器', enc: 'utf-8', qrst: '1', rt: '1', stop: '1', vt: '2', psort: '3', stock: '1', wtype: '1', cod: '1', click: '2' }
In [3]:
// 使用者請求一般都是類似於這樣的 let jd_url = "https://search.jd.com/Search?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2"; // querystring.parse 只是對?後面(不包括`?`)的引數進行解析(以`=`和`&`分隔) str = querystring.parse(jd_url); console.log(str);
{ 'https://search.jd.com/Search?keyword': '空氣淨化器', enc: 'utf-8', qrst: '1', rt: '1', stop: '1', vt: '2', psort: '3', stock: '1', wtype: '1', cod: '1', click: '2' }
注意:querystring.parse 只是對?後面(不包括?)的引數進行解析(以=和&分隔)
上面說下 querystring
只是一個鋪墊,基本上不太用, url
模組已經包含這個了:
In [1]:
const url = require("url");
In [2]:
// port=null說明是預設埠(http:80,https:443) let jd_url = "https://search.jd.com/Search?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2"; let str = url.parse(jd_url); console.log(str); // 發現query並沒有解析
Url { protocol: 'https:', slashes: true, auth: null, host: 'search.jd.com', port: null, hostname: 'search.jd.com', hash: null, search: '?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2', query: 'keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2', pathname: '/Search', path: '/Search?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2', href: 'https://search.jd.com/Search?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2' }
In [3]:
// 想要解析`query`,可以多傳一個引數 str = url.parse(jd_url, true); console.log(str); // 對query解析
Url { protocol: 'https:', slashes: true, auth: null, host: 'search.jd.com', port: null, hostname: 'search.jd.com', hash: null, search: '?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2', query: { keyword: '空氣淨化器', enc: 'utf-8', qrst: '1', rt: '1', stop: '1', vt: '2', psort: '3', stock: '1', wtype: '1', cod: '1', click: '2' }, pathname: '/Search', path: '/Search?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2', href: 'https://search.jd.com/Search?keyword=空氣淨化器&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&psort=3&stock=1&wtype=1&cod=1&click=2' }
PS:一般都是這麼用的:
node // {a, b} = {a:21,b=34,c=22} 只要對應即可解包,如果想取別名可以使用:{a:xx, b} = {...} let { pathname, query } = url.parse(request.url, true);
3.前幾個模組的綜合案例
結合上面的 HTTP模組
,來個簡單的 web伺服器
:
node const fs = require("fs"); const url = require("url"); const http = require("http"); // 建立服務 let server = http.createServer((request, response) => { // 請求 // {a, b} = {a:21,b=34,c=22} 只要對應即可解包,如果想取別名可以使用:{a:xx, b} = {...} let { pathname, query } = url.parse(request.url, true); console.log(query, pathname); // 讀取對應檔案 fs.readFile(`www${pathname}`, (ex, data) => { if (ex) { // 返回404狀態碼,並設定編碼為UTF-8 response.writeHeader(404, { "Content-Type": "text/html;charset=utf-8" }); // 提示需要在 writeHeader 之後,不然訪問的是瀏覽器404頁面 response.write("<h1>訪問的頁面不存在~</h1>"); } else { response.write(data); } // 響應結束 response.end(); }); }); // 伺服器啟動並監聽指定埠 server.listen(8080);
輸出:( www
目錄就兩個檔案,一個 test.html
,一個 test.png
)
推薦寫法:
node const fs = require("fs"); const url = require("url"); const http = require("http"); let server = http.createServer((request, response) => { let { pathname } = url.parse(request.url, true); console.log(pathname); let rs = fs.createReadStream(`www${pathname}`); // `request`和`response`就是一個典型的讀寫流(`ReadStream`、`WriteStream`) rs.pipe(response); // 讀取失敗 ==> 404 rs.on("error", ex => { response.writeHeader(404); response.write("404 Not Found"); response.end(); }); }); server.listen(8080);
PS:request和response就是一個典型的讀寫流(ReadStream、WriteStream)
文件: http://nodejs.cn/api/zlib.html
or https://nodejs.org/api/zlib.html
先看個案例:( zlib
是讀寫流)
node const fs = require("fs"); const zlib = require("zlib"); // 讀寫流 let gz = zlib.createGzip(); // 讀流 let rs =fs.createReadStream("./www/jquery-2.1.1.js"); // 寫流 let ws =fs.createWriteStream("test.js.gz"); // 可以這麼理解:(gz是讀寫流) // rs水龍頭先傳給了gz,gz又當一個水龍頭傳給了ws rs.pipe(gz).pipe(ws); ws.on("finish",()=>{ console.log("寫入完畢"); });
效果:
結合上面再來個加強版:
node const fs = require("fs"); const zlib = require("zlib"); const http = require("http"); let server = http.createServer((request, response) => { // let { pathname } = url.parse(request.url); console.log(request.url); let rs = fs.createReadStream(`www${request.url}`); let gz = zlib.createGzip(); // 響應之前告訴瀏覽器是gzip的格式 response.setHeader("Content-Encoding", "gzip"); // 返回gzip壓縮後的檔案 rs.pipe(gz).pipe(response); // 讀取失敗,404錯誤 rs.on("error", ex => { response.removeHeader("Content-Encoding"); // 返回404狀態碼,並設定編碼為UTF-8 response.writeHeader(404, { "Content-Type": "text/html;charset=utf-8" }); // 提示需要在 writeHeader 之後,不然訪問的是瀏覽器404頁面 response.write("<h2>您訪問的頁面不存在~</h2>"); response.end(); }); }); server.listen(8080, () => { console.log("伺服器啟動成功,埠:8080"); });
輸出對比:
文件: http://nodejs.cn/api/path.html
or https://nodejs.org/api/path.html
這個主要是針對路徑的模組,看個案例: path.parse()
In [4]:
const path = require("path");
In [5]:
let file_name = "./images/png/小明.png"; // 檔案路徑 ./images/png console.log(path.dirname(file_name)); // 提取出用 `/` 隔開的 `path` 的最後一部分 // 小明.png console.log(path.basename(file_name)); // 檔案字尾 .png console.log(path.extname(file_name)); // 檔案資訊 {root: "", dir: "./images/png", base: "小明.png", ext: ".png", name: "小明"} console.log(path.parse(file_name)); // 這個經常使用 // ------------------------------ // 當前檔案所在資料夾絕對路徑 console.log(path.resolve()); // 檔案的絕對路徑 console.log(path.resolve(file_name));
./images/png 小明.png .png { root: '', dir: './images/png', base: '小明.png', ext: '.png', name: '小明' } /home/dnt/桌面/work/BaseCode/javascript/NoteBook/2.Node /home/dnt/桌面/work/BaseCode/javascript/NoteBook/2.Node/images/png/小明.png
In [6]:
// 可以看看path的完整資訊 console.log(path);
{ resolve: [Function: resolve], normalize: [Function: normalize], isAbsolute: [Function: isAbsolute], join: [Function: join], relative: [Function: relative], _makeLong: [Function: _makeLong], dirname: [Function: dirname], basename: [Function: basename], extname: [Function: extname], format: [Function: format], parse: [Function: parse], sep: '/', delimiter: ':', win32: { resolve: [Function: resolve], normalize: [Function: normalize], isAbsolute: [Function: isAbsolute], join: [Function: join], relative: [Function: relative], _makeLong: [Function: _makeLong], dirname: [Function: dirname], basename: [Function: basename], extname: [Function: extname], format: [Function: format], parse: [Function: parse], sep: '\\', delimiter: ';', win32: [Circular], posix: [Circular] }, posix: [Circular] }
文件: http://nodejs.cn/api/crypto.html
or https://nodejs.org/api/crypto.html
這塊主要就是用來加密(簽名)的,很簡單:
In [7]:
const cypto = require("crypto");
In [8]:
let obj = cypto.createHash("md5"); // // PS:分多次提交也一樣: // obj.update("123"); // obj.update("456"); obj.update("123456"); // e10adc3949ba59abbe56e057f20f883e console.log(obj.digest("hex"));
e10adc3949ba59abbe56e057f20f883e
In [9]:
let obj2 = cypto.createHash("sha1"); obj2.update("123456"); // 7c4a8d09ca3762af61e59520943dc26494f8941b console.log(obj2.digest("hex"));
7c4a8d09ca3762af61e59520943dc26494f8941b
In [10]:
let obj3 = cypto.createHash("sha256"); obj3.update("123456"); // 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 console.log(obj3.digest("hex"));
8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
文件: http://nodejs.cn/api/os.html
or https://nodejs.org/api/os.html
這塊是系統相關的模組,看個常用的案例:
In [11]:
const os = require("os"); // cup核數 console.log(os.cpus().length); // 輸出我老電腦CPU詳細資訊 console.log(os.cpus());
4 [ { model: 'Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz', speed: 2804, times: { user: 6287300, nice: 2400, sys: 1690400, idle: 80994500, irq: 0 } }, { model: 'Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz', speed: 2661, times: { user: 5714300, nice: 2800, sys: 1585700, idle: 83027300, irq: 0 } }, { model: 'Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz', speed: 2796, times: { user: 6217400, nice: 2700, sys: 1720400, idle: 82200300, irq: 0 } }, { model: 'Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz', speed: 2821, times: { user: 5779100, nice: 1900, sys: 1589800, idle: 82863800, irq: 0 } } ]
In [ ]:
可以參考我之前講的Python多程序進行對比學習: https://www.cnblogs.com/dotnetcrazy/p/9363810.html
node const os = require("os"); const process = require("process"); const cluster = require("cluster"); // 主程序:分配任務 if (cluster.isMaster) { console.log(`主程序PID:${process.pid}`); for (let i = 0; i < os.cpus().length; i++) { cluster.fork(); } } else { // 子程序執行任務 console.log(`當前程序PID:${process.pid},父程序PPID:${process.ppid}`); }
輸出:
主程序PID:20680 當前程序PID:20620,父程序PPID:20680 當前程序PID:23340,父程序PPID:20680 當前程序PID:11644,父程序PPID:20680 當前程序PID:22144,父程序PPID:20680
node const os = require("os"); const http = require("http"); const process = require("process"); const cluster = require("cluster"); // 主程序:分配任務 if (cluster.isMaster) { console.log(`主程序PID:${process.pid}`); for (let i = 0; i < os.cpus().length; i++) { cluster.fork(); } } else { // 子程序執行任務 http.createServer((request, response) => { console.log(`當前程序PID:${process.pid},父程序PPID:${process.ppid}`); response.write("Fork Test"); response.end(); }).listen(8080, () => { console.log(`伺服器啟動成功,當前埠:8080,程序PID:${process.pid}`); }); }
輸出:
PS:NodeJS注意下面幾地方:
- 子程序可以共用一個埠
- 併發的時候,如果不等到某個核的CPU100%,其他核的CPU就看戲(不工作)
這個是第三方模組,如果沒安裝:
-
cnpm init
-
cnpm i uuid -D
(開發環境) orcnpm i uuid -S
(生產環境)
使用案例:
node const uuid = require("uuid/v4"); let uid = uuid(); console.log(uid);
輸出:
65334387-110e-489a-b4c5-cb19cb3875d0