#? Node.js學(xué)習(xí)筆記
## 簡介
- 編寫高性能網(wǎng)絡(luò)服務(wù)器的JavaScript工具包
- 單線程岭粤、異步、事件驅(qū)動
- 特點(diǎn):快特笋,耗內(nèi)存多
- PHP是單線程剃浇,耗內(nèi)存少速度相對慢些
## 在Linux上安裝node
1. 先安裝一個[nvm](https://github.com/creationix/nvm)用于切換node版本:
- 進(jìn)入nvm的GitHub點(diǎn)擊README的installation
- 找到下列代碼(為了獲取最新的版本)復(fù)制下? 來輸入到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測試,如果報錯:
```bash
Computing checksum with shasum -a 256 Checksums do not match:
```
- 就再輸入:
```bash
export NVM_DIR="$HOME/.nvm"
#不行就再輸入
. "/usr/local/opt/nvm/nvm.sh"
```
- 如果還報錯去[官網(wǎng)](https://github.com/creationix/nvm/issues/576)再找解決方案
2. nvm安裝完成后輸入:
```bash
nvm install --lts
nvm use (你安裝的版本號)
```
- 安裝最新穩(wěn)定版,并使用它
- 輸入node雹有,如果進(jìn)入了node交互環(huán)境就安裝成功了
- 如果要查看已經(jīng)安裝的node版本偿渡,輸入:
```bash
nvm ls
```
3. 完善安裝
- 上述過程完成后,有時會出現(xiàn)霸奕,當(dāng)開啟一個新的 shell 窗口時溜宽,找不到 node 命令的情況,這種情況一般來自兩個原因:
- 一质帅、shell 不知道 nvm 的存在
- 二适揉、nvm 已經(jīng)存在留攒,但是沒有 default 的 Node.js 版本可用。
- 解決方式:
- 一嫉嘀、檢查 ~/.profile 或者 ~/.bash_profile 中有沒有這樣兩句
```bash
export NVM_DIR="/Users/YOURUSERNAME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"? # This loads nvm
```
- 沒有的話炼邀,加進(jìn)去。這兩句會在 bash 啟動的時候被調(diào)用剪侮,然后注冊 nvm 命令拭宁。
- 二、調(diào)用
```bash
nvm ls
```
- 看看有沒有 default 的指向瓣俯。如果沒有的話杰标,執(zhí)行
```bash
$ nvm alias default (你安裝的版本號)
#再
$ 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)建一個服務(wù),該函數(shù)有兩個參數(shù):request和response它們是對象彩匕,用它們的方法來處理HTTP請求的細(xì)節(jié)腔剂,并且響應(yīng)請求
? ? response.statusCode = 200;? ? ? ? ? ? ? ? ? //返回的狀態(tài)碼
? ? response.setHeader('Content-Type', 'text/plain');? //HTTP協(xié)議頭輸出類型
? ? response.end();? ? ? ? ? ? ? ? ? ? ? ? ? ? //結(jié)束HTTP請求,不寫就沒有HTTP協(xié)議尾
}).listen(8000);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //監(jiān)聽8000端口
console.log("Server running at http://127.0.0.1:8000/")
```
- createServer()方法接受一個方法作為參數(shù)
- 如果只是上面的一個簡單服務(wù)驼仪,瀏覽器訪問時會提交兩次HTTP請求(express框架中已經(jīng)清除)
- 如果不用express框架掸犬,需手工清除:
```javascript
if(request.url !== "/favicon.ico"){? ? //清除第2次訪問:因?yàn)榇蟛糠譃g覽器都會在你訪問 http://localhost:8888/ 時嘗試讀取 http://localhost:8888/favicon.ico(網(wǎng)頁標(biāo)題“title”旁的圖標(biāo)),請求地址就是“/favicon.ico”但對于我們服務(wù)器來說這次請求是不需要進(jìn)行額外處理的無效請求绪爸。
? ? 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)榭梢酝ㄟ^:
? ? 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;? ? //只支持一個函數(shù)
//形式2:支持多個函數(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;
```
## 路由
- 通過解析url獲取路由的字符串毡泻,調(diào)用路由文件內(nèi)對應(yīng)的方法胜茧,通過該方法讀取對應(yīng)的HTML文件,再將HTML文件返回給客戶端
```javascript
//server.js
var http = require('http');
var url = require('url');? ? ? ? ? //node.js提供一個“url”對象來解析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;? ? //通過url.pathname方法解析出url后面的路由
? ? pathname = pathname.replace(/\//,'')? ? ? ? ? ? ? ? //通過正則將路由字符串的斜杠頭給去掉
? ? router[pathname](request,response);? ? ? ? ? ? ? ? //通過pathname字符串調(diào)用路由中對應(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頁面")
? ? }
? ? register: function(req,res){
? ? ? ? res.write("轉(zhuǎn)到注冊頁面")
? ? }
}
```
## 讀取文件
### 同步讀取文件(不推薦)
- 讀取文件的配置文件:optfile.js
```javascript
//optfile.js
var fs = require('fs');? ? ? ? ? ? ? ? //node.js提供的操作文件模塊
module.exports={
? ? readfileSync: function (path) {? ? //同步讀取方法
? ? ? ? var data = fs.readFilesSync(path,'utf-8');? //通過fs的readFilesSync方法同步讀取文件仇味,path為文件路徑,以utf-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')? //路徑是相對于此文件的
? ? response.end("主程序執(zhí)行完畢");
}).listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
```
- node.js的高性能主要就是依靠異步操作雹顺,在異步時丹墨,當(dāng)服務(wù)器執(zhí)行讀文件的操作時程序會繼續(xù)執(zhí)行下去,而不是在原地等文件讀取完畢嬉愧,因此上述代碼會在控制臺依次輸出:
```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);? ? ? ? ? //它可以儲存response
? ? ? ? response.end();
? ? }
? ? optfile.readfileSunc('.src/login.html',recall)? //路徑是相對于此文件的
}).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會儲存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í)際開發(fā)過程中,這些函數(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ù)代表讀取的是一個二進(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模塊來解析POST請求體中的參數(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']);
? ? });
}
```
## 動態(tài)數(shù)據(jù)渲染
- 在使用路由傳輸文件的時候如果想要將頁面中某些字段動態(tài)的替換成對應(yīng)的數(shù)據(jù)挂疆,可以在數(shù)據(jù)(data)傳輸時先將其字符化(toString())改览,再使用正則將匹配到的標(biāo)識字符進(jìn)行替換,如vue中就使用"{{}}"雙大括號標(biāo)識數(shù)據(jù)位置缤言,再進(jìn)行匹配替換為對應(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ù)據(jù)(這里我用“{}”一個大括號標(biāo)識數(shù)據(jù))
? ? ? ? dataStr = dataStr.replace(re,post[arr[i]])? //從數(shù)據(jù)庫中循環(huán)替換對應(yīng)的數(shù)據(jù)
? ? }
? ? res.write(dataStr);
? ? res.end();
}
```
## 異步流程控制
- 當(dāng)有某些操作需要依賴于上一步操作完成后才能執(zhí)行宝当,因?yàn)閚ode里的操作大都是異步的,這就需要對異步流程進(jìn)行控制
- node.js提供了一個異步流程控制對象async
1. 串行無關(guān)聯(lián):async.series? (依次執(zhí)行胆萧,此步執(zhí)行的結(jié)果不影響下一個程序)
1. 并行無關(guān)聯(lián):async.parallel (同時執(zhí)行庆揩,正常的異步)
1. 串行有關(guān)聯(lián):waterfall? (瀑布流)
- async需要另外安裝:
```bash
npm install async --save-dev
```
## 連接MySQL數(shù)據(jù)庫
### 直接連接(不常用,需了解)
- 安裝MySQL支持:
```bash
npm install mysql
```
- 創(chuàng)建一個新的數(shù)據(jù)庫跌穗,建表:
```mysql
create table user(
? ? uid int not null primary key auto_increment,? ? //id自增長
? ? uname varchar(100) not null,
? ? pwd varchar(100) not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
- 直接連接MySQL并進(jìn)行操作订晌,下面列舉了一些常用的操作,在實(shí)際操作過程中一般把這些操作封裝到對應(yīng)的方法中瞻离,需要的時候直接調(diào)用
```javascript
var mysql = require("mysql")? ? //調(diào)用MySQL模塊
//創(chuàng)建一個connection
var connection = mysql.createConnection({
? ? host: 'localhost',? //主機(jī)
? ? user: 'root',? ? ? //MySQL認(rèn)證用戶名
? ? password: "",? ? ? //MySQL認(rèn)證用戶密碼
? ? database: "rain",? //數(shù)據(jù)庫名
? ? port: '3306'? ? ? ? //端口號
});
//打開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語句腾仅,value的問號是占位符
var param = ['test','test'];? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //要插入的數(shù)據(jù)可以通過變量傳值,這里用字符串測試
connection.query(userAddSql,param,function(err,rs){? ? ? ? ? ? //調(diào)用query方法插入數(shù)據(jù)套利,第一個參數(shù)是執(zhí)行的MySQL語句推励,第二個是插入的數(shù)據(jù),第三個是匿名回調(diào)函數(shù)處理報錯肉迫;傳的值一定要和MySQL語句匹配
? ? 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é)果,一般沒什么用
})
//單獨(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");
})
```
- 這里列舉的操作都是較為常用的喊衫,還有其它的操作可以直接通過修改傳入的MySQL語句來完成
### 連接池連MySQL(常用跌造,效率高)
- 原理:
- 創(chuàng)建MySQL連接的開銷十分巨大
- 使用連接池 server啟動時會創(chuàng)建10-20個連接放在連接池中,當(dāng)有訪問需連接MySQL數(shù)據(jù)庫時族购,就從連接池中取出一個連接壳贪,進(jìn)行數(shù)據(jù)庫操作,操作完成后寝杖,再將連接放回連接池
- 連接池會自動的管理連接违施,當(dāng)連接較少時,會減少連接池中的連接瑟幕,當(dāng)連接量較大時磕蒲,會擴(kuò)充連接
- 使用node提供的連接池需安裝node-mysql模塊:
```bash
npm install node-mysql -g
```
#### 操作連接池
```javascript
//optPool.js
var mysql = require('mysql');
function optPool(){? ? ? ? //創(chuàng)建一個連接池的類方便使用
? ? this.flag = true;? ? ? //用來標(biāo)記是否連接過
? ? this.pool = mysql.createPool({
? ? ? ? host: 'localhost',? //主機(jī)
? ? ? ? user: 'root',? ? ? //MySQL認(rèn)證用戶名
? ? ? ? password: "",? ? ? //MySQL認(rèn)證用戶密碼
? ? ? ? database: "rain",? //數(shù)據(jù)庫名
? ? ? ? port: '3306'? ? ? ? //端口號
? ? });
? ? this.getPool = function(){? //初始化pool
? ? ? ? if(this.flag){
? ? ? ? ? ? //監(jiān)聽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)出為一個類
```
```javascript
var optPool = require('./optPool');
var optpool = new optPool();
var pool = optpool.getPool();
//從連接池中獲取一個連接
pool.getConnection(function(err,connect){? //如果操作成功就拿到連接(connect)
? ? //做一個插入操作
? ? 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()? //將連接放回連接池
? ? });
})
```