2017-12-27

#? 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()? //將連接放回連接池

? ? });

})

```

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容