Node 學習筆記
相關概念
介紹
Node.js不是一門語言,也不是庫、框架,Node.js是一個JavaScript運行時環境,簡單點來說就是Node.js可以解析和執行JavaScript代碼。以前的JS代碼只能在瀏覽器中進行解析,現在有了Node.js,JS代碼完全可以脫離瀏覽器來運行。
在瀏覽器中的JS,主要由ECMAScript(JS基礎語法)+BOM(window……)+DOM(document……)三部分組成,而Node.js中的JS沒有BOM、DOM,只有ECMAScript和一些Node提供的服務器級別的API(例如文件讀寫、網絡服務的構建、網絡通信、http服務器等等操作)組成。
特點
Node.js的特點:事件驅動、非阻塞I/O模型(異步)、輕量高效;
npm是Node.js的包管理工具,是世界上最大的開源庫生態系統,絕大多數JS相關的包都存放在了npm上,這樣做的目的是為了讓開發人員更加方便的去下載使用包。
構建過程
值得一提的是,Node.js構建在Chrome V8引擎(JS代碼解析工具)之上。解釋:代碼只是具有特定格式的字符串而已,引擎可以認識它,引擎可以將其解析和執行,Google Chrome的V8引擎是目前公認的解析執行JS代碼最快的引擎,Node.js的作者把Google Chrome中的V8引擎移植出來,開發了一個獨立的JS運行時環境。
作用
Node.js能做Web服務器後台、命令行工具(例如npm)。
參考書籍
- 《深入淺出Node.js》:偏理論,幾乎沒有任何實戰性內容,對理解底層原理很有幫助;
- 《Node.js權威指南》:對Node.js的API進行講解,也沒有實戰性內容;
- Node入門://www.nodebeginner.org/index-zh-cn.html;
通過簡單案例了解Node.js
搭建簡單的服務器並具有一定的響應
const http = require('http')
const server = http.createServer()
// 監聽客戶端的請求
server.on('request', (request, response) => {
switch (request.url) {
case '/login':
response.write('login.html')
break
case '/register':
response.write('register.html')
break
default:
response.write('bad request')
break
}
response.end()
})
// 綁定端口號,啟動服務器
server.listen(8848, () => console.log('server is running, access server with //127.0.0.1:8848/'))
基礎知識
使用命令行執行JS文件
node fileName.js
,注意:
- JS文件名不能為node.js;
- node 後加上JS文件路徑就行了,沒必要使用cmd定位到JS文件所在目錄;
核心模塊
Node為JS提供了許多服務器級別的API,這些API絕大多數都被包裝到了不同的核心模塊中,例如文件操作相關的fs
核心模塊,http服務構建的http
模塊,path
路徑操作模塊、os
操作系統信息模塊等等,詳細的可查看官網API://nodejs.cn/api/http.html
模塊系統
require是一個方法,它的作用就是用來加載執行模塊中的代碼的。在Node中,模塊有三種:
- 具名的核心模塊,例如fs、http;
- 用戶自己編寫的文件模塊(.js文件,同目錄下的相對路徑必須加./);
- 第三方模塊
特點
在Node中,沒有全局作用域,只有模塊作用域(即文件作用域),外部訪問不到內部,內部也訪問不到外部(外部-內部概念,相對於require語句的位置而言);
require方法的作用
加載文件模塊並執行裏面的代碼
拿到被加載文件模塊導出的接口對象
在每個文件模塊中,都提供了一個對象:exports,默認是一個空對象,用於模塊之間的通信。
在node中,沒有全局作用域,只有模塊作用域,要使用其它模塊的方法/變量,只有通過exports:
服務器(http核心模塊)
搭建簡單的服務器
// 導入http核心模塊
const http = require('http')
// 創建一個服務器
const server = http.createServer()
// 對客戶端的request請求作出響應
server.on('request', (req, res) => {
res.end('Hello, Node.js')
})
// 設置端口號,啟動服務器
const port = 8080
server.listen(8080, () => console.log('server is running, access to //127.0.0.1:8080/'))
明確返回數據的編碼方式
對於服務器而言,向客戶端返回的數據默認格式是UTF-8,但是在瀏覽器中,如果沒有明確指定Content-Type,那麼瀏覽器會默認按照客戶端(電腦)的默認編碼(通常為GBK即GB2312
)去解析服務器返回的數據,所以如果我們不設置Content-Type,那麼在瀏覽器中看到服務器返回的中文就是亂碼的。
所以,我們在server.on的回調函數中添加如下代碼,明確(告訴)瀏覽器使用指定編碼解析服務器返回的數據:
response.setHeader('Content-Type', 'text/plain; charset=utf-8')
添加前的Header:
添加後的Header:
注意:
對於text/plain,是指定瀏覽器以什麼方式去解析服務器返回的數據,例如:text/plain是指定以普通文本的方式解析返回的數據,text/html是指定以HTML文件的方式解析返回的數據。如果明確設置Content-Type,瀏覽器會默認按照普通文本去解析返回的數據。
查看不同文件類型對應於Content-Type請看這裡//tool.oschina.net/commons
查看電腦默認編碼方式
//blog.csdn.net/zp357252539/article/details/79084480
實現Apache服務器的訪問功能[通過文件url,直接訪問文件]
和Apache服務器類似,所有需要訪問的文件都放在一個統一的目錄里,用戶通過url即可訪問:
// 導入http核心模塊
const http = require('http')
// 導入fs核心模塊
const fs = require('fs')
// 創建一個服務器
const server = http.createServer()
// 對客戶端的請求,服務器做相應的處理
server.on('request', (request, response) => {
// 文件根目錄
const baseUrl = 'D:/Programming/Web/Test'
// 默認顯示index.html
var url = request.url
if (url === '/') {
url = '/index.html'
}
// 讀取服務器文件,並傳給客戶端
fs.readFile(baseUrl + url, (err, data) => {
if (err) {
// 若文件不存在
response.end('404 not found.')
} else {
response.end(data)
}
})
})
// 監聽某個端口號,啟動服務
server.listen(8080, () => console.log('服務已啟動,URL: //127.0.0.1:8080/'))
簡潔的服務器搭建方式
// 應用程序
// 把當前模塊所有的依賴項都聲明在文件模塊最上面
const http = require('http')
const fs = require('fs')
http.createServer((req, res) => {
res.end('服務器已收到請求')
}).listen(8080, () => {
console.log('服務已啟動,URL: //127.0.0.1:8080/')
})
客戶端請求過程[以後端渲染為例]
在客戶端向服務器發起請求時,服務器將網頁渲染好,發送給客戶端。瀏覽器在收到 HTML 響應內容之後,就要開始從上到下依次解析,在解析過程中如果發現:link、script、img、iframe、video、audio等帶有 src 或者 href(link) 屬性標籤(具有外鏈的資源)的時候,瀏覽器會自動對這些資源發起新的請求。
所以,對於後端渲染的網頁,外鏈資源的地址不要像本地開發那樣使用相對路徑,而是應該以 url 的方式進行獲取,例如服務器開放了 public 目錄,所以請求路徑都寫成 /public/xxx
的形式(對於位於同一服務器的資源),/
在這裡就是表示根路徑的意思,瀏覽器在請求時會自動把域名加上。
案例請見 /example/feedback
服務端重定向
// 服務端重定向
res.statusCode = 302
res.setHeader('Location', '/')
res.end()
具體效果,當時沒有弄成功,後面可以再嘗試一下。
文件操作(fs核心模塊)
fs.readFile(path[, options], callback)
不指定options(路徑 path 可以省略 ./,異步)
// 導入文件操作核心模塊(fs)
const fs = require('fs')
// 讀取文件
fs.readFile('../index.html', (err, data) => {
if (err) {
// 文件讀取錯誤
console.log(err)
} else {
// 文件讀取成功(如果沒有指定字符編碼[options],則返回原始的 buffer)
console.log(data)
}
})
輸出結果如下:
D:\Programming\Web\Test\js>node fileOperate
<Buffer 3c 21 44 4f 43 54 59 50 45 20 68 74 6d 6c 3e 0d 0a 3c 68 74 6d 6c 20 6c 61 6e 67 3d 22 7a 68 22 3e 0d 0a 09 3c 68 65 61 64 3e 0d 0a 09 09 3c 6d 65 74 ... 238 more bytes>
指定options
// 導入文件操作核心模塊(fs)
const fs = require('fs')
// 讀取文件
fs.readFile('../index.html', 'utf8', (err, data) => {
if (err) {
// 文件讀取錯誤
console.log(err)
} else {
// 文件讀取成功(指定字符編碼[options]為UTF-8)
console.log(data)
}
})
輸出結果如下:
D:\Programming\Web\Test\js>node fileOperate
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
<h3>Hello, World</h3>
</body>
</html>
fs.readdir(path[, options], callback)
簡單讀取指定目錄下的文件和目錄:
// 導入文件操作核心模塊(fs)
const fs = require('fs')
// 讀取文件目錄
fs.readdir('D:/Programming/Web/Test', (err, list) => {
if (err) {
// 目錄不存在
console.log(err)
} else {
// 目錄讀取成功
console.log(list)
}
})
輸出結果如下:
D:\Programming\Web\Test\js>node fileOperate
[ 'css', 'file', 'img', 'index.html', 'js', 'less' ]
URL解析(url核心模塊)
一般的,對於客戶端的請求,通過 if-else 結構都能夠進行相應判斷並響應,但是對於表單提交的數據更加複雜,則需要使用 url
核心模塊:
// 導入url解析核心模塊(url)
const url = require('url')
// 通過parse方法解析url
const urlObj = url.parse('//www.baidu.com:8080/login.html?account=123456&password=000000')
// 打印轉換結果
console.log(urlObj)
結果如下:
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com:8080',
port: '8080',
hostname: 'www.baidu.com',
hash: null,
search: '?account=123456&password=000000',
query: 'account=123456&password=000000',
pathname: '/login.html',
path: '/login.html?account=123456&password=000000',
href: '//www.baidu.com:8080/login.html?account=123456&password=000000'
}
如果 url.parse()
方法第二個參數為 true
,則結果中的 query
屬性會被轉換成對象。
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com:8080',
port: '8080',
hostname: 'www.baidu.com',
hash: null,
search: '?account=123456&password=000000',
query: [Object: null prototype] { account: '123456', password: '000000' },
pathname: '/login.html',
path: '/login.html?account=123456&password=000000',
href: '//www.baidu.com:8080/login.html?account=123456&password=000000'
}
模塊系統
CommonJS 模塊規範
在 Node 中對的 JavaScript 還有一個很重要的概念:模塊系統。
- 模塊作用域;
- 使用 require 方法用來加載模塊;
- 使用 exports 接口對象用來導出模塊中的成員;
模塊導出成員
就如我們前面所了解的那樣,每個模塊中默認都有個 exports 對象,用以實現模塊之間的「通信」。
// module_d.js
const ID = '9527'
exports.id = ID
// module_c.js
const moduleD = require('./module_d')
console.log(moduleD.id)
如果只想導出 module_d.js
中的 ID 且不像上面那樣掛載到 exports 對象中如何處理?直接將 ID 賦值給 exports?
// module_d.js
const ID = '9527'
exports = ID
// module_c.js
const moduleD = require('./module_d')
console.log(moduleD)
// result
// {}
我們發現,這樣不行,無論如何,exports 都只能是個對象。
導出多個成員
方式和我們前面了解的一樣,通過掛載到 exports 中的方式實現:
// module_d.js
const name = '張三'
const age = 18
const sex = '男'
exports.name = name
exports.age = age
exports.sex = sex
// module_c.js
const moduleD = require('./module_d')
console.log(moduleD)
// result
// { name: '張三', age: 18, sex: '男' }
導出單個成員
Node 中,每個模塊內部都有一個自己的 module 對象,在該 module 對象中,有個成員叫 exports。
// module_d.js
const ID = '9527'
// 只能導出一個,多次導出,後者會覆蓋前者
module.exports = ID
// module_c.js
const moduleD = require('./module_d')
console.log(moduleD)
底層原理
究竟 exports 和 module.exports 有什麼區別呢?底層中,在剛開始有這麼一段代碼:var exports = module.exports
,最終其它模塊通過 require 獲取時,獲取的是 module.exports。也就是說,在中途修改 exports 的指向,最終都不會影響 module.exports,exports 出現的原因是因為 module.exports 不夠簡潔,內容相對而言更加冗長。
// module_d.js
exports.age = 12
exports = { age: 20 }
// module_c.js
const moduleD = require('./module_d')
console.log(moduleD)
// result
// { age: 12 }
require加載規則
優先從緩存加載
// module_c.js
const module_d = require('./module_d')
const module_e = require('./module_e')
console.log('in module_e', module_e)
// module_d.js
console.log('module d loaded!')
const module_e = require('./module_e')
console.log('in module_d', module_e)
// module_e.js
console.log('module e loaded!')
// 導出的方法
module.exports = function(a, b) {
return a + b
}
運行 module_c.js:
D:\Programming\Web\Test\js>node module_c
module d loaded!
module e loaded!
in module_d [Function]
in module_e [Function]
從結果中我們可以看出:由於在 module_d 中已經加載過 module_e 了,所以在 module_c 中不會重複加載 module_e(不會執行 module_e 中的代碼)就可以拿到 module_e 導出的對象,這樣做的目的是通過緩存加載,避免重複加載代碼,從而提高模塊加載效率。
require方法參數——用戶自定義模塊
路徑形式的模塊:
- ./:當前目錄,不可省略;
- ../:上一級目錄,不可省略;
- /xx:表示當前文件模塊所屬磁盤根路徑(例如C盤,D盤)[幾乎不用];
- d:/xxx:某個盤符下的路徑[幾乎不用];
require方法參數——核心模塊
對於require方法中的參數,除了用戶自定義的模塊(文件)以外,還有核心模塊、第三方模塊。事實上,核心模塊也是一個個的文件,只不過被編譯到了Node環境二進制文件中,如果要查看代碼(Node開源),github:
require方法參數——第三方模塊
第三方模塊使用事項:
- 凡是第三方模塊通常(
都必須,npm 只是一個下載工具而已,只要理清了依賴,粘貼複製文件也可以使用第三方模塊)通過 npm 來下載; - 使用時,通過 require(‘包名’) 的方式來進行加載使用;
- 不可能有任何一個第三方包和核心模塊的名字是一樣的;
- 第三方模塊既不是核心模塊,也不是路徑形式的模塊,加載時:
- 先找到當前文件所處目錄中的 node_modules 目錄[以art-template(模板引擎)為例];
- 找到 node_modules/art-template 目錄;
- 找到第三方目錄下的 package.json 文件;
- 找到 .json 文件中的 main 屬性,該屬性記錄了 art-template 的入口模塊;
- 然後加載使用第三方模塊,實際上最終加載的還是文件;
如果 package.json 文件不存在或者 main屬性 指定的入口模塊不存在,則 node 會自動查找該目錄下的 index.js 文件作為入口模塊,也就是說 index.js 會作為一個默認備選項。
如果以上任何一個條件都不成立,則會進入上一級目錄中的 node_modules 目錄查找,如果上一級還沒有,則繼續往上級找,直到當前磁盤根目錄還找不到,則會報錯:Cannot find module 'xxx'
。
注意:我們項目中有且只有一個 node_modules,放在項目根目錄中,這樣的話項目中所有的子目錄中的代碼都可以加載到第三方包,不會出現多個 node_modules 目錄。
更多底層知識,參考:
- blog://www.infoq.cn/article/nodejs-module-mechanism/;
- book:《深入淺出Node.js》模塊系統章節;
npm 和 package.json
npm
即 node package manager,是 node 包管理工具。
快捷操作:npm 以空格分隔,安裝多個第三方模塊:
npm i art-template jquery better-scroll
建議每一個項目都要有一個 package.json
文件(包描述文件,就像產品說明書一樣),這個文件可以通過 npm init
的方式自動初始化出來:
D:\Programming\Web\Test>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (test) test
version: (1.0.0) 0.0.1
description: 學習知識的代碼書寫區域,例如學習Less,Node等知識點
entry point: (index.js) main.js
test command:
git repository:
keywords: learn
author: [email protected]
license: (ISC) MIT
About to write to D:\Programming\Web\Test\package.json:
{
"name": "test",
"version": "0.0.1",
"description": "學習知識的代碼書寫區域,例如學習Less,Node等知識點",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"learn"
],
"author": "[email protected]",
"license": "MIT"
}
Is this OK? (yes) yes
生成的 .json 文件如上,現在生成了兩個文件:
就目前來講,最有的是 package.json
文件中的 dependencies
選項,可以用來幫我們保存第三方包的依賴信息,就算 node_modles
刪除了也不用擔心,我們只需要使用 npm install
命令,就會把 package.json
中的 dependencies
中所有的依賴項都下載回來,不過請注意:
- 建議每個項目的根目錄都要有個
package.json
文件; - 建議執行
npm install
命令安裝第三方模塊時,都要加上--save
這個選項,目的一是用來保存依賴項的信息(自動添加至 package.json 文件的dependencies
選項中),如果不添加該選項,則不會在package.json
文件中自動添加【現在也能添加】;
npm 網站
npm 命令行工具
npm 的第二層含義就是一個命令行工具,只要安裝了 node 就已經安裝了 npm。npm 同樣有版本的概念,通過以下命令查看 npm 版本:
npm --version
通過以下命令升級 npm:
npm install --global npm
npm 常用命令
- npm init:生成 package.json 文件;
- npm init –yes(npm init -y):跳過嚮導,快速生成;
- npm intall(npm i):一次性把 package.json 文件 dependencies 選項中的依賴項全部安裝;
- npm install 包名(npm i 包名):只下載(現在也能添加安裝信息到 package.json 文件中的 dependencies 選項);
- npm install –save 包名(npm i -S 包名):下載並且保存依賴項(添加安裝信息到 package.json 文件中的 dependencies 選項中);
- npm uninstall 包名(npm un 包名):只刪除,如果有依賴項會依然保存;
- npm uninstall 包名 –save(npm un 包名 -S):刪除的同時也會把依賴信息去除;、
- npm help:查看使用幫助;
- npm 命令 –help:查看某一指令的使用幫助,例如忘記了 uninstall 命令的簡寫可通過
npm uninstall --help
查看使用幫助;
解決 npm 被牆問題
npm 存儲包文件的服務器在國外,有時候會被牆速度很慢,對於此,淘寶開發團隊把 npm 在國內做了一個備份://developer.aliyun.com/mirror/NPM?from=tnpm(//npm.taobao.org/)
安裝淘寶的 cnpm:
# 在任意目錄執行都可以
# --global 表示安裝到全局,而非當前目錄
npm install --global cnpm
在接下來的使用中,將之前的 npm
命令替換成 cnpm
即可。
舉個例子:
# npm 默認走國外 npm 服務器,速度比較慢
npm install jquery
# 使用 cnpm 就會通過淘寶的服務器來下載,比較快
cnpm install jquery
如果不想安裝 cnpm
又想使用淘寶的服務器來下載:
npm install jquery --registry=//registry.npm.taobao.org
但是每一次手動這樣加參數很麻煩,所以我們可以把這個選項加入配置文件中:
npm config set registry //registry.npm.taobao.org
# 查看 npm 配置信息
npm config list
通過以上命令的配置,以後所有的 npm install
都會默認通過淘寶的服務器來下載。
Express框架
原生的 http 核心模塊在某些方面表現不足以應對我們的開發需求,所以我們需要使用框架來加快我們的開發效率。框架的目的就是提高效率,讓我們的代碼更高度統一。在 Node 中,有很多 Web 開發框架,我們這裡以學習 express 框架(創始人 github://github.com/tj)為主。
起步
安裝
# npm install express --save
npm i express -S
輸出 Hello, World
// 引入第三方模塊——express
const express = require('express')
// 創建app
const app = express()
// 監聽默認請求
app.get('/', (req, res) => {
res.send('Hello, World!')
})
// 綁定端口號,啟動服務
app.listen(8080, () => {
console.log('server is running...')
console.log('URL: //127.0.0.1:8080/')
})
修改代碼後自動重啟
我們這裡可以使用第三方命令行工具:nodemon
、supervisor
來幫我們解決頻繁修改代碼重啟服務器問題。
無論是 nodemon
還是 supervisor
,都是基於 Node.js 開發的第三方命令行工具,我們要使用必須獨立安裝:
# 安裝 nodemon
npm i nodemon --global
# 安裝 supervisor
npm i supervisor --global
# 查看是否安裝成功
nodemon --version
安裝完畢之後,使用:
# app.js 可省略 .js
node app
# 使用 nodemon
nodemon app
# 使用 supervisor
supervisor app
基本路由
get:
// 當客戶端以 GET 方法請求 / 的時候,執行對應的處理函數
app.get('/', (req, res) => {
res.send('Got a GET request.')
})
post:
// 當客戶端以 POST 方法請求 / 的時候,執行對應的處理函數
app.post('/', (req, res) => {
res.send('Got a POST request.')
})
靜態服務
對於前面通過底層代碼開放出來的資源做法,express 框架也對此進行了封裝:
// 請求路徑必須以第一次參數開頭,訪問第二個參數所指目錄中的文件
app.use('/lvdm/', express.static('./public/'))
外部可訪問的 url 表現為://127.0.0.1:8080/lvdm/less/index.less
注意:第一個參數是任意指定的,相當於一個別名,但為了避免混淆,最好和目錄名稱保持一致。
若不指定第一參數,即表示沒有別名,可以直接訪問,此時:
app.use(express.static('./public/'))
外部可訪問的 url 表現為://127.0.0.1:8080/less/index.less
在 Express 中配置使用 art-template
模板引擎
art-template:
github 倉庫://github.com/aui/art-template;
中文官方文檔://aui.github.io/art-template/zh-cn/index.html;
安裝
# npm install art-template --save
npm i art-template -S
# ...
npm i express-art-template -S
or
npm i -S art-template express-art-template
配置
app.engine('art', require('express-art-template'))
第一個參數表示:當渲染以 .art
結尾的文件時,使用 art-template 模板引擎;express-art-template 是專門用在 Express 中,把 art-template 整合到 Express 中的;雖然外面不需要加載 art-template 但是必須安裝,原因就在於 express-art-template 依賴了 art-template。
使用
app.get('/404', (req, res) => {
res.render('404.art')
})
Express 為 response(res) 對象提供了一個方法——render。render 方法默認是不可用的,但是如果配置了模板引擎就可以使用。res.render('html 模塊名', {模板數據})
:第一個參數不能寫路徑,默認會去項目中的 views
目錄查找該模板文件,也就是說 Express 框架有個約定,默認地認為開發人員把所有視圖文件(.html
)都放在了 views
目錄中。
優化
當然,我們可以通過直接修改 views 目錄中的 html 文件,將他們的後綴名從 .html 修改成 .art 後綴就可以使用 art-template了,但是這樣反覆操作很不方便,編譯器代碼高亮也會受影響。如何解決呢?細心一點就會發現,在配置過程中,將 app.engine()
方法第一個參數修改成 'html'
,就可以直接在 .html
中使用 art-template
。
同樣地,也可以通過以下代碼修改視圖文件的存儲目錄,注意第一個參數為 views
:
app.set('views', 視圖文件指定存儲路徑)
後端獲取請求參數(例如表單)
get 請求
app.get('/xxx', (req, res) => {
console.log(req.query) // { name: '張三', message: '今天天氣不錯' }
// other code
})
注意:req.query
只能用來獲取 GET
請求的參數。
post請求
Express 框架並沒有直接提供獲取 POST 請求參數的方法,需要安裝中間件,也就是第三方模塊 —— body-parser
(//expressjs.com/en/resources/middleware/body-parser.html):
安裝:
npm i body-parser -S
配置:
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
使用:
app.post('/xxx', (req, res) => {
console.log(req.body) // { name: '張三', message: '今天天氣不錯' }
// other code
})
後端路由跳轉
【自己歸納總結】指前端跳轉除了 a
標籤跳轉頁面以外都是通過後端實現的跳轉,表單提交都是使用最原始的方式(表單的 action
屬性):
原生
res.statusCode = 302
res.setHeader('Location', '/')
Express 框架
res.redirect('/')
路由模塊提取
MongoDB
官方://www.mongodb.com/
菜鳥教程://www.runoob.com/mongodb/mongodb-tutorial.html
關係型數據庫和非關係型數據庫
關係型數據庫就是表與表之間存在關係的數據庫,例如常見的:MySQL、SqlServer、Oracle等數據庫;相對而言,非關係型數據庫(NoSQL:Not Only SQL)更加靈活,表與表之間沒有關係,不需要操作之前設計表結構。值得一提的是,MongoDB 是所有非關係型數據庫中最像關係型數據庫的數據庫。
基本概念
和其它關係型數據庫相比,MongoDB 數據庫在許多方面都有所區別:
- 概念上:MongoDB 有數據庫、集合、文檔等概念,分別與傳統關係型數據庫的數據庫、數據表、數據記錄表示統一含義;
- 結構上:MongoDB 非常靈活,不需要像 MySQL、Sql Server 等傳統關係型數據庫一樣,MongoDB 不需要事先創建數據庫,設計表結構,只需要在插入數據的時候,往哪個數據庫哪個集合操作就可以了,創建數據庫、集合等操作都由 MongoDB 幫你完成,同時 MongoDB 文檔結構很靈活,沒有任何限制;
MongoDB 數據庫、集合、文檔表現形式為:
{
// 系統數據庫 admin
admin: {
// ...
},
// other system database here.
// 業務學校數據庫
school: {
// 學生集合
students: [
{ name: '張三', age: 20 },
{ name: '李四', age: 22 },
{ name: '王五', age: 21 },
// ...
]
},
// ...
}
它們的表現形式也很熟悉,為 JS 對象、數組的形式。
安裝
過程大致為:下載 -> 安裝 -> 配置環境變量[將 bin
目錄地址放到 Path 環境變量中就可以了] -> 命令查看是否安裝成功。命令查看方式:
C:\Users\haveadate>mongod --version
db version v4.4.2
Build Info: {
"version": "4.4.2",
"gitVersion": "15e73dc5738d2278b688f8929aee605fe4279b0e",
"modules": [],
"allocator": "tcmalloc",
"environment": {
"distmod": "windows",
"distarch": "x86_64",
"target_arch": "x86_64"
}
}
啟動/關閉數據庫
啟動:
# MongoDB 默認使用執行 mongod 命令所處盤符目錄下的 /data/db 作為自己的數據存儲目錄
# 所以,在第一次執行該命令之前,需要自己手動創建對應的目錄
mongod
如果想要修改默認的數據存儲目錄,在每次執行時:
mongod --dbpath=數據存儲目錄路徑
關閉:
# 1. 在開啟服務的控制台直接 Ctrl + C 退出;
# 2. 直接關閉啟動 MongoDB 的命令行窗口;
連接/退出數據庫
連接:
# 該命令默認連接本機的 MongoDB 服務
mongo
退出:
# 在連接狀態命令行窗口中輸入 exit 退出連接
exit
基本命令
show dbs
:查看所有數據庫;db
:查看當前操作的數據庫;use 數據庫名
:切換到指定的數據庫,如果沒有則會新建;db.tableName.insertOne(obj)
:向表中插入一條數據,如果表不存在,則創建它;例如:db.students.insertOne({"name": "張三"})
;show collections
:查看查看數據庫中的所有表格(集合);db.tableName.find()
:查看錶格中的數據;
在 Node 中操作 MongoDB
使用官方的 mongodb
包
相關網頁:
總的來說,官方 mongodb
包比較原生、底層,實際開發不用它。
使用第三方 mongoose
包
mongoose
基於 MongoDB 官方的 mongodb
包進行了封裝,是業務開發常用的一個第三方包:
- 官網://mongoosejs.com/
- 官方指南://mongoosejs.com/docs/guide.html
- 官方 API 文檔://mongoosejs.com/docs/api.html
安裝:
npm i mongoose [-S]
使用:
const mongoose = require('mongoose')
// 連接 MongoDB 數據庫
// 該數據庫不需要事先創建,如果沒有,則在向集合中插入文檔時,MongoDB 會自動幫你創建
mongoose.connect('mongodb://localhost:27017/lvdm', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// 創建一個數據模型,也就是設計一個 cats 表置於 lvdm 數據庫中
// MongoDB 是動態的,非常靈活,只需要在代碼中設計數據庫即可
// mongoose 這個包可以讓設計編寫過程變得非常簡單
// 以下代碼是讓 lvdm 數據庫添加一個表 cats,表結構有個屬性 name
const Cat = mongoose.model('Cat', {
name: String
});
// 創建一個實例
const cat = new Cat({
name: 'Tom'
});
// 持久化保存 Cat 實例
cat.save().then(() => console.log('meow'));
mongoose 使用指南
設計 Schema 發佈 Model
// 引入 MongoDB 第三方操作模塊 mongoose
const mongoose = require('mongoose')
// 獲取 mongoose 架構
const Schema = mongoose.Schema
// 1. 連接數據庫[事前不必存在,若不存在,MongoDB 會幫我們創建]
mongoose.connect('mongodb://localhost:27017/college')
// 2. 設計文檔結構(也就是我們熟悉的表結構)
// 字段名稱就是表結構中的屬性名稱
// 約束的目的是為了保證數據的完整性,不要有臟數據
const stuSchema = new Schema({
stuName: {
type: String,
required: true // 進行約束:該字段必須有
},
stuGender: {
type: Boolean,
required: true
},
stuEmail: {
type: String
}
})
// 3. 將文檔結構發佈為模型
// mongoose.model 方法就是用來將一個架構發佈為 model
// 第一個參數:傳入一個首字母大寫單數名詞用來表示集合名稱
// mongoose 會自動將大寫名詞字符串生成小寫複數集合名稱
// 例如這裡,Student 會變成 students
// 第二個參數:架構(Schema)
// 返回值:模型構造函數
const Student = mongoose.model('Student', stuSchema)
增加數據
// 增加數據
const student = new Student({
stuName: '張三',
stuGender: true,
stuEmail: '[email protected]'
})
// 保存至數據庫
student.save().then(data => console.log(data))
結果如下:
(node:18736) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
(node:18736) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
{
_id: 5fed3b0974622e4930973c89,
stuName: '張三',
stuGender: true,
stuEmail: '[email protected]',
__v: 0
}
刪除數據
deleteOne:刪除一條數據
// 刪除一條指定條件的數據
Student.deleteOne({ stuName: '張三' }).then(data => console.log(data))
# 刪除返回結果
{ n: 1, ok: 1, deletedCount: 1 }
deleteMany:刪除多條數據
// 刪除多條指定條件的數據
Student.deleteMany({ stuGender: true }).then(data => console.log(data))
# 刪除返回結果
{ n: 2, ok: 1, deletedCount: 2 }
查找數據
find():
查找集合所有數據:
Student.find().then(data => console.log(data))
結果如下:
[
{
_id: 5fed3b0974622e4930973c89,
stuName: '張三',
stuGender: true,
stuEmail: '[email protected]',
__v: 0
},
{
_id: 5fed3be0ce8a8640848fa28a,
stuName: '李四',
stuGender: false,
stuEmail: '[email protected]',
__v: 0
}
]
根據條件查找:
// 查找性別為男的數據(true: 男,false: 女)
Student.find({ stuGender: true }).then(data => console.log(data))
結果如下:
[
{
_id: 5fed3b0974622e4930973c89,
stuName: '張三',
stuGender: true,
stuEmail: '[email protected]',
__v: 0
}
]
從上面代碼中我們可以看出,通過 find()
方法進行查找時,無論是否有條件,返回結果都是一個數組。與之對應的 findOne()
方法則只取查找結果第一條數據。
findOne():
// 查找第一條數據
Student.findOne().then(data => console.log(data))
結果如下:
{
_id: 5fed3b0974622e4930973c89,
stuName: '張三',
stuGender: true,
stuEmail: '[email protected]',
__v: 0
}
根據條件查找同樣如此,只返回查找結果中第一條文檔。
修改數據
findByIdAndUpdate:
// 根據 id 修改數據
// 參數1:id,參數2:需要修改的內容
Student.findByIdAndUpdate('5fed3be0ce8a8640848fa28a', {
stuGender: true
}).then(data => console.log(data))
updateOne:
// 根據指定條件修改第一條數據
Student.updateOne({ stuName: '李四' }, { stuEmail: '[email protected]' }).then(data => console.log(data))
# 修改返回結果
{ n: 1, nModified: 1, ok: 1 }
updateMany:方式和 updateOne 一樣,修改對象是對所有選中數據進行修改。
MySQL
安裝
# [] 表示可要可不要,無關緊要
npm i mysql [-S]
使用
const mysql = require('mysql')
// 1. 創建連接
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '000000',
database: 'college'
})
// 2. 連接數據庫
connection.connect()
// 3. 執行數據操作
connection.query('SELECT * FROM STUDENTS', (error, results, fields) => {
if (error) throw error
console.log('The solution is:')
console.log(results)
})
// 4. 關閉連接
connection.end()
結果如下:
The solution is:
[
RowDataPacket { STU_ID: '20173101', STU_NAME: '張三', STU_AGE: 18 },
RowDataPacket { STU_ID: '20173102', STU_NAME: '李四', STU_AGE: 20 },
RowDataPacket { STU_ID: '20173103', STU_NAME: '王五', STU_AGE: 19 }
]
總結
Node 操作 MySQL 數據庫並沒有像操作 MongoDB 那樣對於不同操作分別對應不同的方法,在 Node 操作 MySQL 時,使用 query
方法即可執行 MySQL 數據庫的增、刪、查、改操作。
代碼風格
在多人協同開發任務中,往往要求代碼符合一定的規範。有些人的代碼儘管都能運行,但是不是那麼美觀,目前社區有兩種代碼規範普遍受到大家的認同:
- JavaScript Standard Style(名稱如此,並不是官方要求);
- Airbnb JavaScript Style;
在上述兩種編碼風格中,都推薦不添加分號,但有些情況下需要注意:
當一行代碼是以 ” ( ” 、” [ “、” ‘ ‘” 開頭時,則在前面補上一個分號,用以避免一些語法解析錯誤;
當然,有些比較花哨的,在前面補上 ” & “、” ~ “、” ! “ 也可以,但是並不推薦這樣使用。
總結:建議不管代碼風格是否需要添加分號( “;” ),在上述描述的位置前面都補上一個分號!
知識擴展
SE0(Search Engine Optimization)——搜索引擎優化
客戶端渲染不利於搜索引擎優化(SEO),這是因為服務端渲染是可以被爬蟲抓取到的,客戶端異步渲染是很難被爬蟲抓取到到的,所以真正的網站既不是純客戶端也不是純服務端渲染出來的,而是兩者結合來做的。例如京東的商品列表就是採用的是服務端渲染,目的是為了搜索引擎優化,而它的商品評論為了用戶體驗,而且也不需要SEO,所以採用前端渲染。
模板引擎(art-template)
說到模板引擎,就要先說一個我們比較熟悉的 ES6 特性——模板字符串,相對於 ES5 以前繁雜的字符串拼接,模板字符串無疑帶來了福音,而模板字符串最初是來自後端渲染中所用到的模板引擎。
目前使用比較廣泛的模板引擎 art-template 就是此節的重點,直接看例子:
<ul>
<li>測試員:今天天氣不錯 <span style="float: right;">2020-12-24 16:20:37</span></li>
{{ each comments }}
<li>{{ $value.name }}: {{ $value.message }} <span style="float: right;">{{ $value.dateTime }}</span></li>
{{ /each }}
</ul>
這是後端渲染中的一個部分,具體案例請見 /example/feedback
// 通過模板引擎渲染
var htmlStr = template.render(data.toString(), {
comments: comments
})
// htmlStr為渲染好的字符串
res.end(htmlStr)
其實不難理解,這是模板引擎循環添加替換字符串的例子,和 Vue 語法比較類似。渲染顯示結果如下:
REPL
REPL 即 read、eval、print、loop,是在命令行窗口中輸入 node 進入的 JS 執行狀態,讀取用戶輸入的 JS 代碼,執行,輸出,重複這個過程,和瀏覽器中的命令行類似,但又有一定的區別。
狀態碼
- 301:永久重定向,會將重定向地址緩存,下次訪問直接跳轉;
- 302:臨時重定向,不會緩存,每次請求都會詢問是否重定向跳轉;
//tool.oschina.net/commons?type=5
模塊化歷程
在 ES5 以前,是不支持模塊化的。在 Node 中進行了處理,像我們使用 require、module.exports 等是 Node 創建者自己添加的模塊化支持(CommonJS
),只能在 Node 環境中使用,在瀏覽器中並不能直接使用。
於是有人通過編譯,使 JS 在瀏覽器中也可以像在 Node 中的模塊一樣,使用模塊化的概念進行編程,就有了AMD(require.js
)、CMD(sea.js
) 等等對原生 JS 模塊化的支持。
最後,ES6 出來以後,JS 才在官方支持模塊化。
網頁模板
- WordPress://wordpress.org/download/