Node.js基礎筆記
1. Node.js介紹
1. 爲什麼要學Node.js
1. 企業需求
- 具有服務端開發經驗更好
- front-end (前端)
- back-end (後端)
- 全棧開發工程師(全乾)
- 基本的網站開發能力
- 服務端
- 前端
- 運維部署
2.
2. Node.js是什麼
1. Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
- Node.js不是一門語言
- Node.js不是庫,不是框架
- Node.js是一個JavaScript運行時環境
- 簡單點來講就是Node.js可以解析和執行JavaScript代碼
- 以前只有瀏覽器可以解析執行JavaScript代碼
- 也就是說現在的JavaScript可以完全脫離瀏覽器來運行,一切都歸功於Node.js
2. 瀏覽器中的JavaScript
- ECMAScript
- 基本語法
- if (循環)
- function (函數)
- Object (對象)
- Array (數組)
- BOM
- DOM
3. Node.js中的JavaScript
- 沒有BOM、DOM
- ECMAScript
- 在Node這個JavaScript執行環境中爲JavaScript提供了一些服務器級別的操作API
- 例如文件讀寫
- 網絡服務的構建
- 網絡通信
- HTTP服務器
- 。。。
- 構建於Chrome的V8引擎之上
- 代碼只是具有特定格式的字符串而已
- 引擎可以認識它,引擎可以幫你去解析和執行
- Google Chrome的V8引擎是目前公認的解析執行JavaScript代碼最快的
- Node.js的作者把 Google Chrome 中的 V8 引擎移植了出來,開發了一個獨立的 JavaScript 運行時環境
- Node.js uses an event-driven,non-blocking I/O model that makes it lightweight and efficient
- event-driven 事件驅動
- non-blocking I/O model 非阻塞IO模型(異步)
- lightweight and efficient 輕量和高效
- Node.js’ package ecosystem,npm,is the largest ecosystem of open sourse libraries in the world
- npm是世界上最大的開源庫生態系統
- 絕大多數JavaScript相關的包都存放在了npm中,這樣做的目的是爲了讓開發人員更方便的去下載使用
- 例如:
npm install jquery
3. Node.js能做什麼
- Web服務器後臺
- 命令行工具
- npm(node)
- git (c)
- hexo(node)
- 。。。
- 對於前端開發工程師來講,接觸 node 最多的是它的命令行工具
- 自己寫的很少,主要是使用別人第三方的
- webpack
- gulp
- npm
4. 預備知識
- HTML / CSS
- JavaScript
- 基本的命令行操作
- cd — 打開目錄、切換目錄、返回上級目錄、返回根目錄
- dir — 查看目錄內容
- ls — 顯示文件或目錄
- mkdir — 創建文件夾
- rm — 刪除文件
- 具有服務端開發經驗更佳
5. 相關資源
- 《深入淺出Node.js》
- 樸靈
- 偏理論,幾乎沒有任何實戰型內容
- 理解原理底層有幫助
- 結合課程的學習去看
- 《Node.js權威指南》
- API講解
- 沒有實戰,沒有業務
- JavaScript 標準參考教程(alphad): http://javascript.ruanyifeng.com/
- Node入門:http://www.nodebeginner.org/index-zh-cn.html
- 官方API文檔:https://nodejs.org/dist/latest-v6.x/docs/api/
- 中文文檔(版本比較舊):http://www.nodeclass.com/api/node.html
- CNODE社區:http://cnodejs.org
- CNODE-新手入門:http://cnodejs.org/getstart
6. Node.js 能學到什麼
1. B/S編程模型
- Browser-Server
- back-end
- 任何服務端技術這種BS編程模型都是一樣,和語音無關
- Node 只是作爲我們學習BS編程模型的一個工具而已
2. 模塊化編程
- RequireJS
- SeaJS
@import('文件路徑')
- 以前認知的 JavaScript 只能通過 script 標籤來加載
- 在 Node.js中可以像
@import()
一樣來引用加載 JavaScript腳本文件
3. Node常用API
- 。。。
4. 異步編程
- 回調函數
- Promise
- async
- generator
5. Express Web 開發框架
6. ECMAScript 6
7. 前端高級框架
- Vue.js
- React
- Angular
2. Node.js 起步
1. 安裝Node環境
- 查看當前 Node 環境的版本號
- 下載:https://nodejs.org/en/download/
- 安裝
- 確認 Node 環境是否安裝成功
- 打開命令行,輸入
node --version
- 打開命令行,輸入
- 環境變量
2. HelloWorld
- 創建編寫的 JavaScript 腳本文件
- 打開終端,定位到腳本文件所屬目錄
- 輸入
node 文件名
執行對應的文件
注意:文件名不要用node.js
來命名,也就是說除了node
,其他隨便起,最好別用中文
3. 解析執行 JavaScript
// 1. 使用 require 方法加載 fs 核心模塊
var fs = require('fs')
4. 文件讀寫及簡單錯誤處理
- 文件讀取:
// 瀏覽器中的 Javascript 是沒有文件操作的能力的
// 但是 Node 中的 JavaScript 具有文件操作的能力
// fs 是 file-system 的簡寫,就是文件系統的意思
// 在 Node 中如果想要進行文件操作,就必須引入 fs 這個核心模塊
// 在 fs 這個核心模塊中,就提供了所有的文件操作相關的 API
// 例如:fs.readFile 就是用來讀取文件的
// 1. 使用 require 方法加載 fs 核心模塊
var fs = require('fs')
// 2. 讀取文件
// 第一個參數就是要讀取的文件路徑
// 第二個參數是一個回調函數
// 成功
// data 數據
// error null
// 失敗
// data undefined 沒有數據
// error 錯誤對象
fs.readFile('./data/hello.txt',function(error,data){
// 文件中存儲的其實都是二進制數據 0 和 1
// 爲什麼看到的不是二進制,是由於二進制轉換爲了十六進制了
// 可以通過 toString 方法把其轉化爲我們能認識的字符
// console.log(data.toString());
// 錯誤判斷
if(error){
console.log('讀取文件失敗')
}else{
console.log(data.toString());
}
})
- 文件寫入:
// 第一個參數:文件路徑
// 第二個參數:文件內容
// 第三個參數:回調函數
// error
// 成功:
// 文件寫入成功
// error 是 null
// 失敗:
// 文件寫入失敗
// error 就是錯誤對象
fs.writeFile('./data/你好.md','大家好,給大家介紹一下,我是Node.js',function(error){
// 判斷
if(error){
console.log('寫入失敗');
}else{
console.log('寫入成功');
}
})
5. 簡單的 http 服務
- 發送請求
// 在 Node 中專門提供了一個核心模塊: http
// http 這個模塊的職責就是幫你創建編寫服務器的
// 1. 加載 http 核心模塊
var http = require('http')
// 2. 使用 http.createServer() 方法創建一個 Web 服務器
// 返回一個 Server 實例
var server = http.createServer()
// 3. 服務器要幹嘛?
// 提供服務: 對數據的服務
// 發請求
// 接受請求
// 處理請求
// 反饋(發送響應)
// 註冊 request 請求事件
// 當客戶端請求過來,就會自動觸發服務器的 request 請求事件,然後執行第二個參數;回調處理函數
server.on('request',function(){
console.log('收到客戶端的請求了');
})
// 4. 綁定端口號,啓動服務器
server.listen(3000,function(){
console.log('服務器啓動成功了,可以通過 http://127.0.0.1:3000/ 來進行訪問');
})
- 發送響應
var http = require('http');
var server = http.createServer();
// request 請求事件處理函數,需要接收兩個參數;
// Request 請求對象
// 請求對象可以用來獲取客戶端的一些請求信息,例如請求路徑
// Response 響應對象
// 響應對象可以用來給客戶端發送響應信息
server.on('request',function(request,response){
console.log('收到客戶端的請求了,請求路徑是:'+ request.url);
// response 對象有一個方法:write 可以用來給客戶端發送響應數據
// write 可以使用多次,但是最後一定要使用 end 來結束響應,否則客戶端會一直等待
response.write('hello node.js');
// 告訴客戶端,話說完了,可以呈遞用戶
response.end();
})
server.listen(3000,function(){
console.log('服務器啓動成功,可以通過 localhost:3000 進行訪問');
})
6. 根據不同請求路徑返回不同數據
// 根據不同的請求路徑返回不同數據
var http = require('http');
// 1. 創建 Server
var server = http.createServer();
// 2. 監聽 request 請求事件,設置請求處理函數
server.on('request',function(req,res){
// console.log('收到請求了,請求路徑是:'+req.url);
// res.write('hello');
// res.write(' world');
// res.end();
// 上面的方式比較麻煩,推薦使用更簡單的方式,直接 end 的同時發送響應數據
// res.end('hello world');
// 根據不同的請求路徑發送不同的響應結果
// 1. 獲取請求路徑
// req.url 獲取到的是端口號之後的那一部分路徑
// 也就是說所有的 url 都是以 / 開頭的
// 2. 判斷路徑處理響應
var url = req.url;
// if(url === '/'){
// res.end('index page')
// }else if (url === '/login') {
// res.end('login page')
// }else {
// res.end('404 Not Found')
// }
if (url === '/products') {
var products = [
{
name: '蘋果',
price: 8888
},
{
name: '香蕉',
price: 5888
},
{
name: '菠蘿',
price: 3888
}
]
// 響應內容只能是二進制數據或者字符串
// 數字
// 對象
// 數組
// 布爾值
// 都不行
res.end(JSON.stringify(products))
}
})
// 3. 綁定端口號,啓動服務
server.listen(80,function(){
console.log('服務器啓動成功, 可以訪問!');
})
7. each 與 forEach
jQuery 的 each 和原生的 JavaScript 方法 forEach
- EcmaScript 5 提供的
- 不兼容 IE8
- jQuery 的 each 由 jQuery 這個第三方庫提供
- jQuery 2 以下的版本是兼容 IE8 的
- 他的 each 方法主要用來遍歷 jQuery 實例對象(僞數組)
- 同時它也可以作爲低版本瀏覽器中 forEach 替代品
- jQuery 的實例對象不能使用 forEach 方法,如果想要使用必須轉爲數組纔可以使用
[].slice.call(jQuery實例對象)
8. 文件操作路徑和模塊路徑
- 文件操作路徑:
在文件操作的相對路徑中
./data/a.txt 相對於當前目錄
data/a.txt 相對於當前目錄
/data/a.txt 絕對路徑,當前文件模塊所處磁盤根目錄
c:/xx/xx.... 絕對路徑
fs.readFile('./data/a.txt',function(err,data){
if(err){
console.log(err);
return console.log('讀取失敗');
}
console.log(data.toString());
})
- 模塊操作路徑:
// 這裏如果忽略了,則也是磁盤根目錄
require('/data/foo.js')
// 相對路徑
require('./data/foo.js')
// 模塊加載的路徑中的相對路徑不能省略 ./
9. 使用 nodemon 自動重啓
- 我們這裏可以使用一個第三方命名航工具:
nodemon
來幫我們解決頻繁修改代碼重啓服務器問題 - nodemon 是一個基於 Node.js 開發的一個第三方命令行工具,我們使用的時候需要獨立安裝:
// 在任意目錄執行該命令都可以
// 也就是說,所有需要 --global 來安裝的包都可以在任意目錄執行
npm install --global nodemon
安裝完畢之後,使用:
node app.js
// 使用 nodemon
nodemon app.js
只要是通過 nodemon app.js
啓動的服務,則它會監視你的文件變化,當文件發生變化的時候,自動幫你重啓服務器
3. Node 中的 JavaScript
- ECMAScript
- 沒有 BOM, DOM
- 核心模塊
- 第三方模塊
- 用戶自定義模塊
1. 核心模塊
- Node 爲 JavaScript 提供了很多服務器級別的 API ,這些 API 絕大多數都被包裝到了一個具名的核心模塊中了。
- 例如文件操作的
fs
核心模塊,http 服務構建的http
模塊,path
路徑操作模塊,os
操作系統信息模塊。。。 - 以後只要說這個模塊是一個核心模塊,就要馬上想到如果想要使用它,就必須:
// 用來操作文件系統
var fs = require('fs')
// 用來加載 http
var http = require('http')
// 用來獲取操作系統
var os = require('os')
// 用來操作路徑
var path = require('path')
...
2. 簡單的模塊化
- JavaScript 天生不支持模塊化,
require
,exports
,是 Node.js 纔有的 require
是一個方法- 他的作用就是用來加載執行文件模塊中的代碼
- 在 Node 中,模塊有三種:
- 具名的核心模塊,例如
fs
,http
- 用戶自己編寫的文件模塊
- 相對路徑必須加 ./
- 可以省略後綴名
- 相對路徑中的 ./ 不能省略,否則報錯
- 在 Node 中,沒有全局作用域,只有模塊作用域
- 外部訪問不到內部
- 內部也訪問不到外部
- 具名的核心模塊,例如
a.js
var foo = 'aaa'
console.log('a start');
function add(x,y){
return x + y;
}
// Error:Cannot find module 'b'
// require('b')
require('./b.js');
// 推薦:可以省略後綴名
require('./b')
console.log('a end');
console.log('foo 的值是' + foo);
b.js
// 不能訪問外部文件
// 外部文件也不能訪問此文件
console.log('b start');
console.log(add(10,20)); // 不能獲取到 a.js 中的 add 方法
var foo = 'bbb'; // 與 a.js 中的 foo 不衝突
require('./c.js');
console.log('b end');
c.js
console.log('ccc');
顯示結果:
3. 加載與導出
-
模塊作用域默認是封閉的
-
如何讓模塊與模塊之間進行通信
-
有時候,我們加載文件模塊的目的不是爲了簡簡單單的執行裏面的代碼,更重要的是爲了使用裏面的某個成員
-
require 方法有兩個作用:
- 加載文件模塊並執行裏面的代碼
- 拿到被加載文件模塊導出的接口對象
-
在每個文件模塊中都提供了一個對象:exports
- exports 默認是一個空對象
- 你要做的就是把所有需要被外部訪問的成員掛載到這個 exports 對象中
a.js
var bExports = require('./b');
var fs = require('fs');
console.log(bExports.foo);
console.log(bExports.add(10,20));
console.log(bExports.age);//未掛載,訪問不到
bExports.readFile('./a.js');
fs.readFile('./a.js',function(err,data){
if (err) {
console.log('讀取文件失敗');
}else{
console.log(data.toString());
}
})
b.js
var foo = 'bbb'
// console.log(exports);
exports.foo = 'hello'
exports.add = function(x,y){
return x+y;
}
exports.readFile = function(path,callback){
console.log('文件路徑:',path);
}
var age = 18;
function add(x,y){
return x-y;
}
4. 第三方模塊
- 第三方模塊的標識就是第三方模塊的名稱(不可能有第三方模塊和核心模塊的名字一致)
npm
- 開放人員可以把寫好的框架,庫發佈到
npm
上 - 使用者在使用的時候就可以很方便的通過
npm
來下載
- 開放人員可以把寫好的框架,庫發佈到
- 使用方式:
var 名字 = require('npm install 包名)
node_modules
node_modules/express
node_modules/express/package.json
node_modules/express/package.json main
- 如果
package.json
或者package.json main
不成立,則查找備選項:index.js
- 如果以上條件都不成立,則繼續進入上一級目錄中的
node_modules
按照上面的規則繼續查找 - 如果直到當前文件模塊所屬磁盤根目錄都找不到,最後報錯:
can not find module xxx
5. http
- require
- 端口號
- ip 地址定位計算機
- 端口號定位具體的應用程序
- Content-Type
- 服務器最好把每次響應的數據是什麼內容類型都告訴客戶端,而且要正確的告訴
- 不同的資源對應的 Content-Type 是不一樣,具體參照:http://tool.oschina.net/commons
- 對於文本類型的數據,最好都加上編碼,目的是爲了防止中文解析亂碼問題
- 通過網絡發送文件
- 發送的並不是文件,本質上來講發送時文件的內容
- 當瀏覽器收到服務器響應內容之後,就會根據你的 Content-Type 進行對應的解析處理
6. 異步編程
- 如果需要得到一個函數內部異步操作的結果,這時候必須通過
回調函數
來獲取 - 在調用的位置傳遞一個函數進來
- 在封裝的函數內部調用傳遞進來的函數
- 不成立的情況:
function add(x,y){
console.log(1)
setTimeout(function(){
console.log(2)
var ret = x + y
return ret
}, 1000)
console.log(3)
// 到這裏執行就結束了,不會等到前面的定時器,所以直接就返回了默認值 undefined
}
console.log(add(10,20)) // => undefined
- 不成立的情況:
function add(x,y){
var ret
console.log(1)
setTimeout(function(){
console.log(2)
ret = x + y
}, 1000)
console.log(3)
return ret
}
console.log(add(10,20)) // => undefined
- 回調函數:
function add(x,y,callback){
// callback 就是回調函數
// var x = 10
// var y = 20
// var callback = function(ret){ console.log(ret) }
console.log(1)
setTimeout(function(){
var ret = x + y
callback(ret)
},1000)
}
add(10,20,function(ret){
console.log(ret)
})
- 基於原生 XMLHTTPRequest 封裝 get 方法:
function get(url,callback){
var oReq = new XMLHttpRequest()
// 當請求加載成功之後要調用指定的函數
oReq.onload = function(){
// 我現在需要得到這裏的 oReq.responseText
callback(oReq.responseText)
}
oReq.open("get",url,true)
oReq.send()
}
get('data.json', function(data){
console.log(data)
})
4. Web 服務器開發
1. ip 地址和端口號
- ip地址用來定位計算機,端口號用來定位具體的應用程序
- 一切需要聯網通信的軟件都會佔用一個端口號,端口號的範圍從 0-65536 之間
- 在計算機中有一些默認端口號,最好不要去使用,例如 http 服務的 80
- 可以同時開啓多個服務,但一定要確保不同服務佔用的端口號不一致纔可以
- 一臺計算機中,同一個端口號同一時間只能被一個程序佔用
- 所有聯網的程序都需要進行網絡通信
- 計算機中只有一個物理網卡,而且同一個局域網中,網卡的地址必須唯一
- 網卡是通過唯一的 ip 地址來進行定位的
var http = require('http')
var server = http.createServer()
// 2. 監聽 request 請求事件,設置請求處理函數
server.on('request',function(req,res){
console.log('收到請求了,請求路徑是:' + req.url);
console.log('請求我的客戶端的地址是:',req.socket.remoteAddress,req.socket.remotePort);
res.end('hello nodejs')
})
server.listen(3000,function(){
console.log('服務器啓動成功,可以訪問!');
})
2. Content-Type 響應內容類型
- http://tool.oschina.net/
- 在服務器默認發送的數據,其實是 utf8 編碼的內容,但是瀏覽器不知道你是 utf8 編碼的內容
- 瀏覽器在不知道服務器響應內容的編碼的情況下會按照當前操作系統的默認編碼去解析
- 中文操作系統默認是 gbk
- 解決方法就是正確的告訴瀏覽器我給你發送的內容是什麼編碼的
- 在 http 協議中,Content-Type 就是用來告知對方我發送的數據內容是什麼類型
var http = require('http')
var server = http.createServer()
server.on('request',function(req,res){
// res.setHeader('Content-Type','text/plain;charset=utf-8')
// res.end('hello 世界')
var url = req.url;
if(url === '/plain'){
// text/plain 就是普通文本
res.setHeader('Content-Type','text/plain; charset=utf-8')
res.end('hello 世界')
}else if(url === '/html'){
// 如果你發送的是 html 格式的字符串,則也要告訴瀏覽器我給你發送的是 text/html 格式的內容
res.setHeader('Content-Type','text/html; charset=utf-8')
res.end('<p>hello html <a href = ''>點我</a></p>')
}
})
server.listen(3000,function(){
console.log('Server is running');
})
3. 發送文件數據
- 結合 fs 發送文件中的數據
- Content-Type
- http://tool.oschina.net/commons
- 不同的資源對應的 Content-Type 是不一樣的
- 圖片不需要指定編碼
- 一般只爲字符數據才指定編碼
var fs = require('fs')
var http = require('http')
var server = http.createServer()
server.on('request',function(req,res){
var url = req.url
if (url === '/') {
// 肯定不這麼幹
// res.end('<!DOCTYPE html><html lang="]" dir="ltr"><head><meta charset="utf-8"><title></title></head><body><h1>首頁</h1></body></html>')
// 我們要發送的還是在文件中的內容
fs.readFile('./resource/index.html',function(err,data){
if (err) {
res.setHeader('Content-Type', 'text/plain;charset=utf-8')
res.end('請求失敗')
} else {
// data 默認是二進制數據,可以通過 .toString 轉化爲咱們能識別的字符串
// res.end() 支持兩種數據類型,一種是二進制,一種是字符串
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end(data)
}
})
} else if(url === '/img'){
// url: 統一資源定位符
// 一個 url 最終其實是要對應到一個資源的
fs.readFile('./resource/1.jpg',function(err,data){
if (err) {
res.setHeader('Content-Type', 'text/plain;charset=utf-8')
res.end('請求失敗')
}else{
// data 默認是二進制數據,可以通過 .toString 轉化爲咱們能識別的字符串
// res.end() 支持兩種數據類型,一種是二進制,一種是字符串
// 圖片就不需要指定編碼了,因爲我們常說的編碼一般指的是:字符編碼
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
}
})
}
})
server.listen(8000, function() {
console.log('Server is running');
})
4. 實現 Apache 目錄列表渲染
- 如何得到 wwwDir 目錄列表中的文件名和目錄名
- fs.readdir
- 如何將得到的文件名和目錄名替換到 template.html 中
- 在 template.html 中需要替換的位置預留一個特殊的標記(就像以前使用模版引擎的標記一樣)
- 根據 files 生成需要的 HTML 內容
var http = require('http')
var fs = require('fs')
var server = http.createServer()
var wwwDir = 'D:/Movie/www'
server.on('request',function(req,res){
var url = req.url
fs.readFile('./template.html',function(err, data){
if(err){
return res.end('404 not found')
}
fs.readdir(wwwDir,function(err,files){
if(err){
return res.end('can not find www dir')
}
// 2.1 生成需要替換的內容
var content = ''
files.forEach(function(item){
// 在 ES6 中的 ` 字符串中,可以使用 ${} 來引用變量
content += `
<tr>
<td data-value="apple/"><a class="icon dir" href="/D:/Movie/www/apple/">${item}/</td>
<td class="detailsColumn" data-value="0"></td>
<td class="detailsColumn" data-value="1509589967">2017/11/2 上午10:32:47</td>
</tr>
`
})
// 2.3 替換
data = data.toString()
// 普通的字符串解析替換,瀏覽器看到的結果就不一樣了
data = data.replace('>_<',content)
console.log(data);
// 3. 發送解析替換過後的響應數據
res.end(data)
})
})
})
// 3. 綁定端口號,啓動服務
server.listen(3000,function(){
console.log('running');
})
5. 在 Node 中使用模板引擎
-
在 node 中使用模版引擎
-
art-template 不僅可以在瀏覽器使用,也可以在 node 中使用
-
安裝:
npm install art-template
-
該命令在哪裏執行就會把包下載到哪裏,默認會下載到 node_modules 目錄中
-
node_modules 不要改,也不支持改
-
在需要使用的文件模塊中加載 art-template
- 只需要使用 require 方法加載就可以了: require(‘art-template’)
- 參數中的 art-template 就是你下載的包的名字
- 也就是說你 install 的名字是什麼,則你 require 中的就是什麼
-
查文檔,使用模版引擎的 API
var template = require('art-template')
var fs = require('fs')
fs.readFile('./tpl.html',function(err,data){
if(err){
return console.log('讀取文件失敗');
}
// 默認讀取到的 data 是二進制數據
// 而模版引擎的 render 方法需要接收的是字符串
// 所以我們在這裏需要把 data 二進制數據轉爲字符串纔可以給模版引擎使用
var ret = template.render(data.toString(),{
name: 'Jack',
age: 18,
province: '北京',
hobbies:[
'寫代碼',
'唱歌',
'打籃球'
]
})
console.log(ret);
})
6. 統一處理靜態資源
- app application 應用程序
- 把當前模塊所有的依賴項都聲明在文件模塊最上面
- 爲了讓目錄結構保持統一清晰,所以我們約定,把所有的 HTML 文件都放到 views(視圖)
- 我們爲了方便的統一處理這些靜態資源,所以我們約定把所有的靜態資源都存放在 public 的目錄中
- 哪些資源能被用戶訪問,哪些資源不能被用戶訪問,我現在可以通過代碼來進行非常靈活的
- 統一處理:
- 如果請求路徑是以 /public/ 開頭的,則我認爲你要獲取 public 中的某個資源
- 所以我們就直接可以把請求路徑當作文件路徑來直接進行讀取
var http = require('http')
var fs = require('fs')
http
.createServer(function(req,res){
var url = req.url
if(url === '/'){
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (url.indexOf('/public/') === 0) {
fs.readFile('.' + url,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
7. 客戶端渲染與服務端渲染
- 客戶端渲染
- 服務器端渲染
- 客戶端渲染與服務端渲染的區別
- 客戶端渲染不利於 SEO 搜索引擎優化
- 服務端渲染是可以被爬蟲抓取到的,客戶端異步渲染是很難被爬蟲抓取到的
- 所以你會發現真正的網站既不是純異步也不是純服務端渲染出來的
- 而是兩者結合起來的
- 例如京東的商品列表就採用的是服務端渲染,目的是爲了 SEO 搜索引擎優化
- 而它的商品評論列表爲了用戶體驗,而且也不需要 SEO 優化,所以採用的是客戶端渲染
5. 留言本案例
1. 頁面跳轉及404處理
var http = require('http')
var fs = require('fs')
http
.createServer(function(req,res){
var url = req.url
if(url === '/'){
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if(url === '/post.html'){
fs.readFile('./views/post.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (url.indexOf('/public/') === 0) {
fs.readFile('.' + url,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else{
// 其他的都處理成 404 找不到
fs.readFile('./views/404.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
2. 渲染評論首頁
- html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="/public/css/main.css">
</head>
<body>
<div class="header-container">
<div class="page-header">
<h1>Example page header <small>Subtext for header</small></h1>
<a href="/post.html" class="btn btn-success">發表留言</a>
</div>
</div>
<div class="comments container">
<ul class="list-group">
{{each comments}}
<li class="list-group-item">{{ $value.name }}說:{{ $value.message }}
<span class="pull-right">{{ $value.dateTime }}</span>
</li>
{{/each}}
</ul>
</div>
</body>
</html>
- js
var http = require('http')
var fs = require('fs')
var template = require('art-template')
// 模擬數據
var comments = [
{
name: '張三',
message: '今天天氣不錯',
dateTime: '2015-10-16'
},
{
name: '張三2',
message: '今天天氣不錯',
dateTime: '2015-10-16'
},
{
name: '張三3',
message: '今天天氣不錯',
dateTime: '2015-10-16'
},
{
name: '張三4',
message: '今天天氣不錯',
dateTime: '2015-10-16'
},
{
name: '張三5',
message: '今天天氣不錯',
dateTime: '2015-10-16'
},
{
name: '張三6',
message: '今天天氣不錯',
dateTime: '2015-10-16'
},
]
http
.createServer(function(req,res){
var url = req.url
if(url === '/'){
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
var htmlStr = template.render(data.toString(),{
comments:comments
})
res.end(htmlStr)
})
}else if(url === '/post.html'){
fs.readFile('./views/post.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (url.indexOf('/public/') === 0) {
fs.readFile('.' + url,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else{
// 其他的都處理成 404 找不到
fs.readFile('./views/404.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
3. 處理表單get請求
- 對於這種表單提交的請求路徑,由於其中具有用戶動態填寫的內容
- 所以你不可能通過去判斷完整的 url 路徑來處理這個請求
- 結論:對於我們來講,其實只需要判定,如果你的請求路徑是 /pinglun 的時候,那我就認爲你提交的表單已經過來了
- 注意:這個時候無論 /pinglun?xxx 之後是什麼,我都不用擔心了,
- 因爲我的 pathname 是不包含 ? 之後的那個路徑
- 一次請求對應一次響應,響應結束這次請求也就結束了
- 我們已經使用 url 模塊的 parse 方法把請求路徑中的查詢字符串給解析成一個對象了
- 所以接下來要做的就是:
- 獲取表單提交的數據 parseObj.query
- 將當前時間日期添加到數據對象中,然後存儲到數組中
- 讓用戶重定向跳轉到首頁 /
- 當用戶重新請求 / 的時候,我數組中的數據已經發生變化了,所以用戶看到的頁面也就變了
http
.createServer(function(req,res){ // 簡寫方式
// 使用 url.parse 方法將路徑解析爲一個方便操作的對象
// 第二個參數爲 true 表示直接將查詢字符串轉爲一個對象
// 通過 (query)屬性來訪問
var parseObj = url.parse(req.url,true)
// 單獨獲取不包含查詢字符串的路徑部分(該路徑不包含 ? 之後的內容)
var pathname = parseObj.pathname
if(pathname === '/'){ // 首頁
fs.readFile('./views/index.html',function(err,data){
if(err){
return res.end('404 not found')
}
var htmlStr = template.render(data.toString(),{
comments:comments
})
res.end(htmlStr)
})
}else if(pathname === '/post'){ // 發表評論
fs.readFile('./views/post.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if (pathname.indexOf('/public/') === 0) {
// 統一處理:
// 如果請求路徑是以 /public/ 開頭的,則我認爲你要獲取 public 中的某個資源
// 所以我們就直接可以把請求路徑當作文件路徑來直接進行讀取
fs.readFile('.' + pathname,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if(pathname === '/pinglun'){
var comment = parseObj.query
comment.dateTime = '2020'
comments.unshift(comment)
// 服務器這個時候已經把數據存儲好了,接下來就是讓用戶重新請求 / 首頁,就可以看到最新的留言內容了
// 通過響應頭來實現服務端重定向
res.writeHead(302,{
'Location': '/'
})
res.end()
}else{
// 其他的都處理成 404 找不到
fs.readFile('./views/404.html',function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}
})
.listen(5000,function(){
console.log('running');
})
4. 表單提交重定向
- 如何通過服務器讓客戶端重定向?
- 狀態碼設置爲 302 臨時重定向
- statucCode
- 在響應頭中通過 Location 告訴客戶端往哪兒重定向
- setHeader
- 狀態碼設置爲 302 臨時重定向
- 如果客戶端發現收到服務器的響應的狀態碼是 302 就會自動去響應頭中找 Location
- 然後對該地址發起新的請求
- 所以你就能看到客戶端自動跳轉了
else if (pathname.indexOf('/public/') === 0) {
// 統一處理:
// 如果請求路徑是以 /public/ 開頭的,則我認爲你要獲取 public 中的某個資源
// 所以我們就直接可以把請求路徑當作文件路徑來直接進行讀取
fs.readFile('.' + pathname,function(err,data){
if(err){
return res.end('404 not found')
}
res.end(data)
})
}else if(pathname === '/pinglun'){
var comment = parseObj.query
comment.dateTime = '2020'
comments.unshift(comment)
// 服務器這個時候已經把數據存儲好了,接下來就是讓用戶重新請求 / 首頁,就可以看到最新的留言內容了
// 通過響應頭來實現服務端重定向
res.writeHead(302,{
'Location': '/'
})
res.end()
}
6. Node 中的模塊系統
-
使用 Node 編寫應用程序主要就是在使用:
- EcmaScript 語言
- 和瀏覽器不一樣,在 Node 中沒有 BOM,DOM
- 核心模塊
- 文件操作的 fs
- http 服務的 http
- url 路徑操作模塊
- path 路徑處理模塊
- os 操作系統信息
- 第三方模塊
- art-template
- 必須通過 npm 來下載纔可以使用
- 自定義模塊
- EcmaScript 語言
-
在 Node 中沒有全局作用域的概念
-
在 Node 中,只能通過
require
方法來加載執行多個 JavaScript 腳本文件 -
require
加載只能是執行其中的代碼,文件與文件之間由於是模塊作用域,所以不會有污染的問題- 模塊完全是封閉的
- 外部無法訪問內部
- 內部也無法訪問外部
-
模塊作用域固然帶來了一些好處,可以加載執行多個文件,可以完全避免變量命名衝突污染的問題
-
但是某些情況下,模塊與模塊實需要進行通信的
-
在每一個模塊中,都提供了一個對象:
exports
-
該對象默認是一個空對象
-
你要做的就是把需要被外部訪問使用的成員手動的掛載到
exports
接口對象中 -
然後誰來
require
這個模塊,誰就可以得到模塊內部的exports
接口對象
1. 什麼是模塊化
- 文件作用域
- 通信規則
- 加載 require
- 導出
2. CommonJS 模塊規範
在 Node 中的 JavaScript 還有一個很重要的概念,模塊系統。
- 模塊作用域
- 使用 require 方法用來加載模塊
- 使用 exports 接口對象用來導出模塊中的成員
1. 加載 require
- 語法:
var 自定義變量名稱 = require('模塊')
- 兩個作用:
- 執行被加載模塊中的代碼
- 得到被加載模塊中的
exports
導出接口對象
2. 導出 exports
- Node 中是模塊作用域,默認文件中所有的成員只在當前文件模塊有效
- 對於希望可以被其他模塊訪問的成員,我們就需要把這些公開的成員都掛載到 exports 接口對象中就可以了
- 導出多個成員(必須在對象中)
exports.a = 123
exports.b = 'hello'
exports.c = function(){
console.log('ccc')
}
exports.d = {
foo: 'bar'
}
- 導出多個成員也可以這麼來寫:
module.exports = {
foo: 'bar'
add: function(){}
}
- 導出單個成員(拿到的就是:函數,字符串):
module.exports = 'hello'
以下情況會覆蓋:
module.exports = 'hello'
// 以這個爲準,後者會覆蓋前者
module.exports = function(x,y){
return x + y
}
也可以這樣來導出多個成員:
module.exports = {
add: function(){
return x + y
},
str: 'hello'
}
3. 原理解析
exports 和 module.exports 的一個引用:
console.log(exports === module.exports) // => true
exports.foo = 'bar'
// 等價於
module.exports.foo = 'bar'
4. exports 和 module.exports 的區別
- 每個模塊中都有一個 module 對象
- module 對象中有一個 exports 對象
- 我們可以把需要導出的成員都掛載到 module.exports 接口對象中
- 也就是:
module.exports.xxx = xxx
的方式 - 但是每次都
module.exports.xxx = xxx
很麻煩,點兒的太多了 - 所以 Node 爲了你方便,同時在每一個模塊中都提供了一個成員叫:
exports
exports === module.exports
結果爲true
- 所以對於:
module.exports.xxx = xxx
的方式完全可以:exports.xxx = xxx
- 當一個模塊需要導出單個成員的時候,這個時候必須使用:
module.exports = xxx
的方式 - 不要使用
exports = xxx
不管用 - 因爲每個模塊最終向外
return
的是module.exports
- 而
exports
只是module.exports
的一個引用 - 所以即便你爲
exports = xxx
重新賦值,也不會影響module.exports
- 但是有一種賦值方式比較特殊:
exports = module.exports
這個用來重新建立引用關係的
module.exports = {
a: 123
}
// 重新建立 exports 和 module.exports 之間的引用關係
exports = module.exports
exports.foo = 'bar'
5. require 方法加載規則
- 優先從緩存加載
- 判斷模塊標識
- 核心模塊
- 第三方模塊
- 自定義模塊
// 如果是非路徑形式的模塊標識
// 路徑形式的模塊
// ./ 當前目錄,不可省略
// ../ 上一級目錄,不可省略
// /xxx 幾乎不用
// d:/a/foo.js 絕對路徑絕對不用
// 首位的 / 在這裏表示的是當前文件模塊所屬磁盤根路徑
// .js 後綴名可以省略
// require('./foo.js')
// 核心模塊的本質也是文件
// 核心模塊文件已經被編譯到了二進制文件中了,我們只需要按照名字來加載就可以了
// require('fs')
// require('http')
// 第三方模塊
// 凡是第三方模塊都必須通過 npm 來下載
// 使用的時候就可以通過 require('包名') 的方式來進行加載纔可以使用
// 不可能又任何一個第三方包和核心模塊的名字是一樣的
// 既不是核心模塊,也不是路徑形式的模塊
// 先找到當前文件所處目錄中的 node_modules 目錄
// node_modules/art-template
// node_modules/art-template/package.json 文件
// node_modules/art-template/package.json 文件中的 main 屬性
// main 屬性中就記錄了 art-template 的入口模塊
// 然後加載使用這個第三方包
// 實際上最終加載的還是文件
// 如果 package.json 文件不存在或者 main 指定的入口模塊是也沒有
// 則 node 會自動找該目錄下的 index.js
// 也就是說 index.js 會作爲一個默認備選項
// 如果以上所有任何一個條件都不成立,則會進入上一級目錄中的 node_modules 目錄查找
// 如果上一級還沒有,則繼續往上上一級查找
// 如果知道當前磁盤根目錄還找不到,最後報錯:
// can not find module xxx
// var template = require('art-template')
// 注意:我們一個項目有且只有一個 node_modules ,放在項目根目錄中,
// 這樣的話項目中所有的子目錄中的代碼都可以加載到第三方包中
// 不會出現有多個 node_modules
// 模塊查找機制
// 優先從緩存加載
// 核心模塊
// 路徑形式的文件模塊
// 第三方模塊
// node_modules/art-template
// node_modules/art-template/package.json 文件
// node_modules/art-template/package.json 文件中的 main 屬性
// index.js 備選項
// 進入上一級目錄找 node_modules
// 按照這個規則依次往上找,直到磁盤根目錄還找不到,最後報錯:Can not find module xxx
// 一個項目有且只有一個 node_modules 而且是存放到項目的根目錄
3. npm
- node package manager
1. npm 網站
2. npm 命令行工具
- npm 的第二層含義就是一個命令行工具,只要安裝了 node 就已經安裝了 npm
- npm 也有版本這個概念
- 可以通過在命令行中輸入:
npm --version
- 升級 npm:
npm install --global npm
3. 常用命令
npm init
npm init -y
可以跳過嚮導,快速生成
npm install
- 一次性把 dependencies 選項中的依賴項全部安裝
npm i
npm install 包名
- 只下載
npm i 包名
npm install --save 包名
- 下載並且保存依賴項(
package.json
文件中的 dependencies 選項) npm i -S 包名
- 下載並且保存依賴項(
npm uninstall 包名
- 只刪除,如果有依賴項會依然保存
npm un 包名
npm uninstall --save 包名
- 刪除的同時也會把依賴信息也去除
npm un -S 包名
npm help
- 查看使用幫助
npm 命令 --help
- 查看指定命令的使用幫助
- 例如我忘記了 uninstall 命令的簡寫了,這個時候,可以輸入
npm uninstall --help
來查看使用幫助
4. 解決 npm 被牆問題
npm 存儲包文件的服務器在國外,有時候會被牆,速度很慢,所以我們需要解決這個問題。
- http://npm.taobao.org/
- 淘寶的開發團隊把 npm 在國內做了一個備份
- 安裝淘寶的
cnpm
:
// 在任意目錄執行都可以
// --global 表示安裝到全局,而非前目錄
// --global 不能省略,否則不管用
npm install --global cnpm
- 接下來你安裝包的時候把之前的
npm
替換成cnpm
// 這裏還是走國外的 npm 服務器,速度比較慢
npm install jquery
// 使用 cnpm 就會通過淘寶的服務器來下載 jquery
cnpm install jquery
- 如果不想安裝
cnpm
又想使用淘寶的服務器來下載:
npm install jquery --registr=https://registry.npm.taobao.org
- 但是每一次手動這樣加參數很麻煩,所以我們可以把這個選項加入配置文件中:
npm config set registry https://registry.npm.taobao.org
// 查看 npm 配置信息
npm config list
- 只要經過了上面命令的配置,則你以後所有的
npm install
都會默認通過淘寶的服務器來下載。
4. package.json
-
建議每一個項目都要有一個 package.json 文件(包描述文件,就像產品的說明書一樣),給人踏實的感覺。
-
這個文件可以通過
npm init
的方式來自動初始化出來。
-
對於我們目前來講,最有用的事那個
dependencies
選項,可以用來幫助我們保存第三方包的依賴信息。 -
如果你的
node_modules
刪除了也不用擔心,我們只需要:npm install
就會自動把package.json
中的dependencies
中所有的依賴項都下載回來- 建議每個項目的根目錄下都有一個
package.json
文件 - 建議執行
npm install 包名
的時候都加上--save
這個選項,目的是用來保存依賴項信息
- 建議每個項目的根目錄下都有一個
5. package-lock.json
- npm 5 以前是不會有
package-lock.json
這個文件的 - npm 5 以後才加入了這個文件
- 當安裝包的時候,npm 都會生成或者更新
package-lock.json
這個文件- npm 5 以後的版本安裝包不需要加
--save
參數,它會自動保存依賴信息 - 當安裝包的時候,會自動創建或者是更新
package-lock.json
這個文件 package-lock.json
這個文件會保存node_modules
中所有包的信息(版本,下載地址)- 這樣的話重新
npm install
的時候速度就可以提升
- 這樣的話重新
- 從文件來看,有一個
lock
稱之爲鎖- 這個
lock
是用來鎖定版本的 - 如果項目依賴了
1.1.1
版本 - 如果你重新
install
其實會下載最新版本,而不是1.1.1
- 我們的目的就是希望可以鎖住
1.1.1
這個版本 - 所以這個
package-lock.json
這個文件的另一個作用就是鎖定版本號,防止自動升級新版
- 這個
- npm 5 以後的版本安裝包不需要加
7. Express
- 原生的
http
在某些方面表現不足以應對我們的開發需求,所以我們就需要使用框架來加快我們的開發效率,框架的目的就是提高效率,讓我們的代碼更高度統一 - 在 Node 中,有很多 Web 開發框架,我們這裏以學習
express
爲主 - http://www.expressjs.com.cn
1. Express 起步
1. 安裝
npm install --save express
2. hello world
const express = require('express')
const app = express()
app.get('/',(req,res) => res.send('hello world'))
app.listen(3000,() => console.log('Example app listening on port 3000!'))
3. 基本路由:
路由器
-
請求方法
-
請求路徑
-
請求處理函數
-
get:
// 當你以 GET 的方法請求 / 的時候,執行對應的處理函數
app.get('/',function(req,res){
res.send('hello world')
})
- post:
// 當你以 POST 的方法請求 / 的時候,指定對應的處理函數
app.post('/',function(req,res){
res.send('Got a POST request')
}
4. static-server 靜態服務
// /public資源
app.use(express.static('public'))
// /static資源
app.use(express.static('files'))
// /public/xxx
app.use('/public',express.static('public'))
// /static/xxx
app.use('/static',express.static('public'))
app.use('/static',express.static(path.join(_dirname,'public')))
2. Express 安裝 art-template 模版引擎
1. 安裝
npm install --save art-template
npm install --save express-art-template
2. 相關配置
app.engine('art',require('express-art-template'))
3. 如何使用
app.get('/',function(req,res){
// express 默認會去項目中的 views 目錄找 index.html
res.render('index.html'.{
title: 'hello world'
})
})
- 如果希望修改默認的
views
視圖渲染存儲目錄,可以:
// 注意:第一個參數 views 千萬不要寫錯
app.set('views',目錄路徑)
3. 在 Express 中獲取表單 GET 請求參數
Express 內置了一個 API ,可以直接通過 req.query
來獲取
req.query
4. 在 Express 獲取表單 POST 請求體數據
在 Express 中沒有內置獲取表單 POST 請求體的 API ,這裏我們需要使用一個第三方包:body-parser
- 安裝:
npm install --save body-parser
- 配置:
var express = require('express')
// 0. 引包
var bodyParser = require('body-parser')
var app = express()
// 配置 body-parser
// 只要加入這個配置,則在 req 請求對象上會多出來一個屬性:body
// 也就是說你就可以直接通過 req.body 來獲取表單 POST 請求體數據了
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
- 使用:
app.use(function(req,res){
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
// 可以通過 req.body 來獲取表單 POST 請求體數據
res.end(JSON.stringify(req.body,null,2))
})
5. 路由模塊的提取
根據不同的請求方法+請求路徑設置具體的請求處理函數
- 入口模塊
app.js
var express = require('express')
var router = require('./router')
var app = express()
// 把路由容器掛載到 app 服務中
app.use(router)
app.listen(3000,function(){
console.log('running 3000...');
})
module.exports = app
- 路由模塊
router.js
var express = require('express')
var fs = require('fs')
// 使用 Express 中的 Router 方法
// 1. 創建一個路由容器
var router = express.Router()
// 2. 把路由都掛載到 router 路由容器中
router.get('/students',function(req,res){
// readFile 的第二個參數是可選的,
// 傳入 utf8 就是告訴它把讀取到的文件直接按照 utf8 編碼轉成我們能認識的字符
// 除了這樣來轉換之外,也可以通過 data.toString() 的方式
fs.readFile('./db.json','utf8',function(err,data){
if(err){
return res.status(500).send('Server error.')
}
// console.log(data); // string
// 從文件中讀取到的數據一定是字符串
// 所以這裏一定要手動轉成對象
var students = JSON.parse(data).students
students: students
})
})
})
// 3. 把 router 導出
module.exports = router
8. Mongo DB
1. 關係型數據庫和非關係型數據庫
表就是關係,或者說表與表之間存在關係
- 所有的關係型數據庫都需要通過
sql
語言來操作 - 所有的關係型數據庫在操作之前都需要設計表結構
- 而且數據表還支持約束
- 唯一的
- 主鍵
- 默認值
- 非空
- 非關係型數據庫非常的靈活
- 有的非關係型數據庫就是 key-value 對兒
- 但是 MongoDB 是長的最像關係型數據庫的非關係型數據庫
- 數據庫 -> 數據庫
- 數據表 -> 集合(數組)
- 表記錄 -> (文檔對象)
- MongoDB 不需要設計表結構
- 也就是說你可以任意的往裏面存數據,沒有結構性這一說
2. 啓動和關閉數據庫
- 啓動:
// mongodb 默認使用執行 mongod 命令所處盤符根目錄下的 /data/db 作爲自己的數據存儲目錄
// 所以在第一次執行該命令之前先自己手動新建一個 /data/db
mongod
- 如果想要修改默認的數據存儲目錄,可以:
mongod --dbpath=數據存儲目錄路徑
- 停止:
// 在開啓服務的控制檯,直接 Ctrl+C 即可停止
// 或者直接關閉開啓服務的控制檯也可以
3. 連接和退出數據庫
- 連接:
// 該命令默認連接本機的 MongoDB 服務
mongo
- 退出:
// 在連接狀態輸入 exit 退出連接
exit
4. 基本命令
命令 | 作用 |
---|---|
show dbs | 查看顯示所有數據庫 |
db | 查看當前操作的數據庫 |
use 數據庫名稱 | 切換到指定的數據(如果沒有會新建) |
插入數據 |
5. 在 Node 中如何操作 MongoDB 數據
1. 使用官方的 mongodb 包來操作
2. 使用第三方 mongoose 來操作 MongoDB 數據庫
- 第三方包:mongoose 基於 MongoDB 官方的 mongodb 包再一次做了封裝
- 官網:http://mongoosejs.com/
- 官方指南:http://mongoosejs.com/docs/guide.html
- 官方 API 文檔:http://mongoosejs.com/docs/api.html
6. MongoDB 數據庫的基本概念
- 可以有多個數據庫
- 一個數據庫中可以有多個集合(表)
- 一個集合中可以有多個文檔(表記錄)
- 文檔結構很靈活,沒有任何限制
- MongoDB非常靈活,不需要像 MySQL 一樣先創建數據庫,表,設計表結構
- 在這裏只需要:當你需要插入數據的時候,只需要指定往哪個數據庫的哪個集合操作就可以了
- 一切都由 MongoDB 來幫你自動完成建庫建表這件事兒
{
qq:{
users:[
{name: '張三', age: 15},
{name: '李四', age: 15},
{name: '王五', age: 15},
...
],
products:[
],
...
},
taobao:{
},
baidu:{
}
}
7. 設計 Scheme 發佈 Model
1. 連接數據庫
- 指定連接的數據庫不需要存在,當你插入第一條數據之後就會自動被創建出來
mongoose.connect('mongodb://localhost/itcast')
2. 設計集合結構
- 集合結構也就是表結構
- 字段名稱就是表結構中的屬性名稱
- 約束的目的是爲了保證數據的完整性,不要有髒數據
var userSchema = new Schema({
username: {
type: String,
required: true // 必須有
},
password: {
type: String,
required: true
},
email: {
type: String
}
})
3. 將文檔結構發佈爲模型
- mongoose.model 方法就是用來將一個架構發佈爲 model
- 第一個參數:傳入一個大寫名詞單數字符串用來表示你的數據庫名稱
- mongoose 會自動將大寫名詞的字符串生成小寫複數的集合名稱
- 例如這裏的 User 最終會變爲 users 集合名稱
- 第二個參數:架構 Schema
- 返回值:模型構造函數
var User = mongoose.model('User',userSchema)
4. 獲取模型構造函數
- 當我們有了模型構造函數之後,就可以使用這個構造函數對 users 集合中的數據爲所欲爲了(增刪改查)
8. Mongodb 增刪改查
1. 增加數據
var admin = new User({
username: 'admin',
password: '123456',
email: '[email protected]'
})
// 增加數據
admin.save(function(err,ret){
if(err){
console.log('保存失敗');
}else{
console.log('保存成功');
console.log(ret);// 插入的數據
}
})
2. 查詢數據
// 查詢所有數據
User.find(function(err,ret){
if (err) {
console.log('查詢失敗');
}else{
console.log(ret);
}
})
// 按條件查詢所有數據
User.find({
username: 'zs' // 查詢條件
},function(err,ret){
if(err){
console.log('查詢失敗');
}else{
console.log(ret);
}
})
// 按條件查詢單個目標
User.findOne({
username: 'zs',
password: '123456'
},function(err,ret){
if(err){
console.log('查詢失敗');
}else{
console.log(ret);
}
})
3. 刪除數據
// 刪除數據
User.remove({
username: 'zs'
},function(err,ret){
if(err){
console.log('刪除失敗');
}else{
console.log('刪除成功');
console.log(ret);
}
})
4. 更新數據
// 更新數據
User.findByIdAndUpdate('abc',{
password: '123'
},function(err,ret){
if(err){
console.log('更新失敗');
}else{
console.log('更新成功');
}
})
9. Node.js連接MySQL數據庫
var mysql = require('mysql');
// 1. 創建連接
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'xxx',
database : 'xxx'
});
// 2. 連接數據庫
connection.connect();
// 3. 執行數據操作
connection.query('SELECT 1 + 1 AS solution', function(error,results,fields){
if (error) throw error;
console.log('The solution is:', results[0].solution);
});
// 4. 關閉連接
connection.end();
10. Promise
- 無法確定順序的代碼:
var fs = require('fs')
fs.readFile('./data/a.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘讀取失敗’);
// 拋出異常
// 1. 阻止程序的執行
// 2. 把錯誤消息打印到控制檯
throw err
}
console.log(data);
})
fs.readFile('./data/b.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘讀取失敗’);
// 拋出異常
// 1. 阻止程序的執行
// 2. 把錯誤消息打印到控制檯
throw err
}
console.log(data);
})
fs.readFile('./data/c.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘讀取失敗’);
// 拋出異常
// 1. 阻止程序的執行
// 2. 把錯誤消息打印到控制檯
throw err
}
console.log(data);
})
- 回調函數
var fs = require('fs')
fs.readFile('./data/a.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘讀取失敗’);
// 拋出異常
// 1. 阻止程序的執行
// 2. 把錯誤消息打印到控制檯
throw err
}
console.log(data);
fs.readFile('./data/b.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘讀取失敗’);
// 拋出異常
// 1. 阻止程序的執行
// 2. 把錯誤消息打印到控制檯
throw err
}
console.log(data);
fs.readFile('./data/c.txt', 'utf8', function(err,data){
if (err) {
// return console.log(‘讀取失敗’);
// 拋出異常
// 1. 阻止程序的執行
// 2. 把錯誤消息打印到控制檯
throw err
}
console.log(data);
})
})
})
- 爲了解決以上編碼方式帶來的問題(回調地獄嵌套),所以在EcmaScript6 中新增了一個API:
Promise
1. Promise 容器概念
2. Promise 基本語法
var fs = require('fs')
// 在 EcmaScript 6 中新增了一個 API Promise
// Promise 是一個構造函數
// 創建 Promise 容器
// 1. 給別人一個承諾 I promise you
// Promise 容器一旦創建,就開始執行裏面的代碼
var p1 = new Promise(function(resolve,reject){
fs.readFile('./data/a.txt','utf8',function(err,data){
if(err){
// 失敗了,承諾容器中的任務失敗了
// console.log(err);
// 把容器的 Pending 狀態變爲 Rejected
// 調用 reject 就相當於調用了 then 方法的第二個參數函數
reject(err)
}else{
// 承諾容器中的任務成功了
// console.log(data);
// 把容器的 Pending 狀態變爲成功 Resolved
// 也就是說這裏調用的 resolve 方法實際上就是 then 方法傳遞的那個 function
resolve(data)
}
})
})
// p1 就是那個承諾
// 當 p1 成功了 然後(then)做指定的操作
// then 方法接收的 function 就是容器中的 resolve 函數
p1
.then(function(data){
console.log(data);
},function(err){
console.log('讀取文件失敗了',err);
})
3. Promise API
var fs = require('fs')
// 在 EcmaScript 6 中新增了一個 API Promise
// Promise 是一個構造函數
// 創建 Promise 容器
// 1. 給別人一個承諾 I promise you
// Promise 容器一旦創建,就開始執行裏面的代碼
var p1 = new Promise(function(resolve,reject){
fs.readFile('./data/a.txt','utf8',function(err,data){
if(err){
// 失敗了,承諾容器中的任務失敗了
// console.log(err);
// 把容器的 Pending 狀態變爲 Rejected
// 調用 reject 就相當於調用了 then 方法的第二個參數函數
reject(err)
}else{
// 承諾容器中的任務成功了
// console.log(data);
// 把容器的 Pending 狀態變爲成功 Resolved
// 也就是說這裏調用的 resolve 方法實際上就是 then 方法傳遞的那個 function
resolve(data)
}
})
})
var p2 = new Promise(function(resolve,reject){
fs.readFile('./data/b.txt','utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
var p3 = new Promise(function(resolve,reject){
fs.readFile('./data/c.txt','utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
// p1 就是那個承諾
// 當 p1 成功了 然後(then)做指定的操作
// then 方法接收的 function 就是容器中的 resolve 函數
p1
.then(function(data){
console.log(data);
// 當 p1 讀取成功的時候
// 當前函數中 return 的結果就可以在後面的 then 中 function 接收到
// 當你 return 123 後面就接收到 123
// return ‘hello’ 後面就接收到 ‘hello’
// 沒有 return 後面收到的就是 undefined
// 上面那些 return 的數據沒什麼用
// 真正有用的是:我們可以 return 一個 Promise 對象
// 當 return 一個 Promise 對象的時候,後續的 then 中的方法的第一個參數作爲 p2 的 resolve
return p2
},function(err){
console.log('讀取文件失敗了',err);
})
.then(function(data){
console.log(data);
return p3
})
.then(function(data){
console.log(data);
console.log('end');
})
- Promise API 代碼圖示
4. 封裝 Promise API
var fs = require('fs')
function pReadFile(filePath){
return = new Promise(function(resolve,reject){
fs.readFile('filePath,'utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
pReadFile('./data/a.txt')
.then(function(data){
console.log(data);
return pReadFile('./data/b.txt')
})
.then(function(data){
console.log(data);
return pReadFile('./data/c.txt')
})
.then(function(data){
console.log(data);
})
11. 中間件
- http://expressjs.com/en/guide/using-middleware.html
- 中間件的本質就是一個請求處理方法,我們把用戶從請求到響應的整個過程分發到多箇中間件中去處理,這樣做的目的是提高代碼的靈活性,動態可擴展的
- 同一個請求所經過的中間件都是同一個請求對象和響應對象
1. 應用程序級別中間件
- 萬能匹配(不關心任何請求路徑和請求方法):
app.use(function(req,res,next){
console.log('Time:',Date.now())
next()
})
- 只要是以
'/xxx'
開頭的
app.use('/a',function(req,res,next){
console.log('Time:',Date.now())
next()
})
2. 路由級別中間件
- get:
app.get('/',function(req,res){
res.send('Hello World!')
})
- post:
app.post('/',function(req,res){
res.send('Got a POST request')
})
- put:
app.put('/user',function(req,res){
res.send('Got a PUT request at /user')
})
- delete:
app.delete('/user'.function(req,res){
res.send('Got a DELETE request at /user')
})
3. 錯誤處理中間件
app.use(function(err,req,res,next){
console.error(err.stack)
res.status(500).send('Something broke!')
})