#? Node.js學(xué)習(xí)筆記
## 簡(jiǎn)介
- 編寫高性能網(wǎng)絡(luò)服務(wù)器的JavaScript工具包
- 單線程、異步、事件驅(qū)動(dòng)
- 特點(diǎn):快,耗內(nèi)存多
- PHP是單線程,耗內(nèi)存少速度相對(duì)慢些
## 在Linux上安裝node
1. 先安裝一個(gè)[nvm](https://github.com/creationix/nvm)用于切換node版本:
- 進(jìn)入nvm的GitHub點(diǎn)擊README的installation
- 找到下列代碼(為了獲取最新的版本)復(fù)制下? 來(lái)輸入到Ubuntu的命令行中
```bash
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
```
- 安裝完要重啟一下
- 激活nvm
```bash
echo ". ~/.nvm/nvm.sh" >> /etc/profile
source /etc/profile
```
- 輸入nvm測(cè)試,如果報(bào)錯(cuò):
```bash
Computing checksum with shasum -a 256 Checksums do not match:
```
- 就再輸入:
```bash
export NVM_DIR="$HOME/.nvm"
#不行就再輸入
. "/usr/local/opt/nvm/nvm.sh"
```
- 如果還報(bào)錯(cuò)去[官網(wǎng)](https://github.com/creationix/nvm/issues/576)再找解決方案
2. nvm安裝完成后輸入:
```bash
nvm install --lts
nvm use (你安裝的版本號(hào))
```
- 安裝最新穩(wěn)定版,并使用它
- 輸入node,如果進(jìn)入了node交互環(huán)境就安裝成功了
- 如果要查看已經(jīng)安裝的node版本,輸入:
```bash
nvm ls
```
3. 完善安裝
- 上述過(guò)程完成后,有時(shí)會(huì)出現(xiàn),當(dāng)開(kāi)啟一個(gè)新的 shell 窗口時(shí),找不到 node 命令的情況,這種情況一般來(lái)自兩個(gè)原因:
- 一、shell 不知道 nvm 的存在
- 二、nvm 已經(jīng)存在,但是沒(méi)有 default 的 Node.js 版本可用。
- 解決方式:
- 一、檢查 ~/.profile 或者 ~/.bash_profile 中有沒(méi)有這樣兩句
```bash
export NVM_DIR="/Users/YOURUSERNAME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"? # This loads nvm
```
- 沒(méi)有的話,加進(jìn)去。這兩句會(huì)在 bash 啟動(dòng)的時(shí)候被調(diào)用,然后注冊(cè) nvm 命令。
- 二、調(diào)用
```bash
nvm ls
```
- 看看有沒(méi)有 default 的指向。如果沒(méi)有的話,執(zhí)行
```bash
$ nvm alias default (你安裝的版本號(hào))
#再
$ nvm ls
#看一下
```
## 基本HTTP服務(wù)器
```javascript
//server.js
var http = require('http');? ? ? ? ? ? ? ? ? ? //導(dǎo)入Node.js自帶的HTTP模塊
http.createServer(function(request,response){? //調(diào)用HTTP模塊的createServer()函數(shù)創(chuàng)建一個(gè)服務(wù),該函數(shù)有兩個(gè)參數(shù):request和response它們是對(duì)象,用它們的方法來(lái)處理HTTP請(qǐng)求的細(xì)節(jié),并且響應(yīng)請(qǐng)求
? ? response.statusCode = 200;? ? ? ? ? ? ? ? ? //返回的狀態(tài)碼
? ? response.setHeader('Content-Type', 'text/plain');? //HTTP協(xié)議頭輸出類型
? ? response.end();? ? ? ? ? ? ? ? ? ? ? ? ? ? //結(jié)束HTTP請(qǐng)求,不寫就沒(méi)有HTTP協(xié)議尾
}).listen(8000);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //監(jiān)聽(tīng)8000端口
console.log("Server running at http://127.0.0.1:8000/")
```
- createServer()方法接受一個(gè)方法作為參數(shù)
- 如果只是上面的一個(gè)簡(jiǎn)單服務(wù),瀏覽器訪問(wèn)時(shí)會(huì)提交兩次HTTP請(qǐng)求(express框架中已經(jīng)清除)
- 如果不用express框架,需手工清除:
```javascript
if(request.url !== "/favicon.ico"){? ? //清除第2次訪問(wèn):因?yàn)榇蟛糠譃g覽器都會(huì)在你訪問(wèn) http://localhost:8888/ 時(shí)嘗試讀取 http://localhost:8888/favicon.ico(網(wǎng)頁(yè)標(biāo)題“title”旁的圖標(biāo)),請(qǐng)求地址就是“/favicon.ico”但對(duì)于我們服務(wù)器來(lái)說(shuō)這次請(qǐng)求是不需要進(jìn)行額外處理的無(wú)效請(qǐng)求。
? ? response.write();
? ? response.end();
}
```
## 調(diào)用其它函數(shù)
### 調(diào)用本文件內(nèi)的函數(shù)
```javascript
//server.js
var http = require('http');
http.createServer(function(request,response){
? ? response.statusCode = 200;
? ? response.setHeader('Content-Type', 'text/plain');
? ? test(response);? ? //直接調(diào)用
? ? response.end();
}).listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
function test(res){
? ? res.write("hello world!")
}
```
### 調(diào)用其它文件內(nèi)的函數(shù)
- 原文件
```javascript
//server.js
var http = require('http');
var test = require('./test.js')
http.createServer(function(request,response){
? ? response.statusCode = 200;
? ? response.setHeader('Content-Type', 'text/plain');
? ? //調(diào)用方式1:
? ? test.hello(response);
? ? test.world(response);
? ? //方式2:
? ? test['hello'](response);
? ? test['world'](response);
? ? //方法2更為常用,因?yàn)榭梢酝ㄟ^(guò):
? ? var funname = 'hello';
? ? test[funname](response)
? ? //這種方式,改變字符串(funname)從而調(diào)用想要的函數(shù)
? ? response.end();
}).listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
```
- 要調(diào)用的文件
```javascript
//test.js
//形式1:
function hello(res){
? ? res.write("hello");
};
function world(res){
? ? res.write("world");
};
module.exports = hello;? ? //只支持一個(gè)函數(shù)
//形式2:支持多個(gè)函數(shù)
module.exports = {
? ? hello: function(res){
? ? ? ? res.write("hello");
? ? },
? ? world: function(res){
? ? ? ? res.write("world");
? ? }
}
```
- 只有用module.exports導(dǎo)出,此文件內(nèi)的方法才能被其他文件調(diào)用
## 模塊的調(diào)用
- JavaScript中類的寫法:
```javascript
//user.js
function user (id,name,age){
? ? this.id=id;
? ? this.name=name;
? ? this.age=age;
? ? this.self=function(){
? ? ? ? console.log(this.name+"is"+this.age+"years old");
? ? }
}
module.exports = user;
```
- 調(diào)用
```javascript
//server.js
var http = require('http');
var user = require('./user')
http.createServer(function(request,response){
? ? response.statusCode = 200;
? ? response.setHeader('Content-Type', 'text/plain');
? ? user1 = new user(1,"Rain",20);
? ? user1.self();
? ? response.end();
}).listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
```
- ps: JavaScript中類的繼承:
```javascript
var user = require('./user');
function admin (id,name,age){
? ? user.apply(this,[id,name,age]);
? ? this.idis=function(res){
? ? ? ? res.write(this.name+"is the"+this.id+"th");
? ? }
}
module.exports = admin;
```
## 路由
- 通過(guò)解析url獲取路由的字符串,調(diào)用路由文件內(nèi)對(duì)應(yīng)的方法,通過(guò)該方法讀取對(duì)應(yīng)的HTML文件,再將HTML文件返回給客戶端
```javascript
//server.js
var http = require('http');
var url = require('url');? ? ? ? ? //node.js提供一個(gè)“url”對(duì)象來(lái)解析url
var router = require('./router');? //引入路由文件
http.createServer(function(request,response){
? ? response.statusCode = 200;
? ? response.setHeader('Content-Type', 'text/plain');
? ? var pathname = url.parse(request.url).pathname;? ? //通過(guò)url.pathname方法解析出url后面的路由
? ? pathname = pathname.replace(/\//,'')? ? ? ? ? ? ? ? //通過(guò)正則將路由字符串的斜杠頭給去掉
? ? router[pathname](request,response);? ? ? ? ? ? ? ? //通過(guò)pathname字符串調(diào)用路由中對(duì)應(yīng)的方法
? ? response.end();
}).listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
```
- 路由文件:router.js
```javascript
//router.js
module.exports={
? ? login: function(req,res){
? ? ? ? res.write("轉(zhuǎn)到login頁(yè)面")
? ? }
? ? register: function(req,res){
? ? ? ? res.write("轉(zhuǎn)到注冊(cè)頁(yè)面")
? ? }
}
```
## 讀取文件
### 同步讀取文件(不推薦)
- 讀取文件的配置文件:optfile.js
```javascript
//optfile.js
var fs = require('fs');? ? ? ? ? ? ? ? //node.js提供的操作文件模塊
module.exports={
? ? readfileSync: function (path) {? ? //同步讀取方法
? ? ? ? var data = fs.readFilesSync(path,'utf-8');? //通過(guò)fs的readFilesSync方法同步讀取文件,path為文件路徑,以u(píng)tf-8的編碼讀取
? ? ? ? return data;
? ? },
? ? readfile: function (path) {? ? ? ? //異步讀取方法
? ? ? ? fs.readFile(path,function(err,data){
? ? ? ? ? ? if (err){
? ? ? ? ? ? ? ? console.log(err);
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? return data
? ? ? ? ? ? };
? ? ? ? ? ? console.log("函數(shù)執(zhí)行完畢");
? ? ? ? });
? ? }
}
```
```javascript
//server.js
var http = require('http');
var optfile = require('./optfile.js');? //引入配置文件
http.createServer(function(request,response){
? ? response.statusCode = 200;
? ? response.setHeader('Content-Type', 'text/plain');
? ? optfile.readfileSunc('.src/login.html')? //路徑是相對(duì)于此文件的
? ? response.end("主程序執(zhí)行完畢");
}).listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
```
- node.js的高性能主要就是依靠異步操作,在異步時(shí),當(dāng)服務(wù)器執(zhí)行讀文件的操作時(shí)程序會(huì)繼續(xù)執(zhí)行下去,而不是在原地等文件讀取完畢,因此上述代碼會(huì)在控制臺(tái)依次輸出:
```bash
函數(shù)執(zhí)行完畢
主程序執(zhí)行完畢? ? ? ? #文件還未讀取完主程序就已經(jīng)結(jié)束了(response.end)
(最后返回讀取到的文件內(nèi)容“data”)
```
- 而正是由于這種異步操作,如果想要將讀取的文件內(nèi)容返回給客戶端,就要使用“閉包”的方式
```javascript
//server.js
var http = require('http');
var optfile = require('./optfile.js');? //引入配置文件
http.createServer(function(request,response){
? ? response.statusCode = 200;
? ? response.setHeader('Content-Type', 'text/plain');
? ? function recall(data){? ? ? ? ? ? ? //創(chuàng)建閉包函數(shù)
? ? ? ? response.write(data);? ? ? ? ? //它可以儲(chǔ)存response
? ? ? ? response.end();
? ? }
? ? optfile.readfileSunc('.src/login.html',recall)? //路徑是相對(duì)于此文件的
}).listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
```
```javascript
//optfile.js
var fs = require('fs');
module.exports={
? ? readfileSync: function (path) {? ? //同步讀取方法
? ? ? ? var data = fs.readFilesSync(path,'utf-8');
? ? ? ? return data;
? ? },
? ? readfile: function (path,recall) {? ? ? ? //異步讀取方法,接入閉包函數(shù)
? ? ? ? fs.readFile(path,function(err,data){
? ? ? ? ? ? if (err){
? ? ? ? ? ? ? ? console.log(err);
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? recall(data);? ? ? ? ? ? ? ? ? //因?yàn)槭情]包函數(shù)recall會(huì)儲(chǔ)存response
? ? ? ? ? ? };
? ? ? ? ? ? console.log("函數(shù)執(zhí)行完畢");
? ? ? ? });
? ? }
}
```
## 寫文件
- 在optfile.js中加入寫文件功能:
```javascript
//optfile.js
var fs = require('fs');
module.exports = {
? ? writefile: function (path,recall) {
? ? ? ? fs.readFile(path, function(err,data){? ? ? //異步方式
? ? ? ? ? ? if(err){
? ? ? ? ? ? ? ? console.log(err)
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? console.log("It's saved!");? ? ? ? //文件被保存
? ? ? ? ? ? ? ? recall("寫入文件成功");
? ? ? ? ? ? }
? ? ? ? })
? ? }
}
```
- 在server.js中加入:
```javascript
optfile.writefile("./src/data.json","這里傳入想要寫入的數(shù)據(jù)",recall);
```
- PS:實(shí)際開(kāi)發(fā)過(guò)程中,這些函數(shù)一般是不放在server.js中的,應(yīng)該放在路由配置文件中,配置相應(yīng)的寫數(shù)據(jù)接口
## 讀取并顯示圖片
- 在optfile.js中加入寫文件功能:
```javascript
readImg: function(path,res){
? ? fs.readFile(path,"binary",function(err,filedata){? //binary參數(shù)代表讀取的是一個(gè)二進(jìn)制流的文件
? ? ? ? if(err){
? ? ? ? ? ? console.log(err);
? ? ? ? ? ? return;
? ? ? ? } else {
? ? ? ? ? ? res.write(filedata,"binary");? ? ? ? ? ? ? //用“binary”的格式發(fā)送數(shù)據(jù)
? ? ? ? ? ? res.end();
? ? ? ? }
? ? })
}
```
- 這種方法只能單獨(dú)讀取圖片
## 參數(shù)接受
- 只需修改路由文件中的:
```javascript
var querystring = require('querystring');? ? ? //需要引入querystring模塊來(lái)解析POST請(qǐng)求體中的參數(shù)
confirm: function (req,res) {
? ? //get方式接收參數(shù),處理是同步的
? ? var rdata = url.parse(req.url,true).query;
? ? if(rdata['email'] != undefined){
? ? ? ? console.log("email:"+rdata['email']+","+"password:"+rdata['password']);
? ? };
? ? //post方式接收參數(shù),處理是異步的
? ? var post = '';
? ? req.on('data',function(chunk){
? ? ? ? post += chunk;
? ? });
? ? req.on('end',function(){
? ? ? ? post = querystring.parse(post);
? ? ? ? console.log(post['email']+","+post['password']);
? ? });
}
```
## 動(dòng)態(tài)數(shù)據(jù)渲染
- 在使用路由傳輸文件的時(shí)候如果想要將頁(yè)面中某些字段動(dòng)態(tài)的替換成對(duì)應(yīng)的數(shù)據(jù),可以在數(shù)據(jù)(data)傳輸時(shí)先將其字符化(toString()),再使用正則將匹配到的標(biāo)識(shí)字符進(jìn)行替換,如vue中就使用"{{}}"雙大括號(hào)標(biāo)識(shí)數(shù)據(jù)位置,再進(jìn)行匹配替換為對(duì)應(yīng)數(shù)據(jù)
- 如修改回調(diào)函數(shù)
```javascript
function recall(data){
? ? dataStr = data.toString();
? ? for(let i=0;i
? ? ? ? re = new RegExp("{"+arr[i]+"}",g);? //用正則匹配標(biāo)識(shí)數(shù)據(jù)(這里我用“{}”一個(gè)大括號(hào)標(biāo)識(shí)數(shù)據(jù))
? ? ? ? dataStr = dataStr.replace(re,post[arr[i]])? //從數(shù)據(jù)庫(kù)中循環(huán)替換對(duì)應(yīng)的數(shù)據(jù)
? ? }
? ? res.write(dataStr);
? ? res.end();
}
```
## 異步流程控制
- 當(dāng)有某些操作需要依賴于上一步操作完成后才能執(zhí)行,因?yàn)閚ode里的操作大都是異步的,這就需要對(duì)異步流程進(jìn)行控制
- node.js提供了一個(gè)異步流程控制對(duì)象async
1. 串行無(wú)關(guān)聯(lián):async.series? (依次執(zhí)行,此步執(zhí)行的結(jié)果不影響下一個(gè)程序)
1. 并行無(wú)關(guān)聯(lián):async.parallel (同時(shí)執(zhí)行,正常的異步)
1. 串行有關(guān)聯(lián):waterfall? (瀑布流)
- async需要另外安裝:
```bash
npm install async --save-dev
```
## 連接MySQL數(shù)據(jù)庫(kù)
### 直接連接(不常用,需了解)
- 安裝MySQL支持:
```bash
npm install mysql
```
- 創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù),建表:
```mysql
create table user(
? ? uid int not null primary key auto_increment,? ? //id自增長(zhǎng)
? ? uname varchar(100) not null,
? ? pwd varchar(100) not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
- 直接連接MySQL并進(jìn)行操作,下面列舉了一些常用的操作,在實(shí)際操作過(guò)程中一般把這些操作封裝到對(duì)應(yīng)的方法中,需要的時(shí)候直接調(diào)用
```javascript
var mysql = require("mysql")? ? //調(diào)用MySQL模塊
//創(chuàng)建一個(gè)connection
var connection = mysql.createConnection({
? ? host: 'localhost',? //主機(jī)
? ? user: 'root',? ? ? //MySQL認(rèn)證用戶名
? ? password: "",? ? ? //MySQL認(rèn)證用戶密碼
? ? database: "rain",? //數(shù)據(jù)庫(kù)名
? ? port: '3306'? ? ? ? //端口號(hào)
});
//打開(kāi)connection連接
connection.connect(function(err){
? ? if(err){
? ? ? ? console.log('[query] - :'+err);
? ? ? ? return;
? ? }
? ? console.log("[connection connect] succeed");
})
//向表中插入數(shù)據(jù)
var userAddSql = "insert into user (uname,pwd) values(?,?)";? ? //要執(zhí)行的MySQL語(yǔ)句,value的問(wèn)號(hào)是占位符
var param = ['test','test'];? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //要插入的數(shù)據(jù)可以通過(guò)變量傳值,這里用字符串測(cè)試
connection.query(userAddSql,param,function(err,rs){? ? ? ? ? ? //調(diào)用query方法插入數(shù)據(jù),第一個(gè)參數(shù)是執(zhí)行的MySQL語(yǔ)句,第二個(gè)是插入的數(shù)據(jù),第三個(gè)是匿名回調(diào)函數(shù)處理報(bào)錯(cuò);傳的值一定要和MySQL語(yǔ)句匹配
? ? if(err){
? ? ? ? console.log("insert err:",err.message);
? ? ? ? return;
? ? }
? ? console.log(rs);? ? //rs是插入成功后返回的一些參數(shù)
});
//執(zhí)行查詢
//群體查詢
connection.query('SELECT * from user',function(err,rs,fields){
? ? if(err){
? ? ? ? console.log('[query] - :'+err);
? ? ? ? return;
? ? }
? ? for(let i=0;i
? ? ? ? console.log('The solution is: ',rs[i].uname);
? ? }
? ? console.log(fields);? ? ? ? //fields為查詢產(chǎn)生的信息,不是查詢結(jié)果,一般沒(méi)什么用
})
//單獨(dú)查詢
connection.query('SELECT * from user where uid=?',[2],function(err,rs,fields){? //查詢uid=2的那條數(shù)據(jù)
? ? if(err){
? ? ? ? console.log('[query] - :'+err);
? ? ? ? return;
? ? }
? ? for(let i=0;i
? ? ? ? console.log('The solution is: ',rs[i].uname);
? ? }
? ? console.log(fields);
})
//關(guān)閉connection連接
connection.end(function(err){
? ? if(err){
? ? ? ? console.log(err.toString());
? ? ? ? return;
? ? }
? ? console.log("[connection end] succeed");
})
```
- 這里列舉的操作都是較為常用的,還有其它的操作可以直接通過(guò)修改傳入的MySQL語(yǔ)句來(lái)完成
### 連接池連MySQL(常用,效率高)
- 原理:
- 創(chuàng)建MySQL連接的開(kāi)銷十分巨大
- 使用連接池 server啟動(dòng)時(shí)會(huì)創(chuàng)建10-20個(gè)連接放在連接池中,當(dāng)有訪問(wèn)需連接MySQL數(shù)據(jù)庫(kù)時(shí),就從連接池中取出一個(gè)連接,進(jìn)行數(shù)據(jù)庫(kù)操作,操作完成后,再將連接放回連接池
- 連接池會(huì)自動(dòng)的管理連接,當(dāng)連接較少時(shí),會(huì)減少連接池中的連接,當(dāng)連接量較大時(shí),會(huì)擴(kuò)充連接
- 使用node提供的連接池需安裝node-mysql模塊:
```bash
npm install node-mysql -g
```
#### 操作連接池
```javascript
//optPool.js
var mysql = require('mysql');
function optPool(){? ? ? ? //創(chuàng)建一個(gè)連接池的類方便使用
? ? this.flag = true;? ? ? //用來(lái)標(biāo)記是否連接過(guò)
? ? this.pool = mysql.createPool({
? ? ? ? host: 'localhost',? //主機(jī)
? ? ? ? user: 'root',? ? ? //MySQL認(rèn)證用戶名
? ? ? ? password: "",? ? ? //MySQL認(rèn)證用戶密碼
? ? ? ? database: "rain",? //數(shù)據(jù)庫(kù)名
? ? ? ? port: '3306'? ? ? ? //端口號(hào)
? ? });
? ? this.getPool = function(){? //初始化pool
? ? ? ? if(this.flag){
? ? ? ? ? ? //監(jiān)聽(tīng)connection事件
? ? ? ? ? ? this.pool.on('connection',function(connection){
? ? ? ? ? ? ? ? connection.query('SET SESSION auto_increment_increment=1');
? ? ? ? ? ? ? ? this.flag = false;
? ? ? ? ? ? });
? ? ? ? }
? ? ? ? return this.pool;
? ? }
};
module.exports = optPool;? //導(dǎo)出為一個(gè)類
```
```javascript
var optPool = require('./optPool');
var optpool = new optPool();
var pool = optpool.getPool();
//從連接池中獲取一個(gè)連接
pool.getConnection(function(err,connect){? //如果操作成功就拿到連接(connect)
? ? //做一個(gè)插入操作
? ? var userAddSql = "insert into user (uname,pwd) values(?,?)";
? ? var param = ['test','test'];
? ? connect.query(userAddSql,param,function(err,rs){? ? //異步操作
? ? ? ? if(err){
? ? ? ? ? ? console.log("insert err:",err.message);
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? console.log('success');
? ? ? ? connect.release()? //將連接放回連接池
? ? });
})
```