Create by jsliang on 2018-11-8 13:42:42
Recently revised in 2018-12-23 21:59:20
Hello 小夥伴們,如果覺得本文還不錯,記得點個贊或者給個 star,你們的贊和 star 是我編寫更多更精彩文章的動力!GitHub 地址
本文重點內容
- Node 基礎 - 通過對 Node 基礎的瞭解學習,打下 Node 基礎
- Node API - 開啟服務提供 API 給前端呼叫
- Node 連線 MySQL - 通過 npm 安裝 mysql,從而實現資料庫的連結
- Node 實戰 - 企業官網從 0 開始,打造能註冊、登入以及留言的企業官網
- Node 部署 - 如何通過部署雲伺服器,讓小夥伴們可以檢視到你的網站
本文延伸連結
本文成品演示
- Node 專案演示:jsliang 前端有限公司
一 目錄
不折騰的前端,和鹹魚有什麼區別
二 前言
本文主要目的:
- 整合 Node 基礎,加深 jsliang 對 Node 的學習瞭解,並且方便日後複習。
- 整合 Node 工具,方便查詢在 Node 開發中,有哪些工具比較有利於開發。
- 給初學 Node 的小夥伴做一個參考,如有疑問還請在 QQ 群:
798961601
中諮詢。
三 基礎
萬丈高樓平地起,地基還得自己起。
3.1 HTTP - 開始 Node 之旅
話不多說,先上程式碼:
01_http.js
// 1. 引入 http 模組
var http = require("http");
// 2. 用 http 模組建立服務
/**
* req 獲取 url 資訊 (request)
* res 瀏覽器返回響應資訊 (response)
*/
http.createServer(function (req, res) {
// 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往頁面列印值
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
// 結束響應
res.end();
}).listen(3000); // 監聽的埠
複製程式碼
那麼,上面程式碼,我們要怎麼用呢?
首先,將上面的程式碼複製貼上到 01_http.js
中。
然後,啟動 VS Code 終端:Ctrl + ~
。
接著,輸入 node 01_http.js
並回車。
最後,開啟 localhost:3000
:
OK,搞定完事,現在我們一一講解上面程式碼:
- 首先,我們需要先開啟仙人模式。哦,不是,是 HTTP 模式。我們都知道,像 PHP 這類老牌子的後端語言,需要 Apache 或者 Nginx 開啟 HTTP 服務。然而我們的 Node 不需要:
var http = require("http");
複製程式碼
- 然後,開啟 HTTP 服務,並設定開啟的埠:
/**
* req 獲取 url 資訊 (request)
* res 瀏覽器返回響應資訊 (response)
*/
http.createServer(function (req, res) {
// ... 步驟 3 程式碼
}).listen(3000); // 監聽的埠
複製程式碼
- 接著,我們設定 HTTP 頭部,並往頁面列印值,最後結束響應:
// 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往頁面列印值
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
// 結束響應
res.end();
複製程式碼
- 最後,我們往瀏覽器輸入
http://localhost:3000/
,將訪問到我們開啟的 Node 服務,從而往頁面渲染頁面。
至此,小夥伴們是不是也開啟了自己的 Node 之旅?
3.2 URL 模組
URL 模組是什麼呢?
我們在控制檯(終端)開啟 Node 模式,並列印出 url
來看一下:
好傢伙,它有 Url
、parse
、resolve
、resolveObject
、format
、URL
、URLSearchParams
、domainToASCII
、domainToUnicode
這麼多模組。
那麼,這些模組都有什麼用呢?
話不多說,先上程式碼:
02_url.js
// 1. 引入 url 模組
var url = require("url");
// 2. 引入 http 模組
var http = require("http");
// 3. 用 http 模組建立服務
/**
* req 獲取 url 資訊 (request)
* res 瀏覽器返回響應資訊 (response)
*/
http.createServer(function (req, res) {
// 4. 獲取伺服器請求
/**
* 訪問地址是:http://localhost:3000/?userName=jsliang&userAge=23
* 如果你執行 console.log(req.url),它將執行兩次,分別返回下面的資訊:
* / ?userName=jsliang&userAge=23
* / /favicon.ico
* 這裡為了防止重複執行,所以排除 req.url == /favicon.ico 的情況
*/
if(req.url != "/favicon.ico") {
// 5. 使用 url 的 parse 方法
/**
* parse 方法需要兩個引數:
* 第一個引數是地址
* 第二個引數是 true 的話表示把 get 傳值轉換成物件
*/
var result = url.parse(req.url, true);
console.log(result);
/**
* Url {
* protocol: null,
* slashes: null,
* auth: null,
* host: null,
* port: null,
* hostname: null,
* hash: null,
* search: '?userName=jsliang&userAge=23',
* query: { userName: 'jsliang', userAge: '23' },
* pathname: '/',
* path: '/?userName=jsliang&userAge=23',
* href: '/?userName=jsliang&userAge=23' }
*/
console.log(result.query.userName); // jsliang
console.log(result.query.userAge); // 23
}
// 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往頁面列印值
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
// 結束響應
res.end();
}).listen(3000);
複製程式碼
在上面的程式碼中:
首先,我們引入該章節的主角 url
模組:
// 1. 引入 url 模組
var url = require("url");
複製程式碼
然後,我們引入 http
模組:
// 2. 引入 http 模組
var http = require("http");
複製程式碼
接著,我們建立 http
模組,因為 url
的監聽,需要 http
模組的開啟:
// 3. 用 http 模組建立服務
/**
* req 獲取 url 資訊 (request)
* res 瀏覽器返回響應資訊 (response)
*/
http.createServer(function (req, res) {
// ... 第 4 步、第 5 步程式碼
// 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往頁面列印值
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
// 結束響應
res.end();
}).listen(3000);
複製程式碼
最後,我們訪問我們給出的地址:http://localhost:3000/?userName=jsliang&userAge=23
,並通過它檢視 url
的 parse
模組怎麼用,輸出啥:
// 4. 獲取伺服器請求
/**
* 訪問地址是:http://localhost:3000/?userName=jsliang&userAge=23
* 如果你執行 console.log(req.url),它將執行兩次,分別返回下面的資訊:
* / ?userName=jsliang&userAge=23
* / /favicon.ico
* 這裡為了防止重複執行,所以排除 req.url == /favicon.ico 的情況
*/
if(req.url != "/favicon.ico") {
// 5. 使用 url 的 parse 方法
/**
* parse 方法需要兩個引數:
* 第一個引數是地址
* 第二個引數是 true 的話表示把 get 傳值轉換成物件
*/
var result = url.parse(req.url, true);
console.log(result);
/**
* Url {
* protocol: null,
* slashes: null,
* auth: null,
* host: null,
* port: null,
* hostname: null,
* hash: null,
* search: '?userName=jsliang&userAge=23',
* query: { userName: 'jsliang', userAge: '23' },
* pathname: '/',
* path: '/?userName=jsliang&userAge=23',
* href: '/?userName=jsliang&userAge=23' }
*/
console.log(result.query.userName); // jsliang
console.log(result.query.userAge); // 23
}
複製程式碼
從中,我們可以看出,我們可以通過 query
,獲取到我們想要的路徑欄位。
當然,上面只講解了 parse
的用法,我們可以將上面程式碼中 if
語句裡面的程式碼全部清空。然後,輸入下面的內容,去學習 url
模組更多的內容:
- url 模組所有內容:
console.log(url);
/**
* Console:
{
Url: [Function: Url],
parse: [Function: urlParse], // 獲取地址資訊
resolve: [Function: urlResolve], // 追加或者替換地址
resolveObject: [Function: urlResolveObject],
format: [Function: urlFormat], // 逆向 parse,根據地址資訊獲取原 url 資訊
URL: [Function: URL],
URLSearchParams: [Function: URLSearchParams],
domainToASCII: [Function: domainToASCII],
domainToUnicode: [Function: domainToUnicode]
}
*/
複製程式碼
- parse 如何使用
console.log(url.parse("http://www.baidu.com"));
/**
* Console:
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com',
port: null,
hostname: 'www.baidu.com',
hash: null,
search: null,
query: null,
pathname: '/',
path: '/',
href: 'http://www.baidu.com/'
}
*/
複製程式碼
- parse 帶引數:
console.log(url.parse("http://www.baidu.com/new?name=zhangsan"));
/**
* Console:
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com',
port: null,
hostname: 'www.baidu.com',
hash: null,
search: '?name=zhangsan',
query: 'name=zhangsan',
pathname: '/new',
path: '/new?name=zhangsan',
href: 'http://www.baidu.com/new?name=zhangsan'
}
*/
複製程式碼
format
的使用:
console.log(url.format({
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com',
port: null,
hostname: 'www.baidu.com',
hash: null,
search: '?name=zhangsan',
query: 'name=zhangsan',
pathname: '/new',
path: '/new?name=zhangsan',
href: 'http://www.baidu.com/new?name=zhangsan'
}))
// Console:
// http://www.baidu.com/new?name=zhangsan
複製程式碼
resolve
的使用:
console.log(url.resolve("http://www.baidu.com/jsliang", "樑峻榮"));
// Console:
// http://www.baidu.com/樑峻榮
複製程式碼
當然,url
這裡我們只講解了個入門,更多的還請看官網 API:url | Node.js v10.14.1 文件
3.3 CommonJS
- 什麼是 CommonJS?
CommonJS 就是為 JS 的表現來制定規範,因為 JS 沒有模組系統、標準庫較少、缺乏包管理工具,所以 CommonJS 應運而生,它希望 JS 可以在任何地方執行,而不只是在瀏覽器中,從而達到 Java、C#、PHP 這些後端語言具備開發大型應用的能力。
- CommonJS 的應用?
- 伺服器端 JavaScript 應用程式。(Node.js)
- 命令列工具
- 桌面圖形介面應用程式。
- CommonJS 與 Node.js 的關係?
CommonJS 就是模組化的標準,Node.js 就是 CommonJS(模組化)的實現。
- Node.js 中的模組化?
- 在 Node 中,模組分為兩類:一是 Node 提供的模組,稱為核心模組;二是使用者編寫的模組,成為檔案模組。核心模組在 Node 原始碼的編譯過程中,編譯進了二進位制執行檔案,所以它的載入速度是最快的,例如:HTTP 模組、URL 模組、FS 模組;檔案模組是在執行時動態載入的,需要完整的路勁分析、檔案定位、編譯執行過程等……所以它的速度相對核心模組來說會更慢一些。
- 我們可以將公共的功能抽離出一個單獨的 JS 檔案存放,然後在需要的情況下,通過 exports 或者 module.exports 將模組匯出,並通過 require 引入這些模組。
現在,我們通過三種使用方式,來講解下 Node 中的模組化及 exports/require 的使用。
我們先檢視下目錄:
方法一:
首先,我們新建 03_CommonJS.js
、03_tool-add.js
、node_modules/03_tool-multiply.js
、node_modules/jsliang-module/tools.js
這 4 個檔案/資料夾。
其中 package.json
我們暫且不理會,稍後會講解它如何自動生成。
在 03_tool-add.js
中:
03_tool-add.js
// 1. 假設我們檔案其中有個工具模組
var tools = {
add: (...numbers) => {
let sum = 0;
for (let number in numbers) {
sum += numbers[number];
}
return sum;
}
}
/**
* 2. 暴露模組
* exports.str = str;
* module.exports = str;
* 區別:
* module.exports 是真正的介面
* exports 是一個輔助工具
* 如果 module.exports 為空,那麼所有的 exports 收集到的屬性和方法,都賦值給了 module.exports
* 如果 module.exports 具有任何屬性和方法,則 exports 會被忽略
*/
// exports 使用方法
// var str = "jsliang is very good!";
// exports.str = str; // { str: 'jsliang is very good!' }
// module.exports 使用方法
module.exports = tools;
複製程式碼
那麼,上面的程式碼有啥含義呢?
第一步,我們定義了個工具庫 tools
。
第二步,我們通過 modules.exports
將 tools
進行了匯出。
所以,我們在 03_CommonJS.js
可以通過 require
匯入使用:
var http = require("http");
var tools1 = require('./03_tool-add');
http.createServer(function (req, res) {
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
console.log(tools1.add(1, 2, 3));
/**
* Console:
* 6
* 6
* 這裡要記得 Node 執行過程中,它請求了兩次,
* http://localhost:3000/ 為一次,
* http://localhost:3000/favicon.ico 為第二次
*/
res.end();
}).listen(3000);
複製程式碼
這樣,我們就完成了 exports
與 require
的初次使用。
方法二:
當我們模組檔案過多的時候,應該需要有個存放這些模組的目錄,Node 就很靠譜,它規範我們可以將這些檔案都放在 node_modules
目錄中(大家都放在這個目錄上,就不會有其他亂七八糟的命名了)。
所以,我們在 node_modules
中新建一個 03_tool-multiply.js
檔案,其內容如下:
03_tool-multiply.js
var tools = {
multiply: (...numbers) => {
let sum = numbers[0];
for (let number in numbers) {
sum = sum * numbers[number];
}
return sum;
}
}
module.exports = tools;
複製程式碼
在引用方面,我們只需要通過:
// 如果 Node 在當前目錄沒找到 tool.js 檔案,則會去 node_modules 裡面去查詢
var tools2 = require('03_tool-multiply');
console.log(tools2.multiply(1, 2, 3, 4));
複製程式碼
這樣,就可以成功匯入 03_tool-multiply.js
檔案了。
方法三:
如果全部單個檔案丟在 node_modules
上,它會顯得雜亂無章,所以我們應該定義個自己的模組:jsliang-module
,然後將我們的 tools.js
存放在該目錄中:
jsliang-module/tools.js
var tools = {
add: (...numbers) => {
let sum = 0;
for (let number in numbers) {
sum += numbers[number];
}
return sum;
},
multiply: (...numbers) => {
let sum = numbers[0];
for (let number in numbers) {
sum = sum * numbers[number];
}
return sum;
}
}
module.exports = tools;
複製程式碼
這樣,我們就定義好了自己的工具庫。
但是,如果我們通過 var tools3 = require('jsliang-module');
去匯入,會發現它報 error
了,所以,我們應該在 jsliang-module
目錄下,通過下面命令列生成一個 package.json
PS E:\MyWeb\node_modules\jsliang-module> npm init --yes
這樣,在 jsliang-module
中就有了 package.json
。
而我們在 03_CommonJS.js
就可以引用它了:
03_CommonJS.js
var http = require("http");
var tools1 = require('./03_tool-add');
// 如果 Node 在當前目錄沒找到 tool.js 檔案,則會去 node_modules 裡面去查詢
var tools2 = require('03_tool-multiply');
/**
* 通過 package.json 來引用檔案
* 1. 通過在 jsliang-module 中 npm init --yes 來生成 package.json 檔案
* 2. package.json 檔案中告訴了程式入口檔案為 :"main": "tools.js",
* 3. Node 通過 require 查詢 jsliang-module,發現它有個 package.json
* 4. Node 執行 tools.js 檔案
*/
var tools3 = require('jsliang-module');
http.createServer(function (req, res) {
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
console.log(tools1.add(1, 2, 3));
console.log(tools2.multiply(1, 2, 3, 4));
console.log(tools3.add(4, 5, 6));
/**
* Console:
* 6
* 24
* 15
* 6
* 24
* 15
* 這裡要記得 Node 執行過程中,它請求了兩次,
* http://localhost:3000/ 為一次,
* http://localhost:3000/favicon.ico 為第二次
*/
res.end();
}).listen(3000);
複製程式碼
到此,我們就通過三種方法,瞭解了各種 exports
和 require
的姿勢以及 Node 模組化的概念啦~
參考文獻:
- CommonJS 規範 | 部落格園 - Little Bird
- js模組化程式設計之徹底弄懂CommonJS和AMD/CMD! | 部落格園 - 方便以後複習
- [js高手之路] es6系列教程 - 不定引數與展開運算子(...) | 部落格園 - ghostwu
3.4 包與 npm
Node 中除了它自己提供的核心模組之外,還可以自定義模組,以及使用 第三方模組。
Node 中第三方模組由包組成,可以通過包來對一組具有相互依賴關係的模組進行統一管理。
那麼,假如我們需要使用一些第三方模組,應該去哪找呢?
那麼,npm 是啥?
npm 是世界上最大的開放原始碼的生態系統。我們可以通過 npm 下載各種各樣的包。
在我們安裝 Node 的時候,它預設會順帶給你安裝 npm。
npm -v
:檢視 npm 版本。npm list
:檢視當前目錄下都安裝了哪些 npm 包。npm info 模組
:檢視該模組的版本及內容。npm i 模組@版本號
:安裝該模組的指定版本。
在平時使用 npm 安裝包的過程中,你可能需要知道一些 npm 基本知識:
i
/install
:安裝。使用install
或者它的簡寫i
,都表明你想要下載這個包。uninstall
:解除安裝。如果你發現這個模組你已經不使用了,那麼可以通過uninstall
解除安裝它。g
:全域性安裝。表明這個包將安裝到你的計算機中,你可以在計算機任何一個位置使用它。--save
/-S
:通過該種方式安裝的包的名稱及版本號會出現在package.json
中的dependencies
中。dependencies
是需要釋出在生成環境的。例如:ElementUI
是部署後還需要的,所以通過-S
形式來安裝。--save-dev
/-D
:通過該種方式安裝的包的名稱及版本號會出現在package.json
中的devDependencies
中。devDependencies
只在開發環境使用。例如:gulp
只是用來壓縮程式碼、打包的工具,程式執行時並不需要,所以通過-D
形式來安裝。
例子:
cnpm i webpack-cli -D
npm install element-ui -S
那麼,這麼多的 npm 包,我們通過什麼管理呢?
答案是 package.json
。
如果我們需要建立 package.json
,那麼我們只需要在指定的包管理目錄(例如 node_modules
)中通過以下命名進行生成:
npm init
:按步驟建立package.json
。npm init --yes
:快速建立package.json
當然,因為國內網路環境的原因,有些時候通過 npm 下載包,可能會很慢或者直接卡斷,這時候就要安裝淘寶的 npm 映象:cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
3.5 fs 檔案管理
本章節我們講解下 fs 檔案管理:
如需快速找到下面某個內容,請使用
Ctrl + F
fs.stat
檢測是檔案還是目錄fs.mkdir
建立目錄fs.writeFile
建立寫入檔案fs.appendFile
追加檔案fs.readFile
讀取檔案fs.readdir
讀取目錄fs.rename
重新命名fs.rmdir
刪除目錄fs.unlink
刪除檔案
此章節檔案目錄:
首先,我們通過 fs.stat
檢查一個讀取的是檔案還是目錄:
05_fs.js
// 1. fs.stat
let fs = require('fs');
fs.stat('index.js', (error, stats) => {
if(error) {
console.log(error);
return false;
} else {
console.log(stats);
/**
* Console:
* Stats {
* dev: 886875,
* mode: 33206,
* nlink: 1,
* uid: 0,
* gid: 0,
* rdev: 0,
* blksize: undefined,
* ino: 844424931461390,
* size: 284,
* blocks: undefined,
* atimeMs: 1542847157494,
* mtimeMs: 1543887546361.2158,
* ctimeMs: 1543887546361.2158,
* birthtimeMs: 1542847157493.663,
* atime: 2018-11-22T00:39:17.494Z,
* mtime: 2018-12-04T01:39:06.361Z,
* ctime: 2018-12-04T01:39:06.361Z,
* birthtime: 2018-11-22T00:39:17.494Z }
*/
console.log(`檔案:${stats.isFile()}`);
// Console:檔案:true
console.log(`目錄:${stats.isDirectory()}`);
// Console:目錄:false
return false;
}
})
複製程式碼
通過 Console
列印出來的資訊,我們基礎掌握了 fs.stat
的作用。
然後,我們嘗試通過 fs.mkdir
建立目錄:
05_fs.js
// 2. fs.mkdir
let fs = require('fs');
/**
* 接收引數
* path - 將建立的目錄路徑
* mode - 目錄許可權(讀寫許可權),預設 0777
* callback - 回撥,傳遞異常引數 err
*/
fs.mkdir('css', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("建立目錄成功!");
// Console:建立目錄成功!
}
})
複製程式碼
通過 node 05_fs.js
,我們發現目錄中多了一個 css
資料夾。
那麼,有建立就有刪除,建立的目錄如何刪除呢?這裡講解下 fs.rmdir
:
05_fs.js
// 8. fs.rmdir
let fs = require('fs');
/**
* 接收引數
* path - 將建立的目錄路徑
* mode - 目錄許可權(讀寫許可權),預設 0777
* callback - 回撥,傳遞異常引數 err
*/
fs.rmdir('css', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("建立目錄成功!");
// Console:建立目錄成功!
}
})
複製程式碼
通過 node 05_fs.js
,我們發現目錄中的 css
資料夾被刪除了。
接著,我們通過 fs.writeFile
來建立寫入檔案:
05_fs.js
// 3. fs.writeFile
let fs = require('fs');
/**
* filename (String) 檔名稱
* data (String | Buffer) 將要寫入的內容,可以是字串或者 buffer 資料。
* · encoding (String) 可選。預設 'utf-8',當 data 是 buffer 時,該值應該為 ignored。
* · mode (Number) 檔案讀寫許可權,預設 438。
* · flag (String) 預設值 'w'。
* callback { Function } 回撥,傳遞一個異常引數 err。
*/
fs.writeFile('index.js', 'Hello jsliang', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log('寫入成功!');
}
})
複製程式碼
值得注意的是,這樣的寫入,是清空原檔案中的所有資料,然後新增 Hello jsliang
這句話。即:存在即覆蓋,不存在即建立。
有建立就有刪除,感興趣的可以使用 fs.unlink
進行檔案的刪除,再次不做過多講解。
既然,上面的是覆蓋檔案,那麼有沒有追加檔案呢?有的,使用 fs.appendFile
吧:
05_fs.js
// 4. fs.appendFile
let fs = require('fs');
fs.appendFile('index.js', '這段文字是要追加的內容', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("追加成功");
}
})
複製程式碼
這樣,我們就成功往裡面追加了一段話,從而使 index.js
變成了:
index.js
Hello jsliang這段文字是要追加的內容
複製程式碼
在上面,我們已經做了:新增、修改、刪除操作。那麼小夥伴一定很熟悉下一步驟是做什麼了:
fs.readFile
讀取檔案fs.readdir
讀取目錄
05_fs.js
let fs = require('fs');
// 5. fs.readFile
fs.readFile('index.js', (err, data) => {
if(err) {
console.log(err);
return false;
} else {
console.log("讀取檔案成功!");
console.log(data);
// Console:
// 讀取檔案成功!
// <Buffer 48 65 6c 6c 6f 20 6a 73 6c 69 61 6e 67 e8 bf 99 e6 ae b5 e6 96 87 e6 9c ac e6 98 af e8 a6 81 e8 bf bd e5 8a a0 e7 9a 84 e5 86 85 e5 ae b9>
}
})
// 6. fs.readdir 讀取目錄
fs.readdir('node_modules', (err, data) => {
if(err) {
console.log(err);
return false;
} else {
console.log("讀取目錄成功!");
console.log(data);
// Console:
// 讀取目錄成功!
// [ '03_tool-multiply.js', 'jsliang-module' ]
}
})
複製程式碼
如上,我們成功做到了讀取檔案和讀取目錄。
最後,我們再回顧一開始的目標:
1. fs.stat
檢測是檔案還是目錄
2. fs.mkdir
建立目錄
3. fs.writeFile
建立寫入檔案
4. fs.appendFile
追加檔案
5. fs.readFile
讀取檔案
6. fs.readdir
讀取目錄
7. fs.rename
重新命名
8. fs.rmdir
刪除目錄
9. fs.unlink
刪除檔案
很好,我們就剩下重新命名了:
05_fs.js
let fs = require('fs');
// 7. fs.rename 重新命名
fs.rename('index.js', 'jsliang.js', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("重新命名成功!");
}
})
複製程式碼
當然,如果 fs.rename
還有更勁爆的功能:剪下
05_fs.js
let fs = require('fs');
// 7. fs.rename 重新命名
fs.rename('jsliang.js', 'node_modules/jsliang.js', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("剪下成功!");
}
})
複製程式碼
OK,通通搞定,現在目錄變成了:
3.6 fs 案例
在上一章節中,我們瞭解了 fs
的檔案管理。
那麼,在這裡,我們嘗試使用 fs
做點小事情:
06_fsDemo.js
/**
* 1. fs.stat 檢測是檔案還是目錄
* 2. fs.mkdir 建立目錄
* 3. fs.writeFile 建立寫入檔案
* 4. fs.appendFile 追加檔案
* 5. fs.readFile 讀取檔案
* 6. fs.readdir 讀取目錄
* 7. fs.rename 重新命名
* 8. fs.rmdir 刪除目錄
* 9. fs.unlink 刪除檔案
*/
// 1. 判斷伺服器上面有沒有 upload 目錄,沒有就建立這個目錄
// 2. 找出 html 目錄下面的所有的目錄,然後列印出來
let fs = require('fs');
// 圖片上傳
fs.stat('upload', (err, stats) => {
// 判斷有沒有 upload 目錄
if(err) {
// 如果沒有
fs.mkdir('upload', (error) => {
if(error) {
console.log(error);
return false;
} else {
console.log("建立 upload 目錄成功!");
}
})
} else {
// 如果有
console.log(stats.isDirectory());
console.log("有 upload 目錄,你可以做更多操作!");
}
})
// 讀取目錄全部檔案
fs.readdir('node_modules', (err, files) => {
if(err) {
console.log(err);
return false;
} else {
// 判斷是目錄還是資料夾
console.log(files);
let filesArr = [];
(function getFile(i) {
// 迴圈結束
if(i == files.length) {
// 列印出所有目錄
console.log("目錄:");
console.log(filesArr);
return false;
}
// 判斷目錄是檔案還是資料夾
fs.stat('node_modules/' + files[i], (error, stats) => {
if(stats.isDirectory()) {
filesArr.push(files[i]);
}
// 遞迴呼叫
getFile(i+1);
})
})(0)
}
})
複製程式碼
3.7 fs 流
話不多說,我們瞭解下 fs
流及其讀取:
// 新建 fs
const fs = require('fs');
// 流的方式讀取檔案
let fileReadStream = fs.createReadStream('index.js');
// 讀取次數
let count = 0;
// 儲存資料
let str = '';
// 開始讀取
fileReadStream.on('data', (chunk) => {
console.log(`${++count} 接收到:${chunk.length}`);
// Console:1 接收到:30
str += chunk;
})
// 讀取完成
fileReadStream.on('end', () => {
console.log("——結束——");
console.log(count);
console.log(str);
// Console:——結束——
// 1
// console.log("Hello World!");
})
// 讀取失敗
fileReadStream.on('error', (error) => {
console.log(error);
})
複製程式碼
在這裡,我們通過 fs
模組的 createReadStream
建立了讀取流,然後讀取檔案 index.js
,從而最後在控制檯輸出了:
1 接收到:259
——結束——
1
console.log("盡信書,不如無書;盡看程式碼,不如刪掉這些檔案。");
console.log("盡信書,不如無書;盡看程式碼,不如刪掉這些檔案。");
console.log("盡信書,不如無書;盡看程式碼,不如刪掉這些檔案。");
複製程式碼
其中 console.log()
那三行就是 index.js
的文字內容。
然後,我們試下流的存入:
let fs = require('fs');
let data = 'console.log("Hello World! 我要存入資料!")';
// 建立一個可以寫入的流,寫入到檔案 index.js 中
let writeStream = fs.createWriteStream('index.js');
// 開始寫入
writeStream.write(data, 'utf8');
// 寫入完成
writeStream.end();
writeStream.on('finish', () => {
console.log('寫入完成!');
// Console:寫入完成
});
複製程式碼
我們開啟 index.js
,會發現裡面的內容變成了 console.log("Hello World! 我要存入資料!")
,依次,我們通過流的形式進行了讀取和寫入的操作。
3.8 建立 Web 伺服器
在這裡,我們利用 http 模組、url 模組、path 模組、fs 模組建立一個 Web 伺服器。
什麼是 Web 伺服器?
Web 伺服器一般指網站伺服器,是指駐留於因特網上某種型別計算機的程式,可以像瀏覽器等 Web 客戶端提供文件,也可以放置網站檔案,讓全世界瀏覽;可以放置資料檔案,讓全世界下載。目前最主流的三個 Web 伺服器是 Apache、Nginx、IIS。
下面,我們使用 Node 來建立一個 Web 服務:
08_WebService.js
// 引入 http 模組
let http = require("http");
// 引入 fs 模組
let fs = require("fs");
http.createServer((req, res) => {
// 獲取響應路徑
let pathName = req.url;
// 預設載入路徑
if (pathName == "/") {
// 預設載入的首頁
pathName = "index.html";
}
// 過濾 /favicon.ico 的請求
if (pathName != "/favicon.ico") {
// 獲取 08_WebService 下的 index.html
fs.readFile("./08_WebService/" + pathName, (err, data) => {
if (err) {
// 如果不存在這個檔案
console.log("404 Not Found!");
fs.readFile('./08_WebService/404.html', (errorNotFound, dataNotFound) => {
if(errorNotFound) {
console.log(errorNotFound);
} else {
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 讀取寫入檔案
res.write(dataNotFound);
// 結束響應
res.end();
}
})
return;
} else {
// 返回這個檔案
// 設定請求頭
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 讀取寫入檔案
res.write(data);
// 結束響應
res.end();
}
});
}
}).listen(8080);
複製程式碼
這樣,我們在瀏覽器輸入 localhost:8080
即可以看到:
好傢伙,感情它就載入了整個 index.html
檔案,連 CSS 這些沒引入麼?
所以,下一步,我們要動態載入 html
、css
以及 js
:
08_WebService.js
// 引入 http 模組
let http = require("http");
// 引入 fs 模組
let fs = require("fs");
// 引入 url 模組
let url = require("url");
// 引入 path 模組
let path = require("path");
http.createServer((req, res) => {
// 獲取響應路徑
let pathName = url.parse(req.url).pathname;
// 預設載入路徑
if (pathName == "/") {
// 預設載入的首頁
pathName = "index.html";
}
// 獲取檔案的字尾名
let extName = path.extname(pathName);
// 過濾 /favicon.ico 的請求
if (pathName != "/favicon.ico") {
// 獲取 08_WebService 下的 index.html
fs.readFile("./08_WebService/" + pathName, (err, data) => {
// 如果不存在這個檔案
if (err) {
console.log("404 Not Found!");
fs.readFile(
"./08_WebService/404.html",
(errorNotFound, dataNotFound) => {
if (errorNotFound) {
console.log(errorNotFound);
} else {
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 讀取寫入檔案
res.write(dataNotFound);
// 結束響應
res.end();
}
}
);
return;
}
// 返回這個檔案
else {
// 獲取檔案型別
let ext = getExt(extName);
// 設定請求頭
res.writeHead(200, {
"Content-Type": ext + "; charset='utf-8'"
});
// 讀取寫入檔案
res.write(data);
// 結束響應
res.end();
}
});
}
}).listen(8080);
// 獲取字尾名
getExt = (extName) => {
switch(extName) {
case '.html': return 'text/html';
case '.css': return 'text/css';
case '.js': return 'text/js';
default: return 'text/html';
}
}
複製程式碼
這樣,當我們再次請求的時候,瀏覽器就變成了:
當然,在上面,我們僅僅模擬了 html
、css
、js
這三種檔案型別而已,我們需要模擬更多的檔案型別:
程式碼詳情請點選上面的連結
複製程式碼
在上面的 json
檔案中,我們定義了各種的檔案型別,此刻檔案目錄如下所示:
這時候,我們需要修改下我們的 js
檔案,讓它適應多種請求響應了:
08_WebService.js
// 引入 http 模組
let http = require("http");
// 引入 fs 模組
let fs = require("fs");
// 引入 url 模組
let url = require("url");
// 引入 path 模組
let path = require("path");
http.createServer((req, res) => {
// 獲取響應路徑
let pathName = url.parse(req.url).pathname;
// 預設載入路徑
if (pathName == "/") {
// 預設載入的首頁
pathName = "index.html";
}
// 獲取檔案的字尾名
let extName = path.extname(pathName);
// 過濾 /favicon.ico 的請求
if (pathName != "/favicon.ico") {
// 獲取 08_WebService 下的 index.html
fs.readFile("./08_WebService/" + pathName, (err, data) => {
// 如果不存在這個檔案
if (err) {
console.log("404 Not Found!");
fs.readFile(
"./08_WebService/404.html",
(errorNotFound, dataNotFound) => {
if (errorNotFound) {
console.log(errorNotFound);
} else {
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 讀取寫入檔案
res.write(dataNotFound);
// 結束響應
res.end();
}
}
);
return;
}
// 返回這個檔案
else {
// 獲取檔案型別
let ext = getExt(extName);
console.log(ext);
// 設定請求頭
res.writeHead(200, {
"Content-Type": ext + "; charset='utf-8'"
});
// 讀取寫入檔案
res.write(data);
// 結束響應
res.end();
}
});
}
}).listen(8080);
// 獲取字尾名
getExt = (extName) => {
// readFile 是非同步操作,所以需要使用 readFileSync
let data = fs.readFileSync('./08_ext.json');
let ext = JSON.parse(data.toString());
return ext[extName];
}
複製程式碼
如此,我們做了個簡單的 Web 伺服器。
3.9 非阻塞 I/O 事件驅動
Java、PHP 或者 .NET 等服務端語言,會為每一個客戶端的連線建立一個新的執行緒。
Node 不會為每一個客戶連線建立一個新的執行緒,而僅僅使用一個執行緒。
當有使用者連線了,就會觸發一個內部事件,通過非租塞 I/O、事件驅動機制,讓 Node 程式巨集觀上也是並行的。
使用 Node,一個 8GB 記憶體的伺服器,可以同時處理超過 4 萬使用者的連線。
在這一章節中,主要解決:
- Node 的非阻塞 I/O 是什麼?
- Node events 模組是什麼?
首先,在我們正常程式設計中,我們是希望程式能夠一行一行按照我們的意願編寫的:
09_io.js
console.log("1");
console.log("2");
console.log("3");
/**
* Console:
* 1
* 2
* 3
*/
複製程式碼
但是,事與願違。
我們有時候,會執行一些非同步方法(函式):
09_io.js
console.log("1");
// console.log("2");
let fs = require('fs');
getExt = () => {
fs.readFile('08_ext.json', (err, data) => {
console.log("2");
})
}
getExt();
console.log("3");
/**
* Console:
* 1
* 3
* 2
*/
複製程式碼
在上面程式碼中,由於 fs.readFile
是 Node 的非同步函式。所以,程式先執行了 1 和 3,最後才執行 fs.readFile
的 2 部分。
在這裡,可以看出 Node 不會因為一段程式碼的邏輯錯誤,從而導致其他程式碼無法執行。
這樣子,就導致了一個問題:步驟 3 可能拿不到步驟 2 的執行結果了!這就是 Node 的非租塞性 I/O 驅動。
那麼,我們有沒有辦法解決這個問題呢?
有的!
- 通過回撥函式
- 通過 Node 的
events
模組
首先,我們通過回撥函式來解決這個非同步問題:
09_io.js
let fs = require("fs");
getExt = (callback) => {
fs.readFile('08_ext.json', (err, data) => {
callback(data);
})
}
getExt( (result) => {
console.log(result.toString());
})
複製程式碼
通過回撥,我們可以將 getExt
的資料提取出來。
然後,我們通過 Node 的 events
模組來解決這個非同步問題:
// 引入 fs 模組
let fs = require("fs");
/**
* Node 事件迴圈:
* 1. Node 是單程式單執行緒應用程式,但是通過事件和回撥支援併發,所以效能非常高。
* 2. Node 的每一個 API 都是非同步的,並作為一個獨立執行緒執行,使用非同步函式呼叫,並處理併發。
* 3. Node 有多個內建的事件,我們可以通過引入 events 模組,並通過例項化 EventEmitter 類來繫結和監聽事件。
*/
// 引入 events 模組
let events = require("events");
// 例項化事件物件
let EventEmitter = new events.EventEmitter();
getExt = () => {
fs.readFile('08_ext.json', (err, data) => {
// 將 data 廣播出去
EventEmitter.emit('data', data.toString());
})
};
getExt();
// 監聽 data
EventEmitter.on('data', (ext) => {
console.log(ext);
});
複製程式碼
在這裡,EventEmitter.on
通過監聽 data
的形式,獲取了 getExt
內部的執行結果。
如此,我們就瞭解了 Node 的 I/O 事件及 events
模組
3.10 get 與 post
話不多說,先上程式碼:
index.js
// 載入 http 模組
var http = require('http');
// 虛擬 SQL 讀取出來的資料
var items = [];
// 建立 http 服務
http.createServer(function (req, res) {
// 設定跨域的域名,* 代表允許任意域名跨域
res.setHeader('Access-Control-Allow-Origin', '*');
// 設定 header 型別
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// 跨域允許的請求方式
res.setHeader('Content-Type', 'application/json');
// 判斷請求
switch (req.method) {
// post 請求時,瀏覽器會先發一次 options 請求,如果請求通過,則繼續傳送正式的 post 請求
case 'OPTIONS':
res.statusCode = 200;
res.end();
break;
// 如果是 get 請求,則直接返回 items 陣列
case 'GET':
let data = JSON.stringify(items);
res.write(data);
res.end();
break;
// 如果是 post 請求
case 'POST':
let item = '';
// 讀取每次傳送的資料
req.on('data', function (chunk) {
item += chunk;
});
// 資料傳送完成
req.on('end', function () {
// 存入
item = JSON.parse(item);
items.push(item.item);
// 將資料返回到客戶端
let data = JSON.stringify(items);
res.write(data);
res.end();
});
break;
}
}).listen(3000)
console.log('http server is start...');
複製程式碼
首先,我們載入了 http
模組,並建立了服務。
然後,我們設定了跨域的處理方式,允許進行跨域。
接著,我們進行了請求的判斷處理,由於只做簡單演練,故只判斷是 get
請求還是 post
請求。
最後,我們將請求的結果返回給客戶端。
在上面,我們進行了後端 Node 的部署,那麼前端頁面要怎麼做呢?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-⌃-Compatible" content="ie=edge">
<title>Node Web</title>
</head>
<body>
<div id="app">
<h1>Todo List</h1>
<ul>
<li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>
<input type="text" v-model="item">
<button @click="postApi">新增</button>
</div>
<!-- cdn 引用:Vue 和 Node -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
new Vue({
el: document.getElementById('app'),
data: function () {
return {
items: [],
item: '',
}
},
created() {
// 進入頁面請求資料
axios.get('http://localhost:3000/').then(res => {
console.log("\n【API - get 資料】");
console.log(res);
this.items = res.data;
}).catch(function (err) {
console.log(err)
})
},
methods: {
// 點選按鈕提交資料
postApi() {
axios.post('http://localhost:3000/', {
item: this.item
}).then(res => {
console.log("\n【API - post 資料】")
console.log(res);
this.items = res.data;
}).catch(function (err) {
console.log(err)
})
}
}
})
</script>
</body>
</html>
複製程式碼
我們通過 Vue 進行了佈局,通過 Axios 進行了介面的請求。從而完成了對資料的操作。
3.11 Node 連線 MySQL
關於 MySQL 的安裝,可以檢視 jsliang 寫的:MySQL 安裝及圖形化工具
首先,我們通過視覺化工具進行表的設計:
名 | 型別 | 長度 | 鍵 |
---|---|---|---|
id | int | 11 | 主鍵 |
name | varchar | 255 | |
age | varchar | 255 |
然後,我們進行表的填充:
id | name | age |
---|---|---|
1 | jslliang | 23 |
2 | 樑峻榮 | 23 |
接著,我們安裝 Node 連線 MySQL 的包:
npm i mysql -D
複製程式碼
再來,我們編寫 Node 的 index.js
:
index.js
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
connection.connect();
connection.query('SELECT * FROM user', function (error, results, fields) {
if (error) throw error;
console.log(results);
});
connection.end();
複製程式碼
最後,我們通過 node index.js
,開啟該服務:
[ RowDataPacket { id: 1, name: 'jsliang', age: '23' },
RowDataPacket { id: 2, name: '樑峻榮', age: '23' } ]
複製程式碼
如此,我們便完成了 Node 連線 MySQL。
———————華麗分割線———————
當然,增刪改查是後端的基本操作,所以在這裡,我們可以補全基本的增刪改查功能。
先看目錄:
- 新增表欄位
add.js
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
connection.connect();
let addSql = "INSERT INTO user(id,name,age) VALUES(0,?,?)";
let addSqlParams = ["jsliang", "23"];
connection.query(addSql, addSqlParams, function (err, res) {
if (err) {
console.log("新增錯誤:");
console.log(err);
return;
} else {
console.log("新增成功:");
console.log(res);
}
});
connection.end();
複製程式碼
我們只需要直接 node add.js
,就能往資料庫中新增資料了。
- 刪除表欄位
delete.js
// 連線 MySQL
var mysql = require('mysql');
// MySQL 的連線資訊
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
// 開始連線
connection.connect();
// 新增的 SQL 語句及新增的欄位資訊
var delSql = 'DELETE FROM user where id = 2';
// 連線 SQL 並實施語句
connection.query(delSql, function (err, res) {
if (err) {
console.log("刪除錯誤:");
console.log(err);
return;
} else {
console.log("刪除成功:");
console.log(res);
}
});
// 終止連線
connection.end();
複製程式碼
- 修改表欄位
update.js
// 連線 MySQL
var mysql = require('mysql');
// MySQL 的連線資訊
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
// 開始連線
connection.connect();
// 新增的 SQL 語句及新增的欄位資訊
let updateSql = "UPDATE user SET name = ?,age = ? WHERE Id = ?";
let updateSqlParams = ["LiangJunrong", "23", 1];
// 連線 SQL 並實施語句
connection.query(updateSql, updateSqlParams, function (err, res) {
if (err) {
console.log("修改錯誤:");
console.log(err);
return;
} else {
console.log("修改成功:");
console.log(res);
}
});
// 終止連線
connection.end();
複製程式碼
- 查詢表欄位
read.js
// 連線 MySQL
var mysql = require('mysql');
// MySQL 的連線資訊
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
// 開始連線
connection.connect();
// 新增的 SQL 語句及新增的欄位資訊
let readSql = "SELECT * FROM user";
// 連線 SQL 並實施語句
connection.query(readSql, function (err, res) {
if (err) throw err;
console.log(res);
});
// 終止連線
connection.end();
複製程式碼
以上,我們打通了 Node 與 MySQL 的壁壘,實現了資料的增刪改查。
四 Web 實戰 —— 企業官網
在進行程式碼實戰的時候,我們很多時候會遇到一些小事兒,例如:logo 製作、ico 製作、icon 挑選等……
下面這些都是 jsliang 平時碰到的,小夥伴有需要的可以 mark 啦~
另外,由於 HTML 與 CSS 沒什麼好講的,所以本章節的前提靜態頁面 jsliang 已經寫好了,小夥伴們在學習前可以預先下載:
4.1 程式設計環境
首先,我們檢視下我們的前端基本程式碼:地址
如上,我們僅需要了解 FrontEndCode 目錄以及 NodeWeb 目錄即可,其他目錄為上面章節練習參考。
然後,我們進行後端功能分析:
- 留言板。使用者點選 留言板 的時候,需要先判斷使用者是否登入。如果使用者尚未登入,則直接跳轉到 登入頁;如果使用者登入了,則顯示 留言板頁面。
在 留言板頁面 中,存在兩個介面:
- 獲取留言內容:調取
getMessage
介面,返回全部留言資訊,由於預計資訊不多,故這裡不做分頁功能,有需要的小夥伴在實現完這個功能後,可以進行分頁介面的設計。 - 提交留言內容:調取
sendMessage
介面,將使用者名稱、使用者 id、留言內容傳送給後端。
- 在 登入頁面 中,存在一個介面:
- 登入:調取
login
介面,提交使用者填寫的姓名和密碼。
- 在 註冊頁面 中,存在一個介面:
- 註冊:調取
register
介面,提交使用者填寫的姓名和密碼。
由此,我們可以設計下前後端的介面結合:
介面文件
介面 | 型別 | 引數 | 返回資訊 |
---|---|---|---|
getMessage :獲取留言資訊 |
get | 無參 | n 條記錄:id(使用者 id)、user_name(使用者名稱)、user_message(使用者留言內容)、time(留言時間) |
sendMessage :提交留言資訊 |
post | id(使用者 id)、user_name(使用者名稱)、user_message(使用者留言內容) | status 狀態 |
login :登入 |
post | id(使用者 id)、user_name(使用者名稱)、user_password(使用者密碼) | status 狀態 |
register :註冊 |
post | id(使用者 id)、user_name(使用者名稱)、user_password(使用者密碼) | status 狀態 |
最後,我們進行 MySQL 資料庫的表設計:
user 表
名 | 型別 | 長度 | 鍵 |
---|---|---|---|
id | int | 11 | 主鍵 |
user_name | varchar | 255 | |
user_password | varchar | 255 | |
time | datetime |
message 表
名 | 型別 | 長度 | 鍵 |
---|---|---|---|
id | int | 11 | 主鍵 |
user_message | varchar | 255 | |
user_id | varchar | 255 | 外來鍵 |
user_name | varchar | 255 | |
time | datetime |
4.2 後端介面
在我們進行實操之前,先確認我們是否能寫介面,所以我們可以新建一個 test
資料夾,裡面放一個 index.html
以及一個 index.js
來測試一下。
- text
- index.html
- index.js
複製程式碼
首先,我們就 4.1 提到的介面,提前進行後端介面的設定:
index.js
// 連線 MySQL:先安裝 npm i mysql -D
var mysql = require('mysql');
// MySQL 的連線資訊
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'nodebase'
});
// 開始連線
connection.connect();
// 引入 http 模組:http 是提供 Web 服務的基礎
const http = require("http");
// 引入 url 模組:url 是對使用者提交的路徑進行解析
const url = require("url");
// 引入 qs 模組:qs 是對路徑進行 json 化或者將 json 轉換為 string 路徑
const qs = require("querystring");
// 用 http 模組建立服務
/**
* req 獲取 url 資訊 (request)
* res 瀏覽器返回響應資訊 (response)
*/
http.createServer(function (req, res) {
// 設定 cors 跨域
res.setHeader("Access-Control-Allow-Origin", "*");
// 設定 header 型別
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// 跨域允許的請求方式
res.setHeader('Content-Type', 'application/json');
if (req.method == "POST") { // 介面 POST 形式
console.log("\n【POST 形式】");
// 獲取前端發來的路由地址
let pathName = req.url;
console.log("\n介面為:" + pathName);
// 接收傳送過來的引數
let tempResult = "";
// 資料接入中
req.addListener("data", function (chunk) {
tempResult += chunk;
});
// 資料接收完成
req.addListener("end", function () {
var result = JSON.stringify(qs.parse(tempResult));
console.log("\n引數為:");
console.log(result);
if (pathName == "/sendMessage") { // 提交留言資訊
console.log("\n【API - 提交留言資訊】");
} else if (pathName == "/login") { // 登入
console.log("\n【API - 登入】");
} else if (pathName == "/register") { // 註冊
console.log("\n【API - 註冊】");
}
// 介面資訊處理完畢
})
// 資料接收完畢
} else if (req.method == "GET") { // 介面 GET 形式
console.log("\n【GET 形式】");
// 解析 url 介面
let pathName = url.parse(req.url).pathname;
console.log("\n介面為:" + pathName);
if (pathName == "/getMessage") { // 獲取留言資訊
console.log("\n【API - 獲取留言資訊】");
} else if(pathName == "/") { // 首頁
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('<h1 style="text-align:center">jsliang 前端有限公司服務已開啟!</h1><h2 style="text-align:center">詳情可見:<a href="https://github.com/LiangJunrong/document-library/blob/master/other-library/Node/NodeBase.md" target="_blank">Node 基礎</a></h2>');
res.end();
}
}
}).listen(8888); // 監聽的埠
// 獲取當前時間
function getNowFormatDate() {
var date = new Date();
var year = date.getFullYear(); // 年
var month = date.getMonth() + 1; // 月
var strDate = date.getDate(); // 日
var hour = date.getHours(); // 時
var minute = date.getMinutes(); // 分
var second = date.getMinutes(); // 秒
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (strDate >= 0 && strDate <= 9) {
strDate = "0" + strDate;
}
// 返回 yyyy-mm-dd hh:mm:ss 形式
var currentdate = year + "-" + month + "-" + strDate + " " + hour + ":" + minute + ":" + second;
return currentdate;
}
複製程式碼
通過判斷 req.method
屬於 GET
還是 POST
形式,從而確定載入的介面:
- 在
POST
中,判斷是屬於 提交留言資訊、登入 還是 註冊; - 在
GET
中,判斷是不是 獲取留言資訊。
同時,我們在其中定義了 MySQL 的連線以及一個 getNowFormatDate
用來獲取當前時間,格式為:2018-12-21 10:03:59
然後,我們通過一個前端頁面來演示我們的介面是否能使用:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>演示程式碼</title>
</head>
<body>
<div>
<label for="user">使用者名稱</label><input type="text" id="user">
</div>
<div>
<label for="password">密 碼</label><input type="password" id="password">
</div>
<div>
<button id="register">註冊</button>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
$(function () {
// 測試 get 介面
$.ajax({
url: "http://localhost:8888/getMessage",
type: "POST",
data: {
username: "jsliang"
},
success: function (res) {
console.log(res);
},
error: function (err) {
console.log(err);
}
})
$("#register").click(function () {
// 測試 post 介面
$.ajax({
url: "http://localhost:8888/login",
type: "POST",
data: {
username: $("#user").val(),
password: $("#password").val()
},
success: function (res) {
console.log(res);
},
error: function (err) {
console.log(err);
}
})
})
});
</script>
</body>
</html>
複製程式碼
最後,我們通過 node index.js
,並開啟 index.html
,通過 F12
控制檯檢視我們的介面是否正常:
可以看到我們的介面能正常調通,這樣我們就可以連線資料庫,進行這 4 個介面的設計了。
如果小夥伴們覺得每次更新 Node 程式碼後,又要重啟一遍
node index.js
覺得麻煩,可以通過supervisor
來監聽 Node 程式碼的改動,supervisor
的安裝使用:supervisor
4.3 註冊功能
很好,我們回到仿企業網站的頁面上,準備編寫介面以及豐富 Node 的介面。
首先,我們開啟前端和 Node 服務:
-
開啟命令列/終端
-
開啟前端
cd FrontEndCode
live-server
安裝
live-server
:npm i live-server -g
- 開啟後端
cd NodeWeb
supervisor index.js
安裝
supervisor
:npm i supervisor -g
然後,我們在註冊頁面通過點選事件來觸發調介面:
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="keywords" content="前端,jsliang,bootstrap,企業建站">
<meta http-equiv="description" content="jsliang 為你打造最好的企業服務">
<link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>註冊-jsliang 前端有限公司</title>
<link rel="stylesheet" href="./css/index.css">
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<!-- 省略 body 中程式碼,有需要的請前往第四章開頭下載檢視全部程式碼 -->
<script src="./js/jquery-3.3.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/islogin.js"></script>
<script>
$(function () {
$("#register-submit").click(function () {
let userName = $("#userName").val();
let userPassword = $("#userPassword").val();
if (!userName) {
alert("請輸入使用者名稱");
$("#userName").focus();
} else if (!userPassword) {
alert("請輸入密碼");
$("#userPassword").focus();
} else if (userName.length > 10) {
alert("請輸入少於 10 位的使用者名稱");
$("#userName").focus();
} else if (userPassword.length > 20) {
alert("請輸入少於 20 位的密碼");
$("#userPassword").focus();
} else {
// 如果使用者輸入的沒毛病,那就載入介面
$.ajax({
url: "http://localhost:8888/register",
type: 'post',
dataType: 'json',
data: {
username: userName,
password: userPassword
},
success: function (res) {
console.log(res);
if (res.code == "0") {
alert("註冊成功,前往登入!");
window.location.href = "./login.html";
}
},
error: function (err) {
console.log(err.responseText);
if (err.responseText == "註冊失敗,姓名重複!") {
alert("使用者名稱已被註冊!");
} else if (err.responseText == "註冊失敗,名額已滿!") {
alert("註冊失敗,名額已滿!");
} else if (err.responseText == "註冊失敗,密碼為空!") {
alert("註冊失敗,密碼為空!");
} else if (err.responseText == "註冊失敗,姓名過長!") {
alert("註冊失敗,姓名過長!");
} else if (err.responseText == "註冊失敗,密碼過長!") {
alert("註冊失敗,密碼過長!");
} else {
alert("未知錯誤!");
}
}
})
}
})
})
</script>
</body>
</html>
複製程式碼
如此,我們在使用者點選 註冊 按鈕的時候,進行介面的呼叫,傳送資料到了後端,如果成功了,那就彈窗,並跳轉到登入頁;如果沒成功,就彈窗提示。
接著,我們編寫 Node,前端呼叫介面後,Node 判斷這兩個引數是否為空,如果不為空,則將資料儲存到資料庫。
index.js
// ... 其他程式碼省略,請自行前往章節 4.2 後端介面 獲取其他程式碼
if (pathName == "/sendMessage") { // 提交留言資訊
console.log("\n【API - 提交留言資訊】");
} else if (pathName == "/login") { // 登入
console.log("\n【API - 登入】");
} else if (pathName == "/register") { // 註冊
console.log("\n【API - 註冊】");
result = JSON.parse(result);
let username = result.username; // 使用者名稱
let password = result.password; // 密碼
let time = getNowFormatDate(); // 時間
if (!username) { // 使用者名稱為空
res.end("註冊失敗,使用者名稱為空。");
return;
} else if (!password) { // 密碼為空
res.end("註冊失敗,密碼為空!");
return;
} else if(username.length > 10) { // 姓名過長
res.end("註冊失敗,姓名過長!");
return;
} else if(password.length > 20) { // 密碼過長
res.end("註冊失敗,密碼過長!");
return;
} else {
// 查詢 user 表
// 使用 Promise 的原因是因為中間呼叫了兩次資料庫,而資料庫查詢是非同步的,所以需要用 Promise。
new Promise( (resolve, reject) => {
// 新增的 SQL 語句及新增的欄位資訊
let readSql = "SELECT * FROM user";
// 連線 SQL 並實施語句
connection.query(readSql, function (error1, response1) {
if (error1) { // 如果 SQL 語句錯誤
throw error1;
} else {
console.log("\nSQL 查詢結果:");
// 將結果先去掉 RowDataPacket,再轉換為 json 物件
let newRes = JSON.parse(JSON.stringify(response1));
console.log(newRes);
// 判斷姓名重複與否
let userNameRepeat = false;
for(let item in newRes) {
if(newRes[item].user_name == username) {
userNameRepeat = true;
}
}
// 如果姓名重複
if(userNameRepeat) {
res.end("註冊失敗,姓名重複!");
return;
} else if(newRes.length > 300) { // 如果註冊名額已滿
res.end("註冊失敗,名額已滿!");
return;
} else { // 可以註冊
resolve();
}
}
});
}).then( () => {
console.log("\n第二步:");
// 新增的 SQL 語句及新增的欄位資訊
let addSql = "INSERT INTO user(user_name,user_password, time) VALUES(?,?,?)";
let addSqlParams = [result.username, result.password, time];
// 連線 SQL 並實施語句
connection.query(addSql, addSqlParams, function (error2, response2) {
if (error2) { // 如果 SQL 語句錯誤
console.log("新增錯誤:");
console.log(error2);
return;
} else {
console.log("\nSQL 查詢結果:");
console.log(response2);
console.log("\n註冊成功!");
// 返回資料
res.write(JSON.stringify({
code: "0",
message: "註冊成功!"
}));
// 結束響應
res.end();
}
});
})
// Promise 結束
}
// 註冊流程結束
}
複製程式碼
最後,我們在檢視下該功能是否成功:
4.4 登入功能
在上面,我們完成了註冊功能,那麼相對來說,登入功能就容易通了,因為查詢部分我們已經試過了一次。
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="keywords" content="前端,jsliang,bootstrap,企業建站">
<meta http-equiv="description" content="jsliang 為你打造最好的企業服務">
<link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>登入-jsliang 前端有限公司</title>
<link rel="stylesheet" href="./css/index.css">
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<!-- 程式碼省略,有需要的小夥伴請在第四章前言部分下載程式碼 -->
<script src="./js/jquery-3.3.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/islogin.js"></script>
<script>
$(function () {
$("#login-submit").click(function () {
let userName = $("#userName").val(); // 使用者名稱
let userPassword = $("#userPassword").val(); // 密碼
if (!userName) {
alert("請輸入使用者名稱");
$("#userName").focus();
} else if (!userPassword) {
alert("請輸入密碼");
$("#userPassword").focus();
} else if (userName.length > 10) {
alert("請輸入少於 10 位的使用者名稱");
$("#userName").focus();
} else if (userPassword.length > 20) {
alert("請輸入少於 20 位的密碼");
$("#userPassword").focus();
} else {
$.ajax({
url: "http://localhost:8888/login",
type: 'post',
dataType: 'json',
data: {
username: userName,
password: userPassword
},
success: function (res) {
console.log(res);
if (res.code == "0") {
sessionStorage.setItem("id", res.data.id);
sessionStorage.setItem("userName", res.data.userName);
alert("登入成功!");
window.location.href = "./messageBoard.html";
} else if (res.code == "1") {
alert("登入失敗,密碼錯誤!");
}
},
error: function (err) {
console.log(err.responseText);
if (err.responseText == "不存在該使用者!") {
alert("不存在該使用者!");
} else if (err.responseText == "登入失敗,使用者名稱為空!") {
alert("登入失敗,使用者名稱為空!");
} else if (err.responseText == "登入失敗,密碼為空!") {
alert("登入失敗,密碼為空!");
} else if (err.responseText == "登入失敗,姓名過長!") {
alert("登入失敗,姓名過長!");
} else if (err.responseText == "登入失敗,密碼過長!") {
alert("登入失敗,密碼過長!");
} else {
alert("未知錯誤!");
}
}
})
}
})
})
</script>
</body>
</html>
複製程式碼
編寫完前端的程式碼後,我們進行 Node 程式碼的編輯:
index.js
// ... 其他程式碼省略,請自行前往章節 4.2 後端介面 獲取其他程式碼
if (pathName == "/sendMessage") { // 提交留言資訊
console.log("\n【API - 提交留言資訊】");
} else if (pathName == "/login") { // 登入
console.log("\n【API - 登入】");
result = JSON.parse(result);
let username = result.username; // 使用者名稱
let password = result.password; // 密碼
if (!username) { // 使用者名稱為空
res.end("登入失敗,使用者名稱為空!");
return;
} else if (!password) { // 密碼為空
res.end("登入失敗,密碼為空!");
return;
} else if(username.length > 10) {
res.end("登入失敗,姓名過長!");
return;
} else if(password.length > 20) {
res.end("登入失敗,密碼過長!");
return;
} else {
// 新增的 SQL 語句及新增的欄位資訊
let readSql = "SELECT * FROM user WHERE user_name = '" + username + "'";
// 連線 SQL 並實施語句
connection.query(readSql, function (error1, response1) {
if (error1) {
throw error1;
} else {
if(response1 == undefined || response1.length == 0) { // 不存在使用者
res.end("\n不存在該使用者!");
return;
} else { // 存在使用者
console.log("\n存在該使用者!");
let newRes = JSON.parse(JSON.stringify(response1));
console.log(newRes);
if(newRes[0].user_password == password) { // 密碼正確
// 返回資料
res.write(JSON.stringify({
code: "0",
message: "登入成功!",
data: {
id: newRes[0].id,
userName: newRes[0].user_name
}
}));
res.end();
} else { // 密碼錯誤
// 返回資料
res.write(JSON.stringify({
code: "1",
message: "登入失敗,密碼錯誤!"
}));
res.end();
}
// 判斷密碼正確與否完畢
}
// 存在使用者處理結束
}
});
}
// 登入步驟結束
} else if (pathName == "/register") { // 註冊
console.log("\n【API - 註冊】");
}
複製程式碼
很好,前端和後端都編寫完畢,是時候查驗下功能是否實現了:
4.5 留言功能
現在,我們就剩下留言功能了,一鼓作氣做好它吧!
messageBoard.html
<!-- 留言板 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="keywords" content="前端,jsliang,bootstrap,企業建站">
<meta http-equiv="description" content="jsliang 為你打造最好的企業服務">
<link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>留言板-jsliang 前端有限公司</title>
<link rel="stylesheet" href="./css/index.css">
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<!-- 程式碼省略,基礎程式碼請前往本章節前言下載 -->
<script src="./js/jquery-3.3.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/islogin.js"></script>
<script>
$(function() {
let userName = sessionStorage.getItem("userName");
let userId = sessionStorage.getItem("id");
// 查詢留言板
if(userName && userId) { // 如果有儲存
$.ajax({
url: "http://localhost:8888/getMessage",
type: 'get',
dataType: 'json',
success: function (res) {
console.log(res);
let li = ``;
for(let item in res.data) {
li = li + `
<li>
<span class="text-warning font-bold">☆ </span>
<span class="user-message">${res.data[item].user_message}</span>
<span>—— </span>
<span class="user-name">${res.data[item].user_name} [${res.data[item].user_id}]</span>
<span class="message-time">${res.data[item].time}</span>
</li>
`;
}
$("#message-board-ul").append(li);
},
error: function (err) {
console.log(err);
}
})
} else { // 如果沒有儲存
window.location.href = "../login.html";
}
// 提交留言
$("#message-submit").click(function() {
let messageText = $("#message").val()
if(!messageText) {
alert("留言內容不能為空");
} else if(messageText.length > 140) {
alert("留言長度不能超過 140 位!");
} else {
$.ajax({
url: "http://localhost:8888/sendMessage",
type: 'post',
dataType: 'json',
data: {
userid: userId,
username: userName,
message: messageText
},
success: function (res) {
console.log(res);
if(res.code == "0") {
alert("新增成功!");
window.location.reload();
}
},
error: function (err) {
console.log(err);
console.log(err.responseText);
if (err.responseText == "登入失敗,留言內容為空!") {
alert("登入失敗,留言內容為空!");
} else if (err.responseText == "登入失敗,字數超過限制!") {
alert("登入失敗,字數超過限制!");
} else {
alert("未知錯誤!");
}
}
})
}
})
})
</script>
</body>
</html>
複製程式碼
接著編寫下 Node 後端:
index.js
// ... 其他程式碼省略,請自行前往章節 4.2 後端介面 獲取其他程式碼
if (pathName == "/sendMessage") { // 提交留言資訊
console.log("\n【API - 提交留言資訊】");
result = JSON.parse(result);
let id = result.userid; // id
let userName = result.username; // 使用者名稱
let messageText = result.message; // 留言內容
let time = getNowFormatDate(); // 時間
if(!messageText) {
res.end("登入失敗,留言內容為空!");
return;
} else if(messageText.length > 140) {
res.end("登入失敗,字數超過限制!");
return;
} else {
// 新增的 SQL 語句及新增的欄位資訊
let addSql = "INSERT INTO message(user_message, user_id, user_name, time) VALUES(?, ?, ?, ?)";
let addSqlParams = [messageText, id, userName, time];
// 連線 SQL 並實施語句
connection.query(addSql, addSqlParams, function (error1, response1) {
if (error1) { // 如果 SQL 語句錯誤
throw error1;
} else {
console.log("\n新增成功!");
// 返回資料
res.write(JSON.stringify({
code: "0",
message: "新增成功!"
}));
// 結束響應
res.end();
}
})
}
} else if (pathName == "/login") { // 登入
console.log("\n【API - 登入】");
} else if (pathName == "/register") { // 註冊
console.log("\n【API - 註冊】");
}
// ... 其他程式碼省略,請自行前往章節 4.2 後端介面 獲取其他程式碼
if (pathName == "/getMessage") { // 獲取留言資訊
console.log("\n【API - 獲取留言資訊】");
// 解析 url 引數部分
let params = url.parse(req.url, true).query;
console.log("\n引數為:");
console.log(params);
// 新增的 SQL 語句及新增的欄位資訊
let readSql = "SELECT * FROM message";
// 連線 SQL 並實施語句
connection.query(readSql, function (error1, response1) {
if (error1) {
throw error1;
} else {
let newRes = JSON.parse(JSON.stringify(response1));
console.log(newRes);
// 返回資料
res.write(JSON.stringify({
code: "1",
message: "查詢成功!",
data: newRes
}));
// 結束響應
res.end();
}
});
// 查詢完畢
} else if(pathName == "/") { // 首頁
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('<h1 style="text-align:center">jsliang 前端有限公司服務已開啟!</h1><h2 style="text-align:center">詳情可見:<a href="https://github.com/LiangJunrong/document-library/blob/master/other-library/Node/NodeBase.md" target="_blank">Node 基礎</a></h2>');
res.end();
}
複製程式碼
敲完程式碼再看下功能是否實現:
綜上,我們完成了所有的功能模組:註冊、登入以及留言。
五 工具整合
工欲善其事,必先利其器。
掌控好了工具,可以方便你更快地進行開發。
5.1 supervisor - 監聽 Node 改動
正如其官網所說,它是一個進行控制系統:
- 安裝外掛:
npm i supervisor -g
- 執行檔案:
supervisor app.js
- 檢視執行:
localhost:3000
平時,我們 node app.js
後,當我們修改了 app.js
的內容,就需要關閉 node 命令列再執行 node app.js
。
而我們使用 supervisor
後,我們修改了 app.js
中的內容,只要點選儲存,即可生效儲存後的程式碼,實現實時監聽 node 程式碼的變動。
關於這個工具,網上更詳細的攻略有:
5.2 PM2 - Node 程式管理
PM2 是 Node 程式管理工具,可以利用它來簡化很多 Node 應用管理的繁瑣任務,如效能監控、自動重啟、負載均衡等,而且使用非常簡單。
下面就對 PM2 進行入門性的介紹,基本涵蓋了 PM2 的常用的功能和配置:
- 全域性安裝 PM2:
npm i pm2 -g
- 監聽應用:
pm2 start index.js
- 檢視所有程式:
pm2 list
- 檢視某個程式:
pm2 describe App name/id
- 停止某個程式:
pm2 stop App name/id
。例如:
先通過
pm2 list
檢視:
App name | id | status |
---|---|---|
index | 0 | online |
只需要執行 pm2 stop index
或者 pm2 stop 0
即可。
- 停止所有程式:
pm2 stop all
- 重啟某個程式:
pm2 restart App name/id
- 刪除某個程式:
pm2 delete App name/id
如上,如果說我們的 supervisor
是監聽單個程式的話,那麼 PM2
就是監聽多個程式。
更多攻略:
六 參考資料
在編寫這篇文章的過程中,有一些參考資料是值得保留閱讀的:
- 經典:該類值得我們研讀
經典,就是隨著時間流逝,它還是那麼有參考價值。
- 嘗試:該類值得我們參考借鑑
Node 基礎模組
Node 編寫介面
MySQL 學習
Node 連線資料庫
Node 仿 Express
- nodejs模組:簡單http請求路由,仿express | CSDN - TTUZ
- 初學nodejs一:別被Express的API搞暈了 | 前端亂燉 - 飛天小黑神豬
- NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(一) | 倔強的石頭 - 掘金
- NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(二) | 倔強的石頭 - 掘金
- 仿 Express | Github - wallaceyuan
- Node.js 封裝仿照 express 的路由 | CSDN - c.
- 學習node中express框架中介軟體的相關知識及實踐 | Github - BadWaka
七 線上部署
關於線上部署及域名、伺服器相關的配置,jsliang 在另外一篇文章有所交代:雲伺服器建站。
如果小夥伴需要訂購雲伺服器來存放像 jsliang 個人網站類的靜態或者有 Node 後端的網頁,但卻不知道怎麼選擇,可以加 jsliang QQ:1741020489
諮詢,下面是一些優惠推廣:
騰訊雲推廣:
新使用者點這裡:
購買雲伺服器:
阿里雲推廣:
新使用者點這裡:
購買雲伺服器:
購買企業級雲伺服器:
八 歸納總結
綜上,搞定一切!
興許在前面程式碼的摧殘下,能看到這裡的小夥伴已經寥寥無幾了,但我堅信我該交代的基本都交代了,不該交代的也交代了~
所以,如果小夥伴看完真覺得不錯,那就點個贊或者給個 star 吧!你們的贊和 star 是我編寫更多更精彩文章的動力!GitHub 地址
如果小夥伴看完這裡要評論的話,可以加個暗語:Node 基礎,***
,這樣 jsliang 看到必回,哈哈~
- Node 基礎,我完成了!
- Node 基礎,我想說 jsliang 肯定還偷懶了,沒寫成最完美的,我不管我打賞了你趕緊給我完善下!
- ……
so, that's all, thanks~
-----------------------
後記
撰文不易,如果文章對小夥伴有幫助,希望小夥伴們給勤勞敲程式碼、辛苦撰文的 jsliang 進行微信/支付寶打賞,你們的每一次打賞都是最好的鼓勵,謝謝~
jsliang 的文件庫 由 樑峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.om/LiangJunron…上的作品創作。
本許可協議授權之外的使用許可權可以從 creativecommons.org/licenses/by… 處獲得。