内容:
1.node.js介绍
2.node.js内置常用模块
3.node.js数据交互
一、node.js介绍
(1)node.js特点
与其他语言相比,有以下优点:
- 对象、语法和JavaScript一模一样,易于前端使用
- 相比PHP、Java、python,性能还可以
- 前后端配合方便
- 非阻塞、异步的交互
当然,也有缺点:比如说库支持不如Java丰富,和js一样是单线程单进程
(2)node.js安装
下载对应你系统的Node.js版本:https://nodejs.org/en/download/,选安装目录进行安装即可
安装完毕测试如下:
(3)node.js用处
- 服务器 - 小型后台系统、中间层
- 做工具(测试、构建、抓取) - grunt、gulp、WebPack
(4)运行node
创建一个node文件夹,然后在文件夹下写入1.js:
1 let a = 12; 2 let b = 5; 3 4 console.log(a+b)
然后在命令行中进入node目录下运行该文件:node 1.js 注:node运行文件:node xxx.js
运行结果如下:
二、node.js内置常用模块
1.断言——assert
1 const assert = require("assert") 2 3 function sum(a, b){ 4 // assert(判断, "xxx") 判断为假输出后面的信息 5 assert(arguments.length==2, "必须传两个参数") 6 assert(typeof a == 'number', "第一个参数必须是数字") 7 assert(typeof b == 'number', "第二个参数必须是数字") 8 9 return a+b 10 } 11 12 console.log(sum(2, 5)) 13 // 执行下面人任意一句将报错: 14 // console.log(sum(2, '1')) 15 // console.log(sum(3))
2.Buffer和File System模块
- buffer:曾经是node中的模块,后来融入到node本身之中了,处理二进制
- file system:处理文件(读写文件)
file system使用实例:
1 const fs = require("fs") 2 3 // 读取文件 4 fs.readFile('1.txt', function(err, data){ 5 if(err){ 6 console.log("有错!"); 7 } else { 8 console.log(data); 9 console.log(data.toString()); 10 } 11 12 }) 13 14 // 写文件 15 fs.writeFile('2.txt', 'xxx', function(err){ 16 if (err) { 17 console.log(err); 18 } else { 19 console.log("成功!"); 20 } 21 })
注意:图片不要将二进制转成字符串,这样做会导致图片格式丢失
获取文件详细信息:
1 const fs = require('fs') 2 3 fs.stat('1.txt', function (err, stat) { 4 if(err){ 5 console.log('获取文件信息失败') 6 } else{ 7 console.log(stat) // detail info 8 console.log(stat.mtime.toGMTString()) // 修改时间 9 } 10 })
Buffer基础使用:
1 let a = new Buffer('abc'); 2 let b = new Buffer('ddd'); 3 console.log(a, b) 4 // <Buffer 61 62 63> <Buffer 64 64 64> 5 6 let c = Buffer.concat([a, b]); 7 console.log(c); 8 // <Buffer 61 62 63 64 64 64>
Buffer数据操作:
1 // 查找 2 let a=new Buffer('abccc-=-dddder-=-qwerqwer'); 3 console.log(a.indexOf('-=-')); 4 5 // 截取 6 let b=new Buffer('abccc-=-dddder-=-qwerqwer'); 7 console.log(b.slice(0, 5).toString()); 8 9 // 切分 --> 目前buffer自带的操作中没有可以直接用的split 10 let c=new Buffer('abccc-=-dddder-=-qwerqwer'); 11 12 Buffer.prototype.split=Buffer.prototype.split||function (c){ // 如果buffer有split就用buffer自带的split,没有就用下面的函数 13 let arr=[]; 14 15 let cur=0; 16 let n=0; 17 while((n=this.indexOf(c, cur))!==-1){ 18 arr.push(this.slice(cur, n)); 19 cur=n+c.length; 20 } 21 22 arr.push(this.slice(cur)); 23 24 return arr; 25 }; 26 27 let arr=c.split('-=-'); 28 console.log(arr); 29 console.log(arr.map(buffer=>buffer.toString()));
3.C++ Addons - 用C语言/C++写插件给node用
4.多进程
理论上JavaScript是单进程单线程的,可以通过以下模块实现多进程:
- Child Processes
- Cluster
- Process
注:node中没有多线程的直接实现(为了考虑安全性、应用性)
(1)进程与线程
- 进程:进程拥有独立的执行空间和存储空间
- 线程:同一个进程内的所有线程共享一套空间、代码
- 多进程:成本高(慢)、安全(进程间隔离)、进程间通信麻烦、写代码简单、PHP、node
- 多线程:成本低(快)、不安全(线程间共享)、线程间通信简单、写代码复杂、Java、C
- 多进程:慢、简单、安全
- 多线程:快、复杂、脆弱
(2)进程之间的通信方法
- 管道
- 共享内存
- socket
(3)详细用法
详细用法见:https://www.cnblogs.com/wyb666/p/9704056.html
5.Crypto——散列、签名
crypto模块提供了md5、sha算法,主要用来进行加密(实质上是散列)、签名
普通加密:
1 const crypto = require('crypto') 2 3 let obj = crypto.createHash('sha1') 4 // 或者用md5加密: 5 // let obj = crypto.createHash('md5') 6 7 obj.update('123456') 8 9 console.log(obj.digest('hex')) # 以16进制输出数据
二次加密并加盐:
1 const crypto = require('crypto') 2 3 function md5(str){ 4 let obj = crypto.createHash('md5') 5 obj.update(str) 6 7 return obj.digest('hex') 8 } 9 10 // 二级加密并加盐 11 console.log(md5(md5('123456') + 'asdfghjklzxcvbnm,./' ))
6.http
- HTTP/HTTPS
- HTTP/2
下面是用http模块搭建简单服务器的大致过程:
最简单的服务器:
1 const http = require("http") 2 3 let server = http.createServer(function(req, res){ 4 // 路由处理 5 switch(req.url){ 6 case '/aaa': 7 res.write('abc'); 8 break; 9 case '/bbb': 10 res.write('dddd'); 11 break; 12 case '/1.html': 13 res.write('<html><head></head><body>sdfasfasf</body></html>'); 14 break; 15 } 16 res.end() 17 }); 18 19 // 监听 20 server.listen(8080)
前后端代码分离的服务器(前端代码存在www文件夹下):
1 const http=require('http'); 2 const fs=require('fs'); 3 4 let server=http.createServer(function(req, res){ 5 fs.readFile(`www${req.url}`, function(err, data){ 6 if(err){ 7 res.write('404'); // 404页面 8 }else{ 9 res.write(data); 10 } 11 res.end(); 12 }); 13 }); 14 15 server.listen(8080);
注意:fs.readFile是一个异步操作,必须将res.end()放在readFile内,如果放在readFile外面会导致以下错误:
这个错误是因为程序不会等readFile执行完就会执行后面的end,因此要将end放在readFile内才会在读完文件后执行end
7.OS和Path
- OS:系统相关
- Path:处理路径
1 const os=require('os'); 2 const path=require('path'); 3 4 // 输出CPU信息: 5 console.log(os.cpus()); 6 7 // 路径相关: 8 let str='/var/local/www/aaa/1.png'; 9 //dirname -> 文件夹路径 10 //basename -> 文件名 11 //extname -> 拓展名 12 console.log(path.dirname(str)); // /var/local/www/aaa 13 console.log(path.basename(str)); // 1.png 14 console.log(path.extname(str)); // .png
8.Events事件队列
(1)机制原理
Nodejs的大部分核心API都是基于异步事件驱动设计的,所有可以分发事件的对象都是EventEmitter类的实例。
大家知道,由于nodejs是单线程运行的,所以nodejs需要借助事件轮询,不断去查询事件队列中的事件消息,然后执行该事件对应的回调函数,有点类似windows的消息映射机制
(2)使用实例
1 const Event = require("events").EventEmitter 2 3 let ev = new Event() 4 5 // 1、监听(接受) 6 ev.on('msg', function(a, b, c){ 7 console.log('收到了msg事件', a, b, c); 8 }) 9 10 // 2、派发(发送) 11 ev.emit('msg', 12, 5, 98)
(3)注意
大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类
- 具有某个实体功能的对象实现事件符合语义, 事件的监听和发射应该是一个对象的方法
- JavaScript 的对象机制是基于原型的,支持 部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系
9.Query Strings和URL
(1)Query Strings
Query Strings:查询字符串,url中的?之后的字符串即为Query Strings
比如www.xxx.com/find?s=k&wd=123中的查询字符串就是 s=k&wd=123
querystring实例:
1 const querystring = require("querystring") 2 3 // url: www.xxx.com/find?s=k&wd=123 4 let obj = querystring.parse("s=k&wd=123") 5 6 console.log(obj) 7 // 解析结果: { s: 'k', wd: '123' }
(2)URL
url模块和querystring模块不同之处:url模块解析整个url,而querystring只能解析url中问号之后的字符串
实例:
1 const url = require("url") 2 3 let obj = url.parse("www.xxx.com/find?s=k&wd=123") 4 5 console.log(obj) 6 /* 7 输出结果: 8 Url { 9 protocol: null, 10 slashes: null, 11 auth: null, 12 host: null, 13 port: null, 14 hostname: null, 15 hash: null, 16 search: '?s=k&wd=123', 17 query: 's=k&wd=123', 18 pathname: 'www.xxx.com/find', 19 path: 'www.xxx.com/find?s=k&wd=123', 20 href: 'www.xxx.com/find?s=k&wd=123' 21 } 22 */
注意:也可以像下面一样指定将query也一并解析
1 const url = require("url") 2 3 let obj = url.parse("www.xxx.com/find?s=k&wd=123", true) 4 5 console.log(obj) 6 /* 7 输出结果: 8 Url { 9 protocol: null, 10 slashes: null, 11 auth: null, 12 host: null, 13 port: null, 14 hostname: null, 15 hash: null, 16 search: '?s=k&wd=123', 17 query: { s: 'k', wd: '123' }, 18 pathname: 'www.xxx.com/find', 19 path: 'www.xxx.com/find?s=k&wd=123', 20 href: 'www.xxx.com/find?s=k&wd=123' 21 } 22 */
10.网络相关模块
- TCP-稳定 -> Net
- UDP-快 -> UDP/Datagram
- DNS -> 域名解析相关
- Domain -> 域名相关
DNS解析实例:
1 const dns = require("dns") 2 3 dns.resolve("baidu.com", function(err, res){ 4 if(err){ 5 console.log("解析失败") 6 } else { 7 console.log(res) 8 } 9 10 }) 11 12 // 结果: [ '220.181.57.216', '123.125.115.110' ]
11.流操作——Stream
(1)什么是流
连续数据都是流:比如说视频流、网络流、文件流、语音流
(2)Stream具体操作
读取写入文件:
1 const fs = require('fs') 2 3 let rs = fs.createReadStream('1.txt') // 读取流 4 let ws = fs.createWriteStream('2.txt') // 写入流 5 6 rs.pipe(ws) 7 8 // 异常处理 9 rs.on('error', function (error) { 10 console.log('读取失败!') 11 }) 12 13 // 读取完成 及 写入完成 14 rs.on('end', function () { 15 console.log('读取完成!') 16 }) 17 18 ws.on('finish', function () { 19 console.log('写入完成!') 20 })
12.TLS/SSL
用于加密、安全
13.ZLIB
用于压缩 - gz压缩
zlib模块使用实例:
1 const zlib = require('zlib') 2 const fs = require('fs') 3 4 let rs = fs.createReadStream('jQuery.js') 5 let ws = fs.createWriteStream('jQuery.js.gz') 6 7 let gz = zlib.createGzip() 8 9 rs.pipe(gz).pipe(ws) 10 11 // 异常处理 12 rs.on('error', function (error) { 13 console.log('读取失败!') 14 }) 15 16 // 读取完成 及 写入完成 17 rs.on('end', function () { 18 console.log('读取完成!') 19 }) 20 21 ws.on('finish', function () { 22 console.log('写入完成!') 23 })
三、node.js数据交互
web服务器三大任务:返回文件(html、css、图片等)、数据交互(get、post)、数据库,下面的部分将围绕返回文件及数据交互展开
1.返回文件
返回文件可以使用node.js中的fs模块,实例如下:
1 const fs = require("fs") 2 3 fs.readFile('1.txt', function(err, data){ 4 if(err){ 5 console.log("有错!"); 6 } else { 7 console.log(data); // 二进制 Buffer -> 可以直接把这样的数据返回给前端(图片只能返回二进制,返回字符串将使图片失效) 8 console.log(data.toString()); 9 } 10 11 })
2.数据交互 - get和post和文件上传
- get数据:url里面、小于32K
- post数据:作为body、比较大
- file数据:form表单的处理、后端的处理
(1)设置header
- setHeader() --> 一般使用这种来设置header(键值对)
- writeHeader()
- write()
简单设置header:
1 const http=require('http'); 2 const fs=require('fs'); 3 4 let server=http.createServer(function(req, res){ 5 fs.readFile(`www${req.url}`, function(err, data){ 6 if(err){ 7 // 返回404 8 res.writeHeader(404); // header 9 res.write('Not Found'); // body 10 }else{ 11 res.write(data); 12 } 13 res.end(); 14 }); 15 }); 16 17 server.listen(8080);
(2)get数据处理
get表单:
1 <form action="http://localhost:8080/aaa" method="get"> 2 用户:<input type="text" name="user" /><br> 3 密码:<input type="password" name="pass" /><br> 4 <input type="submit" value="提交"> 5 </form>
后端node:
1 const http=require('http') 2 const url=require('url') 3 4 let server=http.createServer(function(req, res){ 5 let {pathname, query} = url.parse(req.url, true) 6 console.log(pathname) // -> /xxx的形式 7 console.log(query) // -> { user: 'xxx', pass: 'xxx' }的形式 8 9 res.end() 10 }) 11 12 server.listen(8080)
(3)post数据处理
post表单(注意get和post请求可以同时提交):
1 <form action="http://localhost:8080/aaa?id=12&a=55" method="post"> 2 用户:<input type="text" name="user" /><br> 3 密码:<input type="password" name="pass" /><br> 4 <input type="submit" value="提交"> 5 </form>
后端node:
1 const http=require('http') 2 const querystring=require('querystring') 3 4 let server=http.createServer(function(req, res){ 5 let str='' 6 7 // 有一个段到达了 8 req.on('data', function(data){ 9 str+=data 10 }) 11 12 // 结束了 13 req.on('end', function(){ 14 let post=querystring.parse(str) 15 console.log(str) 16 console.log(post) 17 }) 18 19 res.end() 20 }) 21 22 server.listen(8080)
注意:url和querystring的不同之处:
1 // url解析整个url
2 url.parse("www.xxx.com/aaa/bbb?a=12&b=5")
3 url.parse("/aaa/bbb?a=12&b=5")
4 // 另外加上true表示进一步解析query参数(不加就默认不进一步解析query):
5 url.parse("/aaa/bbb?a=12&b=5", true)
6 ->
7 {
8 、、、
9 "query": {a: 12, b: 5}
10 、、、
11 }
12
13 // querystring解析数据
14 querystring.parse("a=12&b=5")
(4)get和post数据一块处理
前端还是使用前面的get表单和post表单
后端node:
1 const http=require('http'); 2 const url=require('url'); 3 const querystring=require('querystring'); 4 5 let server=http.createServer((req, res)=>{ 6 // GET 7 let {pathname, query}=url.parse(req.url, true); 8 9 // POST 10 let str=''; 11 req.on('data', function(data){ 12 str+=data; 13 }); 14 req.on('end', function(){ 15 let post=querystring.parse(str); 16 17 console.log(pathname, query, post); 18 }); 19 20 res.end() 21 }); 22 23 server.listen(8080);
注意:在一个表单中可以get请求可以和post请求同时发
(5)文件上传
前端代码:
1 <form action="" method="post" enctype="multipart/form-data"> 2 <input type="file" name="upload-file"> 3 <input type="submit" value="上传文件"> 4 </form> 5 6 注意: 7 上传文件时表单中的enctype="multipart/form-data"必须要写 8 input(file)必须要有name
后端可以使用fs中的readFile和writeFile实现(读取完上传的文件后保存)
这样做有弊端:
- 只能等到所有数据都到达了才开始处理
- readFile先把所有数据全读到内存中,然后回调:
- 1.极其占用内存
- 2.资源利用极其不充分
更好的方法:使用流,实例见后面的文件上传实例
3.数据交互实例 - 登陆注册简单实现
(1)基本原理
1 // GET数据 -> 在url中 2 let {pathname, query} = url.parse(req.url, true) // 请求的地址及?之后的参数 3 4 // POST数据 -> 在body里 比较大 5 let str = '' 6 req.on('data', function(data){ 7 str += data // post提交的数据 8 }) 9 10 req.end('end', function(err){ 11 let post = querystring.parse(str) // 解析提交的数据(字符串->对象) 12 })
(2)前端代码
1 <!-- author: wyb --> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>登陆</title> 7 <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> 8 </head> 9 <body> 10 11 用户名: <input type="text" id="user"> <br> 12 密码: <input type="password" id="pass"> <br> 13 <input type="button" value="注册" id="btn1"> 14 <input type="button" value="登陆" id="btn2"> 15 16 <script> 17 /* 18 // 前后端接口: 19 用户注册: 20 /reg?user=xxx&pass=xxx 21 =>{error: 0, msg: '说明'} 22 23 用户登陆: 24 /login?user=xxx&pass=xxx 25 =>{error: 0, msg: '说明'} 26 */ 27 $(function () { 28 // 注册 29 $('#btn1').click(function () { 30 $.ajax({ 31 url: '/reg', 32 data: {user: $('#user').val(), pass: $('#pass').val()}, 33 dataType: 'json', 34 success(data){ 35 if(data.error){ 36 alert("错了: " + data.msg) 37 } else { 38 alert("注册成功") 39 } 40 }, 41 error(){ 42 alert("错了") 43 } 44 }) 45 }) 46 47 // 登陆 48 $('#btn2').click(function () { 49 $.ajax({ 50 url: '/login', 51 data: {user: $('#user').val(), pass: $('#pass').val()}, 52 dataType: 'json', 53 success(data){ 54 if(data.error){ 55 alert("错了: " + data.msg) 56 } else { 57 alert("登陆成功") 58 } 59 }, 60 error(){ 61 alert("错了") 62 } 63 }) 64 }) 65 }) 66 </script> 67 68 </body> 69 </html>
(3)后端代码(node)
1 const http = require('http'); 2 const url = require('url'); 3 const querystring = require('querystring'); 4 const fs = require('fs'); 5 6 /* 7 // 前后端接口: 8 用户注册: 9 /reg?user=xxx&pass=xxx 10 =>{error: 0, msg: '说明'} 11 12 用户登陆: 13 /login?user=xxx&pass=xxx 14 =>{error: 0, msg: '说明'} 15 */ 16 17 // users在内存中保存用户登陆信息 18 let users = { 19 // 'xxx': '123456', 20 // 'wyb': '654321' 21 }; 22 23 let server = http.createServer(function(req, res) { 24 // GET 25 let {pathname, query} = url.parse(req.url, true); 26 27 // POST 28 let str = ''; 29 req.on('data', function (data) { 30 str += data; 31 }); 32 33 req.on('end', function () { 34 let post = querystring.parse(str); 35 let {user, pass} = query 36 37 switch (pathname) { 38 // 注册 39 case '/reg': 40 if (!user) { 41 res.write('{"error": 1, "msg": "user is required!"}') 42 } else if (!pass) { 43 res.write('{"error": 1, "msg": "pass is required!"}') 44 } else if (!/^w{3,32}$/.test(user)) { 45 res.write('{"error": 1, "msg": "invalid username!"}') 46 } else if (!/^w{6,32}$/.test(pass)) { 47 res.write('{"error": 1, "msg": "invalid password!"}') 48 } else if (/^['"|]$/.test(pass)) { 49 res.write('{"error": 1, "msg": "invalid password!"}') 50 } else if (users[user]) { 51 res.write('{"error": 1, "msg": "username already exists!"}') 52 } else { 53 users[user] = pass 54 res.write('{"error": 0, "msg": "register success!"}') 55 } 56 57 res.end() 58 break 59 // 登陆 60 case '/login': 61 if (!user) { 62 res.write('{"error": 1, "msg": "user is required!"}') 63 } else if (!pass) { 64 res.write('{"error": 1, "msg": "pass is required!"}') 65 } else if (!users[user]) { 66 res.write('{"error": 1, "msg": "no this user!"}') 67 } else if (users[user]!==pass) { 68 res.write('{"error": 1, "msg": "username or password is incorrect!"}') 69 } else { 70 res.write('{"error": 0, "msg": "login success!"}') 71 } 72 73 res.end() 74 break 75 default: 76 fs.readFile(`www${pathname}`, function (err, data) { 77 if (err) { 78 res.writeHead(404) 79 res.write("Not Found!") 80 } else { 81 res.write(data) 82 } 83 84 res.end() 85 }) 86 } 87 88 }); 89 90 }); 91 92 server.listen(8080);
4.文件上传实例 - 用流实现