引言
教程源于:
- 小马技术:https://www.youtube.com/watch?v=Vp_iIft0XNI&list=PLliocbKHJNwvbitOJ73M04PUoJae79kEg
- IT营-大地老师:https://www.bilibili.com/video/BV11t411k79h?p=1
该文章用于笔记整理
- 完整脑图:https://share.mubu.com/doc/perHOahM1G
- 个人仓库:https://github.com/sanhuamao1/LEARN-NODE (包含了该文章中涉及的例子)
准备
-
安装nodejs:http://nodejs.cn/download/
-
执行脚本:
node app.js
-
结束程序:ctrl+C
node版本管理:nvm——https://github.com/creationix/nvm)。可以轻松地切换 Node.js 版本,也可以安装新版本用以尝试并且当出现问题时轻松地回滚
一 线程模型
- 接收请求(request)
- 开出线程(thread)处理,用户等待
- 请求处理完成,线程(thread)释放
线程处理模式重复以上三个步骤,来处理来自客户端的各种请求。当有大量客户端请求来袭时,服务器消耗的资源也会随之增加。
二 node中的事件循环
- 开一个事件等待循环(event-loop)
- 接收请求
- 放入事件处理队列中,然后继续接收新的请求
- 请求完成后,调用I/O,结束请求(非阻塞调用)
事件循环处理模式中,线程不用等待req处理完,而是将所有请求放入队列中,然后采用非同步的方式,等待请求处理完成后再调用I/O资源,然后结束请求。
三 node中的非阻塞处理
- 阻塞处理(Java,Ruby,PHP,Asp,Net)
- 非阻塞处理(Node.js)
阻塞处理:后边的语句无法执行,除非前面的执行完毕
function updb1(){
let start =new Date().getTime()
while (new Date().getTime()<start+3000);
}
updb1()
console.log('数据库更新成功!')
console.log('其他语句')
非阻塞处理:继续执行后面的。(必须提供回调函数)
function updb2(done){
setTimeOut(()=>{
done()
},3000)
}
updb2(function(){
console.log('数据库更新成功!')
})
console.log('其他语句')
四 搭建http服务器
- http模块(nodejs内置模块)
- 参考文档:http://nodejs.cn/api/http.html
const http=require('http');
const hostname='127.0.0.1';
const port=3000;
const server=http.createServer((req,res)=>{
res.statusCode=200;//设置状态码
res.setHeader('Content-Type','text/plain')//设置响应头
res.end('Hello World')
})
server.listen(port,hostname,()=>{
console.log(`Server running at http://${hostname}:${port}/`);
})
一般情况下,每次更代码都需重新启动服务器,否则页面将没有变化。supervisor
工具可以在不用重复启动服务器的情况下就能看到页面变化:
npm install -g supervisor //全局安装
supervisor app.js //使用supervisor替代node命令来运行程序
五 引入自定义模块
默认情况下js文件是私有的。要让外部访问,需通过exports
或module.exports
暴露:
- require:引用外部文件
- exports、module.exports:公开文件
例子:hostname和port属于公共部分,可以抽出来作为模块
-
config.js
const config={ hostname:'127.0.0.1', port:3000 } exports.config=config
-
server.js
const http=require('http'); const config=require('./config').config //引用模块 const server=http.createServer((req,res)=>{ res.statusCode=200; res.setHeader('Content-Type','text/plain') res.end('Hello World') }) server.listen(config.port,config.hostname,()=>{ console.log(`Server running at http://${config.hostname}:${config.port}/`); })
另一种写法:
-
config.js
const config={ hostname:'127.0.0.1', port:3000 } module.exports=config
-
server.js
const config=require('./config')
六 路由
const http=require('http');
const hostname='127.0.0.1';
const port=3000;
const server=http.createServer((req,res)=>{
res.statusCode=200;
res.setHeader('Content-Type','text/plain')
switch(req.url){
case '/':
res.end('Hello World');
break;
case '/about':
res.end('This is about page')
break;
case '/home':
res.end('Welcome to myhomepage')
break;
default:
res.end('404 NOT FOUND!')
}
})
server.listen(port,hostname,()=>{
console.log(`Server running at http://${hostname}:${port}/`);
})
七 搭建静态服务器
7.1 读取html
./index.html
<html>
<body>
<h1>Hello world!</h1>
</body>
</html>
./server.js
const http=require('http');
const fs=require('fs')
//__dirname:当前文件所在目录
const server=http.createServer((req,res)=>{
fs.readFile(__dirname+'/index.html','utf-8',(err,data)=>{
if(err){
res.setHeader('Content-Type','text/plain');//纯文本
res.statusCode=404;
res.end('404 Not Founded!')
}else{
res.setHeader('Content-Type','text/html');//html
res.statusCode=200;
res.end(data)
}
})
})
const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
console.log(`Server running at http://${hostname}:${port}/`);
})
7.2 读取其他
由于不同文件的Content-Type不一样,所以这里封装一个根据后缀名获取文件Content-Type的方法:
./modules.js
const fs=require('fs')
exports.getMimeType=function (extname){
let data=fs.readFileSync('./mime.json')
let mimeObj=JSON.parse(data.toString())//转为对象
return mimeObj[extname]
};
//mime.json可在网上找:格式{".html": "text/html"}
./server.js
const http=require('http');
const fs=require('fs')
const path=require('path');
const common=require('./modules');
const server=http.createServer((req,res)=>{
let pathname=req.url
pathname=pathname=='/'?'/index.html':pathname
let extname=path.extname(pathname)//获取文件后缀名
fs.readFile('./static'+pathname,(err,data)=>{
if(err){
res.setHeader('Content-Type','text/plain');
res.statusCode=404;
res.end('404 Not Founded!')
}else{
let mime=common.getMimeType(extname)
res.setHeader('Content-Type', mime+";charset='utf-8'");
res.statusCode=200;
res.end(data)
}
})
})
const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
console.log(`Server running at http://${hostname}:${port}/`);
})
7.3 封装静态服务器
./module/route.js
const fs=require('fs')
const path=require('path');
const url=require('url');
function getMimeType(extname){
let data=fs.readFileSync('./mime.json')
let mimeObj=JSON.parse(data.toString())//转为对象
return mimeObj[extname]
};
exports.static=function(req,res,staticPath){
let pathname=url.parse(req.url,true).pathname
pathname=pathname=='/'?'/index.html':pathname
let extname=path.extname(pathname)
fs.readFile('./'+staticPath+pathname,(err,data)=>{
if(err){
res.setHeader('Content-Type','text/plain');
res.statusCode=404;
res.end('404 Not Founded!')
}else{
let mime=getMimeType(extname)
res.setHeader('Content-Type', mime+";charset='utf-8'");
res.statusCode=200;
res.end(data)
}
})
}
./server.js
const http = require('http');
const route= require('./module/route');
http.createServer(function (req, res) {
route.static(req, res,'static')
}).listen(3000);
八 使用第三方包ejs
- ejs(effective JavaScript template)(第三方模块)——指路-官方文档
- querystring(node内置模块)——查询字符串,指路-官方文档
- npm:Nodejs附带的第三方软件包管理器。是世界上最大的开放源代码的生态系统,可通过它下载各种各样的包,为Nodejs提供更多的功能支持——指路-npm与依赖包
- fs
8.1 基本使用
安装
npm install ejs
./helo.ejs
<html>
<title><%= title %></title>
<body>
<%- content %>
</body>
</html>
./server.js
const http=require('http');
const fs=require('fs')
const ejs=require('ejs')
var template=fs.readFileSync(__dirname+'/helo.ejs','utf-8');
const server=http.createServer((req,res)=>{
//模板渲染-传值
var data=ejs.render(template,{
title:'helo ejs',
content:'<strong>big helo ejs.</strong>'
})
res.setHeader('Content-Type','text/html');
res.statusCode=200;
res.end(data)
})
const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
console.log(`Server running at http://${hostname}:${port}/`);
})
8.2 get/post
./list.ejs
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表</title>
</head>
<body>
<form action="" method="post">
<input type="text" name="content" id="content">
<input type="submit" value="提交">
<ul>
<% for(let i=0 ; i < posts.length;i++){ %>
<li><%= posts[i] %></li>
<% } %>
</ul>
</form>
</body>
</html>
./server.js
const http=require('http');
const fs=require('fs')
const ejs=require('ejs')
const qs=require('querystring') //+ 处理用户提交的表单数据
var template=fs.readFileSync(__dirname+'/list.ejs','utf-8');
var posts=[] //+ 用户输入的内容
const server=http.createServer((req,res)=>{
if(req.method==='POST'){
req.data="";
req.on("data",function(postDataChunk){
req.data+=postDataChunk
})
req.on("end",function(){
//表单处理
let query=qs.parse(req.data)//将数据进行内部解析,生成查询变量
posts.push(query.content)//content就是取到表单name为content的内容
showForm(posts,res)
})
}else{
showForm(posts,res)
}
})
//渲染页面
function showForm(posts,res){
let data=ejs.render(template,{
title:'列表',
posts
})
res.setHeader('Content-Type','text/html');
res.statusCode=200;
res.end(data)
}
const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
console.log(`Server running at http://${hostname}:${port}/`);
})
九 使用MongoDB
- MongoDB入门——指路-使用MongoDB
- MongoDB的node驱动——指路-官方网站
- assert(内置模块)——指路-官方文档
9.1 连接MongoDB
驱动安装
npm install mongodb --save
./connect.js
//mongodb客户端
const MongoClient=require('mongodb').MongoClient
// 判断某两值是否相等的库
const test=require('assert')
// mongodb的连接地址
const url='mongodb://127.0.0.1:27017'
// 数据库名称-指定数据库名称
const dbName='admin'
// 连接使用客户端
MongoClient.connect(url, function(err, client) {
test.equal(null, err);//判断错误对象是否为空,为空说明成功
const adminDb = client.db(dbName)// 打开数据库进行操作
console.log('链接数据库成功')
client.close();
});
9.2 插入数据
./insert.js
const MongoClient=require('mongodb').MongoClient
const test=require('assert')
const url='mongodb://127.0.0.1:27017'
const dbName='mydata'//这里的mydata数据库是提前创建好的,其下创建了posts集合。
MongoClient.connect(url, function(err, client) {
test.equal(null, err);
const db = client.db(dbName)
console.log('链接数据库成功')
db.collection("posts").insertMany(
[
{title:"今天不是很热",tag:"life"},
{title:"外卖还没到,非常饿!",tag:"life"},
],
(err,result)=>{
test.equal(null,err)
client.close();
}
)
});
9.3 查询数据
./find.js
const MongoClient=require('mongodb').MongoClient
const test=require('assert')
const url='mongodb://127.0.0.1:27017'
const dbName='mydata'
MongoClient.connect(url, function(err, client) {
test.equal(null, err);
const adminDb = client.db(dbName)
console.log('链接数据库成功')
adminDb.collection("posts").find({tag:"study"}).toArray((err,docs)=>{
test.equal(null, err);
console.log(docs)
client.close()
})
});
十 处理异步
引言-异步/非阻塞的处理方式
10.1 回调地狱
Node.js是非阻塞编程,那么在编码过程中会遇到很多的回调函数(Callback),如果多个处理的回调函数嵌套在一起的话,就会形成回调地狱,虽然对于程序的结果没有任何影响,但对于程序代码的可读性来说就是个地狱。
function dbupd(sql, done) {
setTimeout(() => done(sql + " upd ok."), 800);
}
dbupd("1.sql1", result => {
console.log(result);
dbupd("2.sql2", result => {
console.log(result);
dbupd("3.sql3", result => {
console.log(result);
});
});
});
10.2 Promise解决
// Promise函数嵌套解决方法
function dbupAsync(sql) {
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(sql + " upd ok.");
resolve(sql + ".ok");
}, 800)
});
return p;
}
方法一:
dbupAsync("2.sql1")
.then(() => dbupAsync("2.sql2"))
.then(() => dbupAsync("3.sql3"));
方法二:async/await 更简洁
async function upAllDB() {
const result1 = await dbupAsync("3.sql1");
const result2 = await dbupAsync("3.sql2");
const result3 = await dbupAsync("3.sql3");
console.log(result1, result2, result3);
}
upAllDB();
十一 封装express路由
./moudle/route
const fs=require('fs')
const path=require('path');
const url=require('url');
//获取文件类型
function getMimeType(extname){
let data=fs.readFileSync('./mime.json')
let mimeObj=JSON.parse(data.toString())//转为对象
return mimeObj[extname]
};
//扩展res
function changeRes(res){
res.send=(data)=>{
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
}
}
//静态web服务的方法
function initStatic(req, res, staticPath) {
//1、获取地址
let pathname = url.parse(req.url).pathname;
pathname = pathname == '/' ? '/index.html' : pathname;
let extname = path.extname(pathname);
//2、通过fs模块读取文件
try {
let data = fs.readFileSync('./' + staticPath + pathname);
if (data) {
let mime=getMimeType(extname)
res.setHeader('Content-Type', mime+";charset='utf-8'");
res.statusCode=200;
res.end(data)
}
} catch (error) {
}
}
let server = () => {
let G = {
_get:{},
_post:{},
staticPath:'static' //默认静态web目录
};
let app = function (req, res) {
//扩展res的方法
changeRes(res);
//配置静态web服务
initStatic(req, res,G.staticPath);
let pathname = url.parse(req.url).pathname;
//获取请求类型
let method=req.method.toLowerCase();
if (G['_'+method][pathname]) {
if(method=="get"){
G['_'+method][pathname](req, res); //执行方法
}else{
//post 获取post的数据 把它绑定到req.body
let postData = '';
req.on('data',(chunk)=>{
postData+=chunk;
})
req.on('end',()=>{
req.body=postData;
G['_'+method][pathname](req, res); //执行方法
})
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('页面不存在');
}
}
//get请求
app.get = function (str, cb) {
//注册方法
G._get[str] = cb;
}
//post请求
app.post = function (str, cb) {
//注册方法
G._post[str] = cb;
}
//配置静态web服务目录
app.static=function(staticPath){
G.staticPath=staticPath;
}
return app;
}
module.exports = server();
十二 注册用户并存入数据库
- ejs
- mongodb
- fs
- querystring
.server.js
const http=require("http")
const app=require('./module/route')//上面封装的
const ejs=require('ejs')
const qs=require('querystring')
const MongoClient=require('mongodb').MongoClient
const test=require('assert')
const url='mongodb://127.0.0.1:27017'
const dbName='mydata'
http.createServer(app).listen(3000)
//首页
app.get('/',(req,res)=>{
ejs.renderFile("./views/index.html",{},(err,data)=>{
res.send(data)
})
})
app.get('/register',(req,res)=>{
ejs.renderFile("./views/register.ejs",{},(err,data)=>{
res.send(data)
})
})
//获取注册用户列表
app.get('/userlist',(req,res)=>{
MongoClient.connect(url, function(err, client) {
test.equal(null, err);
const db = client.db(dbName)
console.log('链接数据库成功')
db.collection("users").find().toArray((err,result)=>{
test.equal(null, err);
ejs.renderFile("./views/userlist.ejs",{
users:result
},(err,data)=>{
res.send(data)
console.log('获取成功')
client.close()
})
})
});
})
//提交注册
app.post('/submit',(req,res)=>{
let body=qs.parse(req.body)
MongoClient.connect(url, function(err, client) {
test.equal(null, err);
const db = client.db(dbName)
console.log('链接数据库成功')
db.collection("users").insertOne(
body,
(err,result)=>{
test.equal(null,err)
console.log('注册成功!')
res.send('注册成功!')
client.close();
}
)
});
})