一、 ECMAScript 6语法
参考见 https://es6.ruanyifeng.com/
1 ECMAScript和JavaScript的关系
浏览器在1996年之前不支持脚本语言,只支持脚本语言html和css。
NetScape网景公司提出了前端编程语言java以热度命名JavaScript。并且经过与Sun经公司授权,网景公司将JavaScript注册为商标。
1996年,网景公司将脚本提交给标准委员会ECMA。1997年,ECMA将其命名为ECMAScript。因此ECMAScript是国际标准,JavaScript实现标准。
2 ES6与ES5
2011年,ECMAScript5.1.发布。从以后,新标准更新非常频繁,后来统称为ES6。
支持当前主流浏览器ES5,但是对ES6.支持不全面。
Bable是一个ES6转码器,可以ES6代码转为ES在旧浏览器中执行5代码。
Bable配置文件时.babelrc,存储在项目的根目录下。配置文件的基本规则是
{ "presets": [], "plugins": [] }
3 let和const
js变量的定义var,var变量增加现象存在,即var声明前可使用变量。
let没有变量增加。建议使用。let和const。const表示常量,let表示变量。
var1 var var1 = 100; //关键词var定义的变量是全局的 // var2 // 因为var2没有定义,所以报错 let var2 = 200; ///在这里使用let定义变量var2 const name = '张三' // name = '里斯' //使用const定义的常量不能重写赋值
4 块级作用域
es五个支持全球作用域和函数作用域,es6.块级作用域增加。
var a = 19; { var a = 20; } console.log('输出a=', a) //输出a=20 let b=10; { let b=30; } console.log('输出b=', b) //输出b=10 ///块级作用域的范围仅限于{...}之内
5 解构赋值
使用符号[]或{}类似于多重赋值。
let a,b = [1,2] //定义变量a,没有赋值;定义变量b,赋值了 console.log('a=', a, 'b=',b) //a= undefined b= [ 1, 2 ] let [c,d] = [1,2] //解构,使用[] console.log('c=', c, 'd=',d) //c= 1 d= 2 // 对象的结构,使用{} let {name, age} = {'name':'wuchao', 'age':23} console.log('name=', name, 'age=',age) let {id, ...other} = {'id':10, 'name':'wuchao', 'age':23, 'address北京市, 'school':'ucas'} console.log('id=', id, 'other=',other) //id= 10 other= { name: 'wuchao', age: 23, address: 北京市, school: 'ucas' } // 提取json数据 let jsonData = {id:42, name:'吴超'} let {id, name} = jsonData
对象的解构赋值可以很容易地将现有对象的方法赋值到某个变量。node中很常用。
// 把Math赋值的三种解构方法 let {log, sin, cos} = Math; // 把console类中的log方法解构赋值; const {log} = console; log('hello') // 下面是输入模块的指定方法 const {SourceMapConsumer, SourceNode} = require('source-map')
6 扩展字符串
6.1 es6支持unicode
比如“\u0061”表示”a“。
6.2 遍历字符串
// 遍历字符串 for(let i of 'foo'){ console.log(i) }
6.2 模板字符串
let name ='张三' , age =23 let s1 = 我的名字叫''' name 年龄是‘,年龄是’ age // 字符串拼接 console.log('s1=', s1) let s2 = 我的名字叫${name},年龄是${age}' // 这里使用单引号,变量无法分析 console.log('s2=', s2) let s3 = `我的名字叫${name},年龄是${age}` // 使用浮号可以分析变量 console.log('s3=', s3)
6.3 标签模板
标签模板相当于函数调用。
alert`hello` 等同于 alert(['hello'])
6.4 新增方法
let s = 'Hello world!' s.includes('o') //true s.startsWith('Hello') //true s.endsWith('!') //true 'x'.repeat(3) //'xxx' 'x'.padStart(5, 'ab') //'ababx' 'x'.padEnd(5, 'ab') //'xabab' ' abc '.trim() //'abc'
7 函数的扩展
7.1 参数的默认值
es默认值不能用于之前的函数参数。
function log(x, y='world'){...}
7.2 结合解构赋值默认值
// 定义 function foo({x, y=5}){...} // 调用 foo({}) //undefined 5 foo({x:1}) //1 5 foo({x:1, y:2}) // 1 2 foo() // TypeError // 定义 function fetch(url, {body='', method='GET', headers={}}){...} // 调用 fetch('http://www.baidu.com', {}) //GET fetch('http://www.baidu.com') //报错
7.3 rest参数
es6引入rest参数(形式为 …用于获取函数的多余参数的变量名)。rest参数匹配的变量是数组。
function add(...values) add(1,2,3)
7.4 箭头函数
// 有名函数 function f1(a, b){return a b;} // 箭头函数 (a, b)=>{ console.log(进入箭头函数, ) return a b } // 简洁写法 (a, b)=>a b // 常用作匿名函数 [1,2,3].map(x=>x*x) // 箭头函数体内this对象是定义对象,而不是使用对象 function foo(){ setTimeout(()=>{ console.log(this.id) }, 100) } var id=21 foo.call({id:42}) // 42
8 数组扩展
9 对象的扩展
ES作为对象的属性和方法,允许在大括号中直接写入变量和函数。
9.1 属性简写
const name = "吴超" const baz = {name} // {name:"吴超"}
在上述代码中,变量foo直接写在大括号中。这时,属性名就是变量名,属性值就是变量值。
function f1(name, age){ return {name, age} } f1("张三", 23) // Object {nae:'张三', age:23}
9.2 方法简写
以前的写法是
{
f1:function(){return "f1"}
}
新的写法是
{
f1(){return "f1"}
}
9.3 对象的定义
现在,使用属性、方法的简写,就可以非常方便的定义一个对象
var id = 1
const person = {
id,
name:'张三',
say(msg){
return 'hello '+msg
}
}
9.4 链判断
如果对象嵌套对象,读取最内层对象的属性msg.body.user.name
,如果一个对象不存在,就报错。
使用链判断运算符?.
,不存在,则返回undefined,最好指定默认值。
// 对象判断链
msg?.body?.user?.name|| 'default'
// 数组判断链
obj?.[1]
// 函数判断链
a?.b()
10、Symbol
11 Set和Map数据结构
12 Proxy
13 Promise对象
14 迭代器和for…of循环
二、 Node语法
1 引言
1.1 nodejs是js的运行环境
nodejs是一个基于chrome v8引擎的js运行环境。
nodejs是让js运行在服务端。nodejs是服务端的开发框架。 bs/cs
1.2 特点
事件驱动、异步处理、非阻塞、高并发、单进程单线程
“拉”模式
“推”模式
1.3 组成
v8、libuv(event loop)、js库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dDfSr8Qz-1608279639345)(vuejs学习笔记.assets/u=2398826131,3965435416&fm=26&gp=0.jpg)]
1.4 安装
略
不建议装到有中文有空格的路径下。
vs code
1.5 基本命令
.exit 退出命令行的方法
2 全局对象
global对象,等价于浏览器中的windows对象,是全局的。
2.1 全局变量
console.log('文件名', __filename)// 文件名 F:\学佳澳\高校实训20-24日\代码\day0106_global全局变量.js
console.log('目录位置', __dirname) //目录位置 F:\学佳澳\高校实训20-24日\代码
2.2 全局函数
setTimeout(cb,ms)只调用一次函数
t = setInterval(cb, ms) 间隔指定时间,周期性的执行函数。
clearTimeout(t) 停止前面的周期调用
// 只执行一次函数调用,5000表示5秒钟后执行这个函数
setTimeout(()=>console.log('只输出一次', ), 5000)
// 周期性执行某个函数,时间单位是毫秒,1000表示1秒钟
// let t = setInterval(()=>console.log('输出我', ), 1000)
// clearInterval(t) // 停止前面的周期性调用
2.3 console对象
是用的最频繁的一个node对象
// 日志输出级别, fatal
// console.log(, )
// console.error(, )
// console.info(, )
// 分组显示日志
console.group('aaaa')
console.log('Runoob,这个在分组里面。')
console.groupEnd()
function f2(a,b){return a+b;}
// 显示函数的调用环境
console.trace('函数调用', f2(1,2))
// 显示一个对象的所有信息,可以与console.log(...)的输出信息,做比较
console.dir({'name':'zhangsan', 'age':23})
function f1(a,b){return a+b;}
//统计函数的执行时间
console.time('flag1')// 记录一个开始时间
f1(1,2) //函数整个执行过程
console.timeEnd('flag1')// 记录一个结束时间
2.4 process对象
获取操作系统的进程信息,与操作系统交互
console.log('进程号', process.pid)
console.log('进程名', process.title)
console.log('CPU架构', process.arch)
console.log('操作系统', process.platform)
console.log('当前的工作目录', process.cwd())
// process.chdir() 切换工作目录
// process.kill() 杀死进程
// process.abort() 终止进程
3 异步回调
nodejs是异步执行,异步是通过什么体现的哪?通过回调函数。
但是,通常理解,回调不一定是异步。
//前面讲的定时器就是异步
setTimeout(()=>console.log('异步执行', ), 1000)
console.log('程序结束', )
讲一下nodejs中的同步和异步操作
let fs = require('fs')
// 同步操作
// const data = fs.readFileSync('day0101_let和const.js')
// console.log('输出结果', data.toString())
// 异步操作
const data = fs.readFile('day0101_let和const.js', (err, data)=>{
if(err){console.log('出错了', err);return;}
console.log('输出结果', data.toString())
})
console.log('--------------------------------------', )
4 事件循环
设计上应该是借鉴了操作系统的思路。
事件机制中,由三部分组成:事件、事件发出、事件接收处理
nodejs负责接收事件、处理事件
// 引入events事件模块
const events = require('events')
// 创建类EventEmitter的对象
var eventEmitter = new events.EventEmitter()
// 事件机制中,由三部分组成:事件、事件发出、事件接收处理
// nodejs负责接收事件、处理事件
// 我们可以发出事件、自定义处理事件的方法、注册事件处理方法
eventEmitter.on('event1', ()=>{
console.log('处理事件', )
});
// 触发事件
eventEmitter.emit('event1');
事件机制,可以让行为和操作相分离,也属于解耦的一种方式。
5 模块系统
编程语言自身提供一些开发包/api,更复杂的更多的功能需要模块提供。
一般一个js文件,就可以作为一个模块。模块间可以互相调用。
关键词:exports、module.exports、require
- require:是node和es6都支持的;
- export/import:只有es6支持,node不支持
- exports/module.exports:只有node支持,es6不支持
简单理解:exports = module.exports = {}
下面是模块文件modue01.js
// module.exports = name = '吴超' // 导出变量
// module.exports = add = (x,y)=>x+y // 导出函数
class Student{
constructor(id, name){
this.id = id;
this.name = name;
}
}
// module.exports = stu = new Student(12, '张三') // 导出对象
下面是调用部分
const aaa = require('./module01')
console.log('输出', aaa)
// console.log('姓名', name)
// console.log('执行函数', add(1,2))
// console.log('学生', stu)
三、 npm
npm(node package manager)是nodejs的 包管理工具,用于node插件的管理(包括安装、卸载、管理依赖等)。
npm是随同nodejs一起安装的管理工具。
java领域有类似的是maven,linux领域有类似的yum、rpm
因为nodejs做的很小巧、高内聚。为了丰富功能,通过package的形式扩展。
使用npm下载模块的时候,自动下载依赖的模块和对应的版本。
1 初始化项目
通过初始化项目,可以创建一个package.json文件。
npm init 项目名称
在package.json里面记录了项目依赖的模块。当把项目给别人时,别人执行npm install即可把依赖项安装,非常方便。
2 安装模块
需要知道模块名称即可。
npm install xxx
在国外有一个nodejs的模块仓库,免费的,谁都可以下载。当我们执行install的时候,就去这个仓库寻找特定的模块和指定的版本。下载到本地。
下载到本地项目的node_modules文件夹中,同时注册到package-lock.json文件中。
使用参数-g可以安装到全局。
npm root -g //可以查看全局模块库的位置
npm install xxx -g
npm uninstall xxx //卸载模块
3 package.json和package-lock.json
npm5之前,不会生成pacakge-lock.json文件。
package.json文件锁定的是大版本,不关心小版本。
package-lock.json文件锁定小版本。
4 运行任务
使用npm可以运行package.json中的任务
{
"scripts":{
"task1":"node -v"
}
}
在命令行运行
npm run task1
npm start和是npm run start的简写形式。
在一个npm管理项目中,一般默认有start的定义,且会经常使用,所以就在npm执行中简化输入目的设置了npm run start的简写,类似的还有npm stop、npm test等等。而其他的一些不太通用的命令项则只能通过npm run <命令项>的形式执行。
5 使用国内的npm源
使用淘宝镜像
npm i cnpm -g --registry=https://registry.npm.taobao.org
6 使用npm源的切换工具nrm
npm i nrm -g // 安装nrm
nrm ls //查看有哪些源仓库
nrm use taobao //指定使用哪个源仓库
四、 Node常用模块
1、文件系统fs
api中由同步和异步两个版本,建议使用异步版本。
const fs = require('fs')
1.1 获取文件信息
比如可以判断是文件还是文件夹?还可以知道文件大小、创建者、创建时间、权限等等。
// 访问文件信息,在进行文件夹遍历的时候,需要查看类型和基本信息
fs.stat('module01.js', (err, data)=>{
console.log('类型', data.isFile()?'文件':'文件夹')
console.log('文件信息', data)
})
1.2 读文件
读文件的时候,回调函数中,首先要进行错误判断,如果有错误,输出错误原因后返回;
fs.readFile('module01.jsasfd', (err, data)=>{
if(err){
console.error('出错了', '文件不存在')
return
}
console.log('文件内容', data.toString())
})
1.3 写文件
// 每次新创建一个文件。如果已经存在,则先删除再创建
fs.writeFile('a.txt', '我的名字叫吴超,年龄23',()=>{})
// 追加
// fs.writeFile('a.txt', '我的名字叫吴超,年龄23',{flag:'a'}, ()=>{})
2、Buffer类
在js中,只有字符串类型,没有二进制类型。
如果要读写图片、音视频文件的时候,就需要使用Buffer类。
学习api的时候,把Buffer想象成String类。
//方法1:创建指定大小的字节空间,指定填充的值,默认是0
let bf1 = Buffer.alloc(10, 1)
console.log('bf1= ', bf1)
// 方法2:根据字节数组创建
let bf2 = Buffer.from([1,2,3,4,5])
console.log('bf2=', bf2)
// 方法3:把字符串转为Buffer
let bf3 = Buffer.from('hello world我的祖国')
console.log('bf3 = ', bf3)
// 输出,指定输出格式
console.log('bf1 = ', bf1.toString('hex'))
// 输出字符串的时候,可以指定编码格式
console.log('bf3 = ', bf3.toString('utf-8'))
3、Stream类
Stream类是个抽象接口,有四种类型:Readable、Writable、Duplex、Transform类型。
所有的Stream对象都是EventEmitter的实例。
3.1 读流
const fs = require('fs')
const readStream = fs.createReadStream('module01.js')
// 注册事件
readStream.on('data', (chunk)=>{
console.log('有数据了', chunk.toString())
})
// 注册事件
readStream.on('end', ()=>{
console.log('读取结束')
})
// 注册事件
readStream.on('error', (error)=>{
console.error('出错了', error)
})
3.2 写流
const fs = require('fs')
const writeStream = fs.createWriteStream('b.txt')
writeStream.on('finish', ()=>console.log('写完了'))
writeStream.write('hello world我的祖国')
// 调用end方法时,触发finish事件
writeStream.end('完成写入数据');
3.3 压缩解压缩
使用第三方库compressing,支持windows和linux。
先安装库 npm install compressing
const compressing = require('compressing')
// 压缩文件夹
// compressing.zip.compressDir('F:/DRMsoft', "DRMsoft.zip")
// compressing.zip.compressFile()
// 解压缩
compressing.zip.uncompress("DRMsoft.zip", __dirname)
4、案例:复制文件夹
知识点:路径的遍历、判断文件类型、判断文件是否存在、创建文件夹、数据复制
const fs = require('fs')
// 复制工具
var copyTool = function(src, dst, callback){
if(! fs.existsSync(src)){//src不存在
console.error('错误:', '文件不存在')
return;
}
fs.exists(dst, (isExist)=>{
// if(isExist){ //存在目标文件夹
// callback(src, dst)
// }else{ //不存在目标文件夹
// fs.mkdir(dst, ()=>callback(src, dst))
// }
if(! isExist){
fs.mkdirSync(dst)
}
callback(src, dst)
})
};
var copy = function(src, dst){
fs.readdir(src, (err, names)=>{ // 读取文件夹
// console.log('读取的内容', names)
names.forEach( name=>{
// console.log('读取的名称', name)
let _src = src+'/'+name
let _dst = dst+'/'+name
fs.stat(_src, (err, st)=>{
// console.log('读取的名称', _src, st.isFile()?'文件':'文件夹')
if(st.isFile()){ //如果碰到是文件,下面完成的时是复制功能
// fs.createReadStream(_src).pipe(fs.createWriteStream(_dst))
let _readStream = fs.createReadStream(_src)
let _writeStream = fs.createWriteStream(_dst)
//管道
_readStream.pipe(_writeStream)
}else if(st.isDirectory()){
copyTool(_src, _dst, copy)
}
})
} )
});
}
copyTool('F:/DRMsoft', __dirname+'/aaa', copy)
5、GET请求
网络请求,知道请求路径是什么,请求参数是什么,可以响应结果。
http://localhost:3000/hello/?name=wuchao&age=23
5.1 引入的模块
const http = require('http')
const url = require('url')
const util = require('util')
5.2 解析请求路径和请求参数
http.createServer((req, res)=>{
let method = req.method //值常见的有GET\POST
console.log('请求方法', method)
// 这里的第二个参数必须是true
let _url = url.parse(req.url, parseQueryString=true)
console.log('请求路径', _url.pathname)
console.log('请求参数是', _url.query.name, _url.query.age)
// res.end(util.inspect(_url))
}).listen(3000, console.log('服务器启动了'))
5.3 返回html页面
const http = require('http')
htmlcode1 = `
<form action="http://localhost:3000/asadfadsf" method="GET">
<input type="text" name="name" value="zhagnsan">
<input type="submit" value="提交">
</form>
`
http.createServer((req, res)=>{
// 必须指定响应头,浏览器才能解析html
res.writeHead(500, {'Content-Type':'text/html; charset=utf8'})
// 调用write或者end写应答
res.end(htmlcode1)
}).listen(3000)
6、POST请求
POST请求是Stream,基于事件的。
解析POST请求的数据,使用querystring模块中的parse方法。
const http = require('http')
const querystring = require('querystring')
let form_html = `
<form action="http://localhost:3000/save" method="post">
姓名:<input type="text" name="name" id=""><br>
年龄:<input type="text" name="age" id=""><br>
<button type="submit">
保存
</button>
</form>
`
http.createServer((req, res)=>{
let data = ''; //data保存post中的数据
req.on('data', chunk=>{
data+=chunk;
// console.log('到来的数据', chunk.toString())
})
req.on('end',()=>{
// 解析post数据
let _data = querystring.parse(data)
console.log('解析post数据', _data)
})
res.writeHead(200, {'Content-Type':'text/html; charset=utf8'})
res.end(form_html)
}).listen(3000, console.log('服务器启动了', ))
7、重定向
为了解决重复提交,使用重定向。
在应答中使用302状态码,指定新的访问地址
res.writeHead(302, {'Location':'http://localhost:3000/'})
下面是完整的代码
const http = require('http')
const querystring = require('querystring')
let form_html = `
<form action="http://localhost:3000/save" method="post">
姓名:<input type="text" name="name" id=""><br>
年龄:<input type="text" name="age" id=""><br>
<button type="submit">
保存
</button>
</form>
`
http.createServer((req, res)=>{
if('GET'==req.method){
res.writeHead(200, {'Content-Type':'text/html; charset=utf8'})
res.end(form_html)
}else if('POST'==req.method){
let data = ''; //data保存post中的数据
req.on('data', chunk=>{
data+=chunk;
// console.log('到来的数据', chunk.toString())
})
req.on('end',()=>{
// 解析post数据
let _data = querystring.parse(data)
console.log('解析post数据', _data)
res.writeHead(302, {'Location':'http://localhost:3000/'})
res.end()
return
})
}
}).listen(3000, console.log('服务器启动了', ))
8、读写json文件
// 把string转为json形式,把json形式转为string
//把json形式转为string
// console.log('输出字符串', typeof JSON.stringify({'name':'wuchao', 'age':23}))
// 把string转为json形式
// console.log('输出json格式', typeof JSON.parse('{"name":"wuchao","age":23}'))
const fs = require('fs')
// 读json文件
fs.readFile('data.json', (err, data)=>{
let jsondata = JSON.parse(data.toString())
console.log('输出文件中的内容', jsondata, jsondata.name, jsondata.age)
})
9、案例:学生信息管理系统
用http、fs、url、querystring等模块,做一个学生信息管理系统,
【1】用户通过get访问首页,服务器从json文件中加载数据,返回学生列表;
【2】用户在页面中点击“添加“, 发送get请求,服务器返回需要填写信息的表单;
【3】用户填写表单,点击提交,发送post请求到服务器,服务器解析数据保存到磁盘文件;重定向到学生列表;
const fs = require('fs')
const http = require('http')
const url = require('url')
const querystring = require('querystring')
table_html = `
<table border="1px solid red" style="width:500px">
<thead>
<tr><th>姓名</th><th>年龄</th></tr>
</thead>
<tbody>
<tr><td>aaaa</td><td>aaaa</td></tr>
<tr><td>aaaa</td><td>aaaa</td></tr>
</tbody>
</table>
<a href="http://localhost:3000/toadd">添加</a>
`
form_html = `
<form action="http://localhost:3000/save" method="post">
姓名:<input type="text" name="name" id=""><br>
年龄:<input type="text" name="age" id=""><br>
<button type="submit">
保存
</button>
</form>
`
function readJSON(){
let data = fs.readFileSync(__dirname+'/data.json').toString()
data = JSON.parse(data)
console.log('读取到的JSON数据', data)
return data
}
function fillTable(jsondata){
head = `<table border="1px solid red" style="width:500px">
<thead>
<tr><th>姓名</th><th>年龄</th></tr>
</thead>
<tbody>`
jsondata.forEach((stu, index)=>{
//let row = '<tr><td>'+stu.name+'</td><td>'+stu.age+'</td></tr>'
let row = `<tr><td>${stu.name}</td><td>${stu.age}</td></tr>`
//console.log('第'+index+'行', row)
head += row
})
tail = `</tbody>
</table>
<a href="http://localhost:3000/toadd">添加</a>`
return head+tail
}
function writeJSON(stu){
let data = readJSON()
data.push(stu)
fs.writeFileSync(__dirname+'/data.json', JSON.stringify(data))
}
function parsePOST(req, writeJSON){
let data = ''
req.on('data', (chunk)=>{data+=chunk})
req.on('end', ()=>{
data = querystring.parse(data)
// console.log('post参数', data.name, data.age)
writeJSON(data)
})
}
function route(req, res){
let method = req.method
let pathname = url.parse(req.url).pathname
switch(pathname){
case '/':
table = fillTable(readJSON())
res.end(table)
break;
case '/toadd':
res.end(form_html)
break;
case '/save':
parsePOST(req, writeJSON)
res.writeHead(302, {'Location':'http://localhost:3000/'})
res.end()
break;
default:
res.end('错误的请求路径 '+pathname)
}
}
function init_app(){
if(! fs.existsSync(__dirname+'/data.json')){
fs.writeFileSync(__dirname+'/data.json', '[]')
}
}
init_app()
http.createServer((req, res)=>{
res.writeHead(200, {'Content-Type':'text/html; charset=utf8'})
route(req, res)
}).listen(3000, console.log('启动服务器,监听3000端口.....'))
五、 express框架
express是基于nodejs的web框架。
1 极简入门
可以在修改代码的时候,重启应用nodemon xxx.js
//加载模块
const express = require('express')
// 创建web服务器
const app = express()
// 启动服务器
app.listen(3000, console.log('服务器启动了,监听3000端口' ))
2 返回各种类型的应答
// 响应get请求
app.get('/', (req, res)=>{
// 输出文本
// res.send('hello express')
// 输出json
// res.send({'name':'wuchao', 'age':23})
// 输出html文件,使用绝对路径
// res.sendFile(__dirname+'/index.html')
// 渲染模板
// res.render(...)
})
3 解析GET请求
对于普通的get请求,如http://localhost:3000/?name=wuchao&age=23,使用req.query 获得get请求信息。
对于url路径中含有数值的,即有名路径,使用req.params获得
// 普通路径
app.get('/', (req, res)=>{
res.send(res.query.name)
})
// 有名路径
app.get('/user/:id', (req, res)=>{
res.send(req.params.id)
})
4 解析POST请求参数
引入第三方模块body-parser,然后使用req.body解析参数。
// 引入模块
const bodyparser = require('body-parser')
// 使用中间件,处理application/x-www-form-urlencoded
app.use(bodyparser.urlencoded({extended:false}))
app.get('/', (req, res)=>{
// 获取GET请求参数
console.log('GET请求参数', req.query)
res.sendFile(__dirname+"/pages/index.html")
})
app.post('/save', (req, res)=>{
// 获取POST请求参数
console.log('POST请求参数', req.body)
})
5 解析json参数
app.use(express.json())
6 路由
当请求路径特别多的时候,我们需要对每一个请求单独做出响应,这时候就会产生大量的代码,堆积在一起。使用路由,可以让请求的路径结构更加清晰,同时也可以分模块处理。
思路:对url进行分级,分为多个大的模块,每个模块下面分为好多请求
/stu/list /stu/add /stu/save /stu/delete
定义一个路由模块stu.js
const express = require('express')
// 定义了一个一级路由
const stu = express.Router()
// 定义二级路由
stu.get('/list', (req, res)=>{
console.log('/stu/list', )
res.send('/stu/list')
})
stu.get('/add', (req, res)=>{res.send('/stu/add')})
module.exports=stu
在服务器模块中,使用路由模块
const express = require('express')
const app = express()
const stu = require('./stu')
// 告诉服务器,使用一级路径/stu
app.use('/stu', stu)
app.listen(3000, console.log('the server is running....', ))
7 模板引擎art-template
常用的操作:输出、判断、循环
art-template模板既可以用在前端js,也可以用在后端js。
7.1 使用模板
安装模板
npm install art-template express-art-template
加载中间件
// 使用中间件,使用express-art-template模板
// 模板文件后缀是html
app.engine('html', require('express-art-template'))
// 模板文件默认存放在views目录下,也可以修改目录
app.set('views', 目录路径)
// 如果输出页面时使用res.render(__dirname+'...') 这种绝对路径,那么关于views的设置无效
输出模板文件的时候,必须使用res.render方法,否则不会渲染模板文件中的语法。
res.render(__dirname + "/pages/index.html", {'stulist': stulist});
7.2 模板语法
if是用于判断的。
{
{if msg}}
{
{msg}}
{
{/if}}
each是循环数组stu,stu里面的每一个元素是一个json,在循环体内使用$value表示每一个循环遍历,使用 ¥ index表示循环序号。
{
{each stu}}
<tr><td>{
{$value.name}}</td><td>{
{$value.age}}</td></tr>
{
{/each}}
7.3 模板继承
模板继承允许构建 一个模板的骨架,然后向里面填充组成部分。
基本语法如下,在骨架模板中适宜{ {block}}作为占位符,在继承模板中使用{ {block}}定义具体的内容。
{
{extend './layout.art'}}
{
{block 'head'}} ... {
{/block}}
下面是骨架模板
<!--layout.art-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{
{block 'title'}}My Site{
{/block}}</title>
{
{block 'head'}}
<link rel="stylesheet" href="main.css">
{
{/block}}
</head>
<body>
{
{block 'content'}}{
{/block}}
</body>
</html>
下面是继承后的模板
<!--index.art-->
{
{extend './layout.art'}}
{
{block 'title'}}{
{title}}{
{/block}}
{
{block 'head'}}
<link rel="stylesheet" href="custom.css">
{
{/block}}
{
{block 'content'}}
<p>This is just an awesome page.</p>
{
{/block}}
7.4 包含模板
把某一个公共部分,加入到当前页面中来。
{
{include './header.art'}}
{
{include './header.art' data}}
7.5 过滤器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UR6Mfeq5-1608279639349)(vuejs学习笔记.assets/1281517-20180205155942998-197158086.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5SpHacp-1608279639352)(…/…/…/_学习笔记/前端笔记/nodejs/1281517-20180205160104326-1983829336.png)]
8 会话管理
使用第三方模块express-session来管理会话。
session是另一种记录客户状态的机制,与cookie保存在客户端浏览器不同,session保存在服务器当中;
当客户端访问服务器时,服务器会生成一个session对象,对象中保存的是key:value值,同时服务器会将key传回给客户端的cookie当中;
当用户第二次访问服务器时,就会把cookie当中的key传回到服务器中,最后服务器会吧value值返回给客户端。
因此上面的key则是全局唯一的标识,客户端和服务端依靠这个全局唯一的标识来访问会话信息数据。
接下来,安装模块,并使用中间件
// npm install express-session // 安装模块
// 导入模块
const session = require('express-session')
// 配置中间件
app.use(session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: ('name', 'value',{maxAge: 5*60*1000,
secure: false})
}))
接下来就可以使用req.session了
// 赋值
req.session.user = '吴超'
// 取值
req.session.user
// 销毁会话
req.session.destroy((err)=>{res.send('销毁')})
会话的配置项有
- name - cookie的名字(原属性名为 key)。(默认:’connect.sid’)
- store - session 的存储方式,默认存放在内存中,也可以使用 redis,mongodb 等。express 生态中都有相应模块的支持
- secret - 通过设置的 secret 字符串,来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改
- cookie - session cookie设置 (默认:{ path: ‘/‘, httpOnly: true,secure: false, maxAge: null })
- genid - 生成新session ID的函数 (默认使用uid2库)
- rolling - 在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
- resave - 强制保存session即使它并没有变化 (默认: true, 建议设为:false)
- proxy - 当设置了secure cookies(通过”x-forwarded-proto” header )时信任反向代理。当设定为true时, ”x-forwarded-proto” header 将被使用。当设定为false时,所有headers将被忽略。当该属性没有被设定时,将使用Express的trust proxy。
- saveUninitialized - 强制将未初始化的session存储。当新建了一个session且未设定属性或值时,它就处于未初始化状态。在设定一个cookie前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的。(默认:true)
- unset - 控制req.session是否取消(例如通过 delete,或者将它的值设置为null)。这可以使session保持存储状态但忽略修改或删除的请求(默认:keep)
9 中间件完成权限控制
使用中间件完成权限控制。
app.use((req, res, next)=>{
console.log(req.url, )
if(没有权限){
res.redirect('/') //重定向
return;
}
next() // 放行
})
10 数据范围
数据的范围可以是res、session或者app。指定数据范围的目的是为了向页面传值,以及在不同的express方法之间传值。
应用级别使用app.locals,会保存在应用的整个生命周期;
响应级别使用res.locals,会保存在本次请求应答的生命周期中;
app.locals
res.locals.session = req.session
11 上传
使用第三方模块multer
首先看一下页面的结构
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="content" />
<input type="submit" value="上传文件" />
</form>
接下来看一下js部分的代码
// 安装模块
// npm install multer
// 导入模块
var multer = require('multer')
// 创建multer实例,指定存放文件的位置
var upload = multer({dest: 'upload_tmp/'});
// 以下是接收上传信息,upload.any()是个能够接收任何上传的中间件,
app.post('/upload', upload.any(), (req, res)=>{
//req.files接收所有的文件,这是个数组。里面的文件有个属性path表示上传后的路径,可以复制到其他位置
console.log('上传文件信息', req.files)
})
接下来是完整的代码
var fs = require('fs');
var express = require('express');
var multer = require('multer');
var router = express.Router();
// 指定存放文件的位置
var upload = multer({dest: 'upload_tmp/'});
router.post('/', upload.any(), function(req, res, next) {
console.log(req.files[0]); // 上传的文件信息
var des_file = "./upload/" + req.files[0].originalname;
fs.readFile( req.files[0].path, function (err, data) {
fs.writeFile(des_file, data, function (err) {
if( err ){
console.log( err );
}else{
response = {
message:'File uploaded successfully',
filename:req.files[0].originalname
};
console.log( response );
res.end( JSON.stringify( response ) );
}
});
});
});
module.exports = router;
multer的配置有:
- dest或storage 存储文件的位置
- fileFilter 文件过滤器
- limits 限制上传的数据
- preservePath 保存包含文件名的完整文件路径
12 文件下载
下载文件只需要使用==res.download(文件路径,下载文件名,回调函数)==即可
app.get('/download',function (req,res) {
res.download(__dirname+'/route.js','route.js',
function (err) {
if(err){
console.log(err)
}
})
})
14 验证码
使用第三方模块svg-captcha
// npm install svg-captcha // 安装模块
// 导入模块
const svgCaptcha = require('svg-captcha')
router.get('/static/captcha', (req, res)=>{
let captch = svgCaptcha.create({
color: true, // 彩色
width:100, // 宽度
height:60, // 高度
fontSize:48, // 字体大小
size:4, // 验证码个数,这里的captch.text是4个字母
noise:3, // 干扰线条
ignoreChars:'0o1ilI' // 不包括的字符
});
// 保存到session中,登录时取出校验
req.session.captcha = captch.text.toLocaleLowerCase();
// 防止使用模板:指定输出类型
res.type('html')
// 这里的captch.data 是一个svg的html内容。指定输出内容
res.end(captch.data)
});
如果要在页面上输出
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script>
var captcha = function(){
$.get('http://localhost:3000/captcha', data=>{
$('#captchaImg').html(data)
})
}
$(function(){
captcha()
$('#captchaImg').click(()=>{
captcha()
})
});
</script>
// 假设页面有下面这个span
<form action="/login" method="post">
用户名:<input type="text" name="username" id="username" value="张三">
<br/>
密码:<input type="password" name="password" id="password" value="admin">
<br/>
验证码:<input type="text" name="captchaCode" id="captchaCode"><span id="captchaImg"></span>
<br>
<input type="submit" value="登录">
</form>
15 响应静态文件
所谓的静态文件,指的是css、js、各种图片。
静态文件通常放在一个统一的文件夹中,比如有如下的目录结构
/static/js/index.js
那么,我们使用 app.use(express.static(‘static’)) 表示static目录。相当于隐藏了指定的目录。
这样,在地址栏就可以使用http://localhost:3000/js/index.js 就可以访问到该文件。在页面中引用该js的话,也应该使用 “/js/index.js” 的形式。
还可以使用app.use(‘/public’, express.static(‘static’)) 表示static目录。注意前面的public必须使用**/**开始。
在地址栏就需要使用http://localhost:3000/public/js/index.js 就可以访问到该文件。
16 跨域请求
安装模块cors,增加下面一行即可。
app.use(require('cors')())
17 mysql操作
使用第三方模块mysql
var mysql = require('mysql');
var pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
port: '3306',
user: 'root',
password: '',
database: 'xkdb'
});
pool.getConnection(function (err, connection) {
if (err) throw err;
var sqlStr='SELECT * FROM category';
connection.query(sqlStr, function (err, rows,fields) {
if (err) throw err;
console.log('json', {results: JSON.stringify(rows),fields:JSON.stringify(fields)});
connection.release();
});
});
18 模块
分模块操作,可以让逻辑更加清晰。
const express = require(`express`)
const users = express.Router()
users.use((req, res, next) => {
console.log(`路由执行成功啦~~~`, Date.now());
next()
})
users.get(`/`, (req, res, next) => {
res.json({
status: 200,
data: `请求成功`
})
})
module.exports = users
在主文件中
// 使用路由 /user 是路由指向名称
import users from '...'
app.use(`/users`,users)
六、 案例:学生管理系统
1、 功能列表
- 数据保存在mysql中
- 首页是学生列表;
- 没有权限时,只能看到学生列表,看不到修改、删除、添加
- 没有权限时,都会跳转到首页的学生列表
- 登录页面,用户名、密码、验证码;如果登录成功,跳转到首页
- 有权限时,首页显示添加、修改、删除
- 可以退出
2、 搭架子
-
创建一个文件夹stu-mis
-
进入这个文件夹,在命令行执行
npx express-generator
,生成模板项目 -
可以删除views文件夹中的所有模板文件。在命令行执行
npm install art-template express-art-template
,安装art模板引擎。 -
在命令行执行
npm install
,安装模块 -
修改app.js的内容的第14行
app.set('html', require('express-art-template'));
修改app.js的内容的第20行
app.use('/static', express.static(path.join(__dirname, 'public')));
这样,就可以使用
/static
访问静态内容了。 -
修改index.js文件,
res.render('index.html');
在views文件夹中创建index.html文件
-
在命令行执行
nodemon
,运行项目。在浏览器访问http://localhost:3000 就能看到首页面。 -
在vscode中安装 EJS language support 插件
至此,架子搭建完毕。
3、显示表格
修改index.js内容
router.get("/", function (req, res, next) {
stuList = [
{ id: 1, name: "张三", age: 23 },
{ id: 2, name: "李四", age: 24 },
];
res.render("index.html", { title: "Express", stuList: stuList });
});
修改views文件夹中index.html内容
<table>
{
{each stuList}}
<tr>
<td>{
{$value.id}}</td>
<td>{
{$value.name}}</td>
<td>{
{$value.age}}</td>
</tr>
{
{/each}}
</table>
4、访问数据库
创建数据库访问模块db.js
let mysql = require('mysql')
let pool = mysql.createPool({
host:'localhost',
port:3306,
user:'root',
password:'admin',
database:'test',
});
module.exports = function(sql, callback){
pool.getConnection(function(err, conn){
if(err){
console.error('数据库连接错误', err)
throw err;
}
conn.query(sql, function(err, result){
if(err){
console.error('执行语句错误', err)
throw err;
}
console.log('执行结果', result)
callback(result);
conn.release();
});
});
}
修改index.js中的内容
router.get("/", function (req, res, next) {
function callback(rows){
res.render("index", { title: "Express", stuList: rows });
}
db('select * from stu', callback);
});
这样,以后就可以非常方便的使用数据库了。
5、使用会话权限管理
在app.js中,使用会话中间件,这里的会话中间件,一定要位于前其他app.use(…)的前面。
// npm install express-session // 安装模块
// 导入模块
const session = require('express-session')
// 配置中间件
app.use(session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: ('name', 'value',{maxAge: 5*60*1000,
secure: false})
}));
// 配置session中间件
app.use((req, res, next)=>{
res.locals.session = req.session
next()
});
权限管理
// 使用中间件完成权限控制
app.use((req, res, next)=>{
if(req.url.startsWith('/static')){
next();
return;
}
if(req.url=='/login'){
console.log('请求/login', )
next()
return;
}
if(! req.session.user){
console.log('重定向/login', )
res.redirect('/login')
return;
}
console.log('放行', req.url)
next() //调用next()表示放行
});
以上的代码,要放在其他app.use()
之前。
创建一个login.html文件
<form action="/login" method="post">
用户名:<input type="text" name="username" id="username">
<br/>
密码:<input type="password" name="password" id="password">
<br/>
<input type="submit" value="登录">
</form>
6、使用post登录
修改app.js文件,在权限控制代码后面增加以下内容:
// 引入模块
const bodyparser = require('body-parser')
// 使用中间件,处理application/x-www-form-urlencoded
app.use(bodyparser.urlencoded({extended:false}))
接下来就可以使用req.body访问post请求的参数了。
如果登录成功,就可以使用
req.session.user = ....
设置会话。
销毁会话,使用
req.session.destroy((err)=>{console.print('销毁')})
如果已经配置了
app.use((req, res, next)=>{
res.locals.session = req.session
next()
});
那么,在页面访问会话中的内容,使用
{
{session.user}}
7、退出操作
router.get('/logout', function(req, res){
req.session.destroy((err)=>{});
res.redirect('/');
});
8、编辑操作
在页面使用
<a href="/edit?id={
{$value.id}}">修改</a>
在index.js中,增加函数
router.get('/edit', function(req, res){
let id = req.query.id;
db(`select * from stu where id=${id}`, rows=>{
res.render('edit.html', {stu:rows[0]});
});
});
保存修改的函数
router.post('/edit', function(req, res){
let id = req.body.id, name = req.body.name, age=req.body.age;
db(`update stu set name='${name}', age=${age} where id=${id}`, rows=>{
res.redirect('/')
});
});
9、添加、删除操作
参考编辑操作,略
10、上传头像
上传文件的表单,使用post提交,要有属性enctype="multipart/form-data"
。
<form action="/add" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="name" id="">
<br>
密码:<input type="text" name="pwd" id="">
<br>
年龄:<input type="text" name="age" id="">
<br>
照片:<input type="file" name="picture" id="">
<br>
<input type="submit" value="添加">
</form>
nodejs中使用multer模块,先安装npm install multer
。然后在index.js中增加以下内容
// 导入模块
var multer = require('multer')
// 创建multer实例,指定存放文件的位置
var upload = multer({dest: 'upload_tmp/'});
// 上传目录
var UPLOAD_DIR = "./upload/";
// 文件夹必须存在,否则报错
if(!fs.existsSync(UPLOAD_DIR)){
fs.mkdirSync(UPLOAD_DIR);
}
修改一下以前的添加方法
router.post('/add', upload.any(), function(req, res){
let name = req.body.name, pwd = req.body.pwd, age = req.body.age;
// 上传的文件都保存在req.files,每个文件都是一个对象。
// 对象的path是上传后的路径,名称是随机生成的。
// 同步读取文件
let data = fs.readFileSync(req.files[0].path);
// 对象的originalname是原始文件名
let dst_file = UPLOAD_DIR + req.files[0].originalname;
// 同步写入文件
fs.writeFileSync(dst_file, data);
let sql = `insert into stu(name,pwd,age,image)values('${name}', '${pwd}', ${age}, '${dst_file}')`
db(sql, rows=>{
res.redirect('/')
});
});
11、下载头像
只需要使用res.download()
即可。
router.get('/download', function(req, res){
let id = req.query.id;
db(`select * from stu where id=${id}`, (rows)=>{
res.download(rows[0].image);
});
});
12、验证码
使用svg-captcha
模块,安装npm install svg-captcha
下面是生成验证码的代码。要注意url使用/static
开头,防止权限过滤掉。
// 导入模块
const svgCaptcha = require('svg-captcha')
// 页面:获取验证码
router.get('/static/captcha', (req, res)=>{
let captch = svgCaptcha.create({
color: true, // 彩色
width:100, // 宽度
height:60, // 高度
fontSize:48, // 字体大小
size:4, // 验证码个数,这里的captch.text是4个字母
noise:3, // 干扰线条
ignoreChars:'0o1ilI' // 不包括的字符
});
// 保存到session中,登录时取出校验
req.session.captcha = captch.text.toLocaleLowerCase();
// 防止使用模板:指定输出类型
res.type('html')
// 这里的captch.data 是一个svg的html内容。指定输出内容
res.end(captch.data)
});
下面是页面中使用验证码的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script>
var captcha = function(){
$.get('/static/captcha', data=>{
$('#captchaImg').html(data)
})
}
$(function(){
captcha()
$('#captchaImg').click(()=>{
captcha()
})
});
</script>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username" id="username" value="张三">
<br/>
密码:<input type="password" name="password" id="password" value="admin">
<br/>
验证码:<input type="text" name="captchaCode" id="captchaCode"><span id="captchaImg"></span>
<br>
<input type="submit" value="登录">
</form>
</body>
</html>
登录时,进行验证
router.post('/login', (req, res)=>{
let name = req.body.username, pwd = req.body.password, captchaCode = req.body.captchaCode;
// 判断验证码
if (req.session.captcha != captchaCode.toLocaleLowerCase()){
res.render('login.html', {error:'验证码错误'});
return;
}
let sql = `select * from stu where name='${name}' and pwd='${pwd}'`
db(sql, rows=>{
if(rows){
req.session.user = rows[0]
res.redirect('/')
}else{
res.render('login.html')
}
});
});
七、Mongoose操作
使用npm install mongoose -S
,按照mongoose
7.1 创建数据库连接
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/test44', {
useNewUrlParser:true,
useCreateIndex:true,
useFindAndModify:true
}).then(()=>console.log('数据库链接正常'))
.catch(err=>console.error(`数据库链接出错 `, err))
该文件可以在main.js中引入,与模型无关。这样,一次性加载数据库连接。
7.2 创建模型
下面的模型中,使用了ObjectId类型,默认是null。
const mongoose = require("mongoose");
const Funcpoint = mongoose.model(
"Funcpoint",
new mongoose.Schema({
pid: {
type: mongoose.ObjectId,
ref: "Funcpoint",
default: null,
},
label: {
type: String,
required: true,
}
})
)
模型有很多参数,如下
const User = mongoose.model(
"User",
new mongoose.Schema({
username: {
type: String,
required: true,
unique: true // 唯一性
},
pas