内置模块-http
-
内置模块-http:专门用于创建http服务的模块
-
导入
let http = require('http') -
创建http服务
let server = http.createServer((req,res)=>{ // 任何一个请求都会经过这个函数 // req请求的信息对象 // res是响应对象 }) -
监听端口
server.linsten(端口号,回调函数)
🌰:
// 1. 导入
let http = require('http');
// 2.创建服务器
let server =http.createServer((request,response)=>{
// console.log(123);
// 1. request 是请求对象
// 1.1 request.url 是请求的地址
// console.log(request.url);
// 2. response 是响应对象
// 2.1 write() 响应数据
// response.write('hello')
// 2.2 end() 结束响应
// response.end(数据); 响应数据,并结束响应
// response.end('hello world');
// 2.3 statusCode 设置响应状态码
// 2.4 statusMessage 设置响应状态码描述
// response.statusCode = 404;
// response.statusMessage = 'not found';
// 2.5 设置响应头 content-type 告诉请求方响应的编码
// response.setHeader('content-type','text/html;charset=utf-8');
// 2.6 设置响应头 及 响应状态码 和 描述
response.writeHead(200,'very ok',{
'content-type':'text/html;charset=utf-8'
})
response.end('<h1>你好<h1>')
})
// 3. 监听端口
server.listen('8090',()=>{
console.log('服务器启动了,请打开: http://127.0.0.1:8090');
})
-
接受
get请求携带的数据🌰: // 创建一个服务器 require('http').createServer((req, res) => { // 根据请求地址和方式判断来接受 --- /page/login接受携带的数据 get let urlObj = require('url').parse(req.url,true);// 获取的请求信息解析得到对象 if(urlObj.pathname ==='/page/login' && req.method.toLowerCase()==='get'){ console.log( urlObj.query ) // get请求携带的数据 // 获取到请求携带的数据 let obj = { info:'你写的数据我们接受到了返回给在data中', data:urlObj.query, } res.end(JSON.stringify(obj)); } }).listen('8090', () => { console.log('ok------') }) -
接受
post请求携带的数据🌰: // 创建一个服务器 require('http').createServer((req, res) => { // 根据请求地址和方式判断来接受 --- /page/login接受携带的数据 post let urlObj = require('url').parse(req.url);// 获取的请求信息解析得到对象 if (urlObj.pathname === '/page/login' && req.method.toLowerCase() === 'post') { // post请求,则开始获取请求携带的数据 // 使用事件进行post请求携带数据的时候 // 通过on方法给请求绑定一个data事件 let resStr = ''; req.on('data', chunk => { // chunk 在data事件获取到的携带数据 resStr += chunk; }) // 通过on方法给请求绑定一个end事件,---end事件触发的时机就是数据传输完成 req.on('end', () => { // console.log(resStr);// 得到查询字符串 let resObj = require('querystring').parse(resStr);//转为对象 if (resObj.name === 'zs' && resObj.pwd === '123456') { res.end(JSON.stringify({ code: 1, msg: '登录成功' })) } else { res.end(JSON.stringify({ code: 0, msg: '用户名或密码错误' })) } }) } }).listen('8090', () => { console.log('ok------') })
npm工具
第三方模块是别人写好的,上传到远程服务器的
可以使用'npm'工具来下载第三方模块(包)
模块: js框架,js插件,js项目都是包---也是模块
npm除了可以下载第三方包之外,可以可帮我们管理下载的包,所以我们也把npm称之为'项目的管理器'
-
npm的安装
- 在安装nodejs的时候,自带安装好了一个**
npm** - 在命令行输入
npm -v出来版本号说明npm就安装好了
- 在安装nodejs的时候,自带安装好了一个**
-
npm初始化项目的包管理
- 命令行切换到项目根目录
- 指令:
npm init- 输入指令后会出现比较多的配置需要填写,都可以使用默认值
- 注意:
package name: 包名(不能是中文,不能有特殊符号,也不要用已有模块包的名称)
- 执行完毕后,会在当前目录中生成一个
package.json的配置文件(包管理文件) - 指令:
npm init -y- 输入这个指令执行,表示使用的都是默认值,不出现配置提示,直接生成
package.json
- 输入这个指令执行,表示使用的都是默认值,不出现配置提示,直接生成
-
npm下载包
- 指令:
npm install 包名- 默认下载的是最新版本的包
- 注意:
install可以简写为i=>npm i 包名
- 指令:
npm i 包名@版本号- 下载指定版本的包
- 比如:
npm i jquery@2下载指定版本的包中2.最新的版本
- 注意:
- 下载指令执行完毕后,下载完毕后,会在当前目录中自动创建一个
node_modules文件夹,而我们下载的包都是在这个文件夹中 - 而且我们下载的包的版本记录都会在
package.json文件的dependencies字段中记录 - 一个项目中,下载同一个包,只能保留一个版本的包,最后下载会将之前的覆盖
- 下载指令执行完毕后,下载完毕后,会在当前目录中自动创建一个
- 指令:
npm i 包名 包名 包名- 一次下载多个包
- 指令:
-
下载参数
- 指令:
npm i 包名@版本号 -D- 下载的包版本会记录在
package.json文件的devDependencies字段中 - 通过此方式下载的包,一般都是在项目开发阶段需要使用的包文件
- 下载的包版本会记录在
- 指令:
npm i 包名@版本号 -s(默认)- 下载的包版本会记录在
package.json文件的dependencies字段中 - 通过此方式下载的包,一般都是在项目上线运行阶段需要使用的包文件
- 下载的包版本会记录在
- 指令:
npm i 包名@版本号 -g- 下载的包版本不会记录在
package.json文件中 - 通过此方式下载的包,下载到电脑node的全局中,一般都是下载全局工具使用
- 下载的包版本不会记录在
- 指令:
-
npm查看所有可以下载包的版本
- 指令:
npm view 包名 versions
- 指令:
-
npm卸载包
- 指令:
npm uninstall 包名uninstall可以简写为un=>npm un 包名
- 指令:
-
npm清除缓存
- 指令:
npm cache clean -f
- 指令:
-
项目复活
- 指令:
npm i- 指令执行: 会将
package.json中记录的所有包都重新下载一次
- 指令执行: 会将
- 指令:
-
查看已下载的包
- 指令:
npm list- 简写:
npm ls - 查看已下载到项目中的包
- 简写:
- 指令:
npm list -g- 查看已下载到全局中的包
- 指令:
nrm工具
- nrm:npm 镜像源管理工具
- nrm的作用: 可以管理切换npm工具下载包 的镜像源地址
- 第三方包: 别人写好的,上传到远程地址,这个远程地址就是 镜像源地址
- npm 下载的包在本地磁盘中存储着
- 下载:
- 指令:
npm i nrm -g下载到全局 - 下载好之后,命令行中输入
nrm --version查看版本
- 指令:
- nrm使用
- 指令:
nrm ls查看所有可用的镜像源 - 指令:
nrm test查看镜像源的速度及当前使用的镜像源,前面有一个* - 指令:
nrm use 名称切换镜像源 - 指令:
nrm del 名称删除已有的镜像源地址 - 指令:
nrm add 名称添加 镜像源地址
- 指令:
npx工具
- npx和npm一样都是下载包的
- 如果npm版本比较低,5版本之前的,则需要自己手动下载npx工具
- 下载:
npm install -g npx - 测试:
npx -v
- 下载:
- 高版本的npm中已经自带了npx工具,不需要下载
- 如果npm版本比较低,5版本之前的,则需要自己手动下载npx工具
- npm 下载的包在本地磁盘中存储着
- npx 下载的包在内存中,所以npx都是下载临时包使用
- 指令:
npx anywhere可以临时下载anywhere的包并执行启动一个临时服务器
- 指令:
anywhere工具
- anywhere--临时开启一个服务器
- 在任何一个位置执行**
anywhere**都可以开启一个临时服务
nodemon工具
-
这是一个全局工具,用法跟node一样,但nodemon可以自动监视文件变化,当文件发生变化会自动重新执行命令
-
下载:
npm i nodemon -g- 使用和
node一样
- 使用和
yarn工具
- yarn跟npm一样,是一个第三方包的管理工具,比起npm,yarn工具更加高效快捷。
- 下载:
npm i yarn -g - 使用:
- 初始化项目:
yarn init -y - 下载包:
yarn add 包名 - 删除包:
yarn remove 包名 - 复活:
yarn install
- 初始化项目:
jsonp跨域
-
同源策略:同协议、用域名、同端口
-
当两个请求不一样的时候,浏览器会限制客户端的请求发送 ---- 浏览器的同源策略(安全策略)
- 访问页面的请求 和 在页面中发起的请求,这两个请求
- 其中
协议,域名,端口中的任意一项不一样都认为是不同源的请求,浏览器会限制
- 其中
- 访问页面的请求 和 在页面中发起的请求,这两个请求
-
触发同源策略的请求就是跨域请求
-
实现跨域请求
1. 设置请求服务的跨域资源共享 在跨域请求的目标服服务器设置 响应头Access-Control-Allow-Origin 2. 使用代理实现跨域请求 在自己服务器中使用一个代理模块实现跨域请求 设置 3. jsonp实现跨域 其实jsonp就是使用了html中的一些已有的标签可以请求不同源的资源 比如: img 标签的src属性请求的资源 在页面中当做图片使用 link 标签的href 属性请求的资源 在页面中当做css使用 script标签的src属性也可以请求不同源的资源,请求回来的资源当做js代码执行 通过jsonp请求的资源我们期望他返回一个 函数调用的字符串jsonp跨域🌰: // 输入内容,在下方的ul中模拟百度搜索的匹配内容 // 获取元素 let inp = document.querySelector('input'); let ul = document.querySelector('ul'); // 给输入框绑定输入事件 inp.addEventListener('input', inpFn); // 事件处理函数 function inpFn() { let value = this.value; // 获取输入的内容 // 1. 创建script标签 let script = document.createElement('script'); // 给所有创建的标签添加一个类名 script.classList.add('jsonp'); // 获取页面中所有script标签,移除类名为jsonp的标签 let scriptAll = document.querySelectorAll('script'); scriptAll.forEach(v=>{ if(v.className==='jsonp') v.remove(); }) // 2. 添加请求地址 script.src = `https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&sugsid=36455,31253,36420,36165,35978,36055,26350,36299,36312,36447&wd=${value}&req=2&bs=12333&pbs=12333&csor=2&pwd=q&cb=fn&_=1653010128946` // 3. 将标签追加到页面中 document.body.appendChild(script); // 4. 书写函数 在函数中移除创建追加的标签 window.fn = function (res) { // 将fn挂载在window上 if (!res.g) { // 没有数据 则不显示 ul.style.display = 'none'; } else { // 渲染数据并显示 ul.style.display = 'block'; ul.innerHTML = res.g.reduce((prev, item) => prev + `<li>${item.q}<li>`, ''); } // 移除创建的script标签 script.remove(); } }
express工具
-
Express 是基于 Node.js 平台,
快速、开放、极简的 Web 开发框架。搭建web服务器 -
Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。
-
使用Express开发框架可以非常方便、快速的创建Web网站的服务器或API接口的服务器
-
下载
- 指令:
npm i express
- 指令:
-
导入
let express = require('express')- 导出返回的是一个函数
-
使用
-
let app = express()- 函数调用返回的是一个http服务
-
app.listen(端口,回调)- 监听端口
🌰: // 导入 const express = require('express'); // 创建app服务 let app = express(); // 监听端口 let port = 8090; app.listen(port,()=>console.log( `服务器启动成功:端口为${port}` )); // 创建的http服务 ---- 具有一些请求响应方法 // 比如: app.get(地址,函数) // 请求方式为get并且请求地址符合,则执行对应的函数 app.get('/list',(req,res)=>{ // req和res是express包装后的 请求信息对象 和 响应对象 res.end('success') })
-
请求方式和请求路径
-
路由: 由
请求方式,请求地址,请求处理函数组成 -
express中支持的多个请求方式,也是 express创建的服务的方法- get(获取) post(提交) put(修改) delete(删除)
-
语法:
http服务.请求方法(请求地址,处理函数) -
处理函数中的两个形参,分别是请求对象和响应对象
-
请求方式(路由方法):
-
app.get() 获取
🌰: app.get('/list', (req, res) => { // req是请求对象,如果是get请求携带的数据则,直接可通过 req.query获取 res.end(JSON.stringify({ code: 1, info: "这是响应的数据", data: req.query })); }) -
app.post() 提交
例:app.post('/list', (req, res) => { res.end(JSON.stringify({ code: 1, info: "这是post提交的请求", data: {a:1} })); }) -
app.put() 修改
例:app.put('/list', (req, res) => { res.end(JSON.stringify({ code: 1, info: "这是put更新数据", data: {a:1} })); }) -
app.delete() 修改
例:app.delete('/list', (req, res) => { res.end(JSON.stringify({ code: 1, info: "这是delete删除数据", data: {a:1} })); }) -
app.all() 所有请求方式都可以匹配
- 一般写在最后面,用于404页面的返回,但是不会用这个
例:app.all('/list', (req, res) => { res.end(JSON.stringify({ code: 1, info: "这是all数据", })); }) -
app.use() 所有请求方式都可以匹配
- 一般用于中间件
例:app.use('/list', (req, res) => { res.end(JSON.stringify({ code: 1, info: "这是响应数据", })); })
-
-
请求路径(路由路径):
-
只能匹配 '/list' 的请求路径
例:app.get('/list', (req, res) => { res.end(JSON.stringify({ code: 1, info: "这是响应数据", })); }) -
+表示前面的字符一个或多个
例:app.get('/li+st', (req, res) => { // 可以匹配的路径 如:/list , /liist , /liiist res.end(JSON.stringify({ code: 1, info: "这是响应数据", })); })?表示前面的字符 0个或1
例:app.get('/li?st', (req, res) => { // 可以匹配的路径 如: /list , /lst res.end(JSON.stringify({ code: 1, info: "这是响应数据", })); })*表示该位置可以是任意内容
app.get('/li*st', (req, res) => { // 可以匹配的路径 如: /list , /liiist , /liwrerst res.end(JSON.stringify({ code: 1, info: "这是响应数据", })); })-
可以匹配正则
例:app.get(/^\/a(p|i)$/, (req, res) => { // 表示匹配正则规则的 只能是 /ap 或 /ai res.end(JSON.stringify({ code: 1, info: "这是响应数据", })); }) -
?表示该位置的参数可以不传递
- 表示
:id的位置是地址的一部分,也是参数 --- rlestful风格参数,动态参数 (\\d+)表示该位置只能是数字,+表示可以是多个- 动态参数获取
req.paramers
app.get('/list/:id(\\d+)/:name?', (req, res) => { // 动态参数获取 req.paramers console.log(req.params) res.end(JSON.stringify({ code: 1, info: "这是响应数据", })); }) // 请求地址 例如:http://10.36.138.107:8090/list/10 // http://10.36.138.107:8090/list/10/lele -
响应方法
express中有多种响应方法
-
响应对象.end()原生的方法res.end('<h1>小张</h1>') -
响应对象.json()响应json格式的数据 一般用于接口文件res.json({name:'zs',age:17}) res.json([{name:'zs',age:17},{name:'zs',age:17}]) -
响应对象.send()可以任何数据类型res.send('<h1>你好</h1>') res.send({name:'zs',age:17}) -
响应对象.status()可以设置响应的状态码res.status('200').send({ name: 'zf', age: 68 }) -
响应对象.sendFile(页面地址)响应页面 (页面地址使用绝对地址)res.sendFile(require('path').join(__dirname,'./public/page/index.html')) -
响应对象.redirect()重定向res.redirect('/404')
路由多次处理
- 一个路由可以有多个处理函数
- 处理函数有 三个参数
- 参数1: 请求信息对象
- 参数2: 响应对象
- 参数3: 回调函数---让我们处理函数执行完毕到下一个路由通道函数中执行
🌰:
app.get('/abc', (req, res, next) => {
// 向req对象中挂载一个属性
req.name = 'zs';
console.log('第一次函数处理')
next();// 执行next 则路由处理进入到下一个通道
})
app.get('/abc', (req, res, next) => {
req.age = 38;
console.log('第二次函数处理')
next();
})
app.get('/abc', (req, res) => {
console.log(req.name)
console.log('第三次函数处理')
res.send({ info: { name: req.name, age: req.age }, code: 1 })
})
👆相当于👇
🌰🌰:
app.get('/abc', (req, res, next) => {
req.name = 'zs';
console.log('第一次函数处理')
next();// 执行next 则路由处理进入到下一个通道
}, (req, res, next) => {
req.age = 38;
console.log('第二次函数处理')
next();
}, (req, res, next) => {
console.log(req.name)
console.log('第三次函数处理')
res.send({ info: { name: req.name, age: req.age }, code: 1 })
})
👆相当于👇
🌰🌰🌰:
let f1 = (req, res, next) => {
req.name = 'zs';
console.log('第一次函数处理')
next();// 执行next 则路由处理进入到下一个通道
}
let f2 = (req, res, next) => {
req.age = 38;
console.log('第二次函数处理')
next();
}
let f3 = (req, res, next) => {
console.log(req.name)
console.log('第三次函数处理')
res.send({ info: { name: req.name, age: req.age }, code: 1 })
}
静态资源托管
-
express中提供了一个方法express.static(目录)- 此方法可以让请求该目中的资源(html,css,js字体视频音频等等) 更简便
-
express.static('public'),将此方法给到**app.use()**中-
所有对
public中文件的请求都可以直接发起/page/index.html-----> /public/page/index.html /js/index.js-----> /public/js/index.js app.use(express.static('public'))
-
-
static是一个虚拟目录-
只有路由开头是
'/static'的时候才会执行后面的**express.static('public')**app.use('/static',express.static('public'))
-
路由
-
express中的路由分3部份组成,分别是请求类型(方法)、请求uri(地址)和对应的处理函数。 -
当一个客户端请求到达服务端之后,先经过路由规则匹配,只有匹配成功之后,才会调用对应的处理函数。
-
在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的函数进行处理。
🌰: // 1. 创建一个路由表 let router = express.Router(); // 2. 给路由表添加信息 router.get('/goods/:id(\\d+)',(req,res)=>{ res.send({info:`响应了商品id为${req.params.id}的详情信息`,code:1}) }) router.post('/goods',(req,res)=>{ res.send({info:`添加了一个商品信息`,code:1}) }) router.put('/goods',(req,res)=>{ res.send({info:`修改了一个商品信息`,code:1}) }) router.delete('/goods/:id(\\d+)',(req,res)=>{ res.send({info:`删除了id为${req.params.id}的商品`,code:1}) }) // 3. 将路由表注册到服务中 app.use(router);
路由模块化
-
含义:将原本可能写在一个文件中的路由规则,拆分成若干个路由文件(js文件,一个js文件就是一个模块)
-
顾名思义,将路由进行模块化,以模块(js文件)为单位进行管理,物以类聚
-
核心思想:能拆就拆(拆到不能拆为止,解耦,高内聚,低耦合)
-
通过**
express.Router()**方法创建路由模块化处理程序,可以将不同业务需求分开到不同的模块中,从而便于代码的维护和项目扩展。 -
app.use('地址1',函数)请求 只要是一个地址1 开头的地址 都可以匹配到 -
🌰:goods.js 路由表模块文件 (users.js模块已省略,详情见GP-9/28-day) #// 导入express创建路由表 let router = require('express').Router(); function errMiddle(req,res,next) { // 错误中间件 let id = req.params.id; if(id>50){ next(`你是不是找茬?id这么大${id}`); // next(参数) 调用如果传递参数,则不会继续到路由下一个函数, // 而是直接执行到异常处理中间件,而且将异常信息给到第一个形参 err }else{ next();// 正常执行下一步 } } // 给路由表添加信息 // router.get('/goods/:id(\\d+)',(req,res)=>{ router.get('/:id(\\d+)',errMiddle,(req,res)=>{ // 假设在执行到这个路由处理函数的时候 抛出异常 // throw Error('你有问题?不给过'); res.send({info:`响应了商品id为${req.params.id}的详情信息`,code:1}) }) // router.post('/goods',(req,res)=>{ router.post('/',(req,res)=>{ res.send({info:`添加了一个商品信息`,code:1}) }) // router.put('/goods',(req,res)=>{ router.put('/',(req,res)=>{ res.send({info:`修改了一个商品信息`,code:1}) }) // router.delete('/goods/:id(\\d+)',(req,res)=>{ router.delete('/:id(\\d+)',(req,res)=>{ res.send({info:`删除了id为${req.params.id}的商品`,code:1}) }) #// 导出路由表 module.exports = router; 🌰🌰:路由.js #//模块中已给请求地址 // 导入路由表 let goods = require('./routers/goods'); let users = require('./routers/users'); // 将路由表注册到服务中 app.use(goods) app.use(users) 👆等价于👇 // 导入路由表 并 将路由表注册到服务中 app.use(require('./routers/goods')) app.use(require('./routers/users')) 🌰🌰:路由.js // 导入路由表 并 将路由表注册到服务中 app.use('/goods', require('./routers/goods')) app.use('/users', require('./routers/users')) 👆等价于👇 // let res = require('fs').readdirSync('./routers') // console.log(res) // [ 'goods.js', 'users.js'] require('fs').readdirSync('./routers').forEach(v => app.use('/' + v.slice(0, -3), require('./routers/' + v)));
中间件
全局中间件
app.use((req,res,next)=>{})
路由中间件
router.use((req,res,next)=>{})
请求中间件
router.post('/login',(req,res,next)=>{},执行函数)
错误中间件
app.use((err,req,res,next)=>{})
错误中间件err:异常信息
参数1:req 请求对象
参数2:res 请求响应
参数3:next
next() 放行
next(错误编码) 错误编码会被错误中间件中的err捕获
- 中间件分类:
- 内置中间件
express本身自带无需npm安装express.static()
- 第三方中间件
- 非
Express官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件 - 以通过
npm进行安装第三方中间件并配置,从而提高项目的开发效率 - 例如
body-parser(解析post数据的)此中间件可以很方便帮助我们获取到post提交过来的数据
- 非
- 自定义中间件
- 开发这者自己编写的中间件(中间件的本质就是一个函数)
- 内置中间件
- 从应用层考虑的分类:
- 应用级别中间件(通过app.get/post/use等方法绑定到app实例的中间件)
- 全局使用中间件(所有路由都生效)
app.use(中间件)
- 局部使用中间件(当前路由生效)
app.请求方法(地址,[中间件.....,]回调函数)
- 全局使用中间件(所有路由都生效)
- 路由级别中间件(绑定到express.Router()上的中间件)
- 其用法与应用级别的中间件没有任何区别,只是一个绑在app实例上,一个绑在router上
outer.use(中间件)router.请求方法(地址,[中间件.....,]回调函数)
- 应用级别中间件(通过app.get/post/use等方法绑定到app实例的中间件)
内置中间件
-
静态资源管理的中间件
-
帮助为我们快速搭建一个静态资源服务器
app.use('前缀',express.static('托管目录地址')) 🌰:app.use('/static', express.static('public'))
-
-
express.json()- 作用:接收json格式提交的数据
- 兼容性问题:express >= 4.16.0
app.use(express.json())- 其在接收完数据后,会将数据的对象形式挂载到
req请求对象的body属性上 =>req.body
-
express.urlencoded()- 作用:处理post表单数据
- 兼容性问题:express >= 4.16.0
app.use(express.urlencoded({extended: false}))- 其在接收完数据后,会将数据的对象形式挂载到
req请求对象的body属性上 =>req.body - get请求的数据在
req.query
注意:
- 后面提及的这2个常用内置中间件存在兼容性问题。
- 上述2个中间件都说把数据处理之后挂到req.body上,但是实际上并不会出现我们想的覆盖的问题。
🌰:
// 搭建一个简单的web服务器
const express = require('express');
// 创建app服务
let app = express();
// 监听端口
let port = 8090
app.listen(port, () => console.log(`服务器启动成功:端口为${port}`));
// 静态资源托管
app.use('/static', express.static('public'))// express.static()就是一个内置中间件
// 内置中间件---body-parser
let bodyParser = require('body-parser');
// 使用导入的中间件
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
#// 经过中间件处理后,post请求的数据在 req.body中
app.post('/login', (req, res) => {
// console.log(req.body)
if (req.body.name == 'zf' && req.body.pwd == '12345') {
res.send({ code: 1, info: '登录成功' });
} else {
res.send({ code: 0, info: '登录失败' });
}
})
自定义中间件
-
自定义中间件,其本质就是定义一个处理请求的函数
-
此函数中除了有req和res参数外还必须包含一个next参数,此参数作用让中间件能够让流程向下执行下去直到匹配到的路由中发送响应给客户端。
-
也可以通过给request对象添加属性来进行中间件数据的向下传递
-
语法:
function mfn(req,res,next){ // 自己需要定义的逻辑流程 // 中间件最后一定要执行此函数,否则程序无法向下执行下去 next() }🌰: const express = require('express');// 搭建一个简单的web服务器 let app = express();// 创建app服务 // 监听端口 let port = 8090 app.listen(8090, () => console.log(`服务器启动成功:端口为${port}`)); // 自定义中间件 function postData(req, res, next) { // 处理post请求携带数据 let str = ''; req.on('data', chunk => str += chunk); req.on('end', () => { // 获取到了post携带的数据,转为对象后将其挂载到req.body中 req.body = require('querystring').parse(str) next();// 执行next() 让处理到一下一个函数中 }) } // 经过中间件处理后,post请求的数据在 req.body中----单独路由中间件 app.post('/login', postData, (req, res) => { // console.log(req.body) if (req.body.name == 'zf' && req.body.pwd == '12345') { res.send({ code: 1, info: '登录成功' }); } else { res.send({ code: 0, info: '登录失败' }); } })
异常处理中间件
-
**作用:**专门用来捕获整个项目发生的异常错误,从而防止项目异常崩溃的问题产生(友好显示异常)
-
**格式:**错误级别中间件的函数参数中,必须有四个形参,分别是
(err,req,res,next)-
err里面包含了错误的信息,**err.message**属性中就包含了错误的文本信息,这个信息可以在中间件中输出给用户看- 参数1:
err异常信息对象 - 参数2:
req请求对象 - 参数3:
res请求响应 - 参数4:
next
app.use((err, req, res, next) => { console.dir(err) // 书写错误日志 require('fs').appendFileSync('./log/err.txt', `${err}\n`) next(); }) - 参数1:
-
404中间件
-
**作用:**用于处理404的请求响应
-
404错误中间件也要求在所有的正常请求路由的后面去声明使用,不要放在路由的前面,否则会导致后面的路由都是404错误
-
**注意点:**错误级别的中间件,必须在所有路由之后,至于404中间件与异常中间件,谁先谁后无所谓
🌰: //前面的请求路由都没有匹配到则 给用户一个404页面 app.use((req, res) => { res.sendFile(require('path').join(__dirname, './public/page/404.html')) }) 🌰🌰: 前面的请求路由都没有匹配到则 给用户一个404页面 app.use((req, res) => { // 输出404错误,先指定404状态码,然后再输出错误信息 res.status(404).send('<h1>404</h1>') })
cookie
cookie的原理是在浏览器中开辟了一个用来存储http请求中的数据,第一次保存之后,下次请求只要还是使用的当前浏览器,就能访问到浏览器这个空间中的数据。
cookie会作为键值对,在响应头和请求头之间携带。
cookie的特点:
1. 域名限制,当前域名下设置的cookie,只能在当前域名下使用
2. 时效性,cookie不会永久存储在浏览器,具有一定的有效期
3. 数量限制,正常情况下,每个域名最多不能超过50个cookie
4. 空间限制,cookie只能存储4kb
5. 数据类型限制,cookie中只能存储字符串
-
下载
- 指令:**
npm i cookie-parser**
- 指令:**
-
导入:
const cookieParser = require('cookie-parser') -
使用
// 导入 const cookieParser = require('cookie-parser') // 中间件 app.use(cookieParser()); // 请求头获取 req.headers.cookie // 获取所有cookie // 响应头设置 res.cookie(键,值,{maxAge: 有效期-毫秒}) // 设置cookie🌰: // 下载cookie-parser包,然后导入事件 let cookieParser = require('cookie-parser'); // 全局注册cookie中间件 app.use(cookieParser()); app.get('/cookie',(req,res)=>{ console.log( 666 ) // 获取cookie // 1. 通过请求头获取到cookie console.log( req.headers.cookie ) // 查询字符串 // 2. 直接通过req.cookies获取----已经使用cookie的中间件 console.log( req.cookies )// 对象 // 服务端 设置cookie res.cookie('hobby','code',{ maxAge: 6000 // 单位毫秒 }) res.send('ok'); })
加密
-
下载:
npm i bcryptjs
-
导入加密模块
let bcryptjs = require('bcryptjs'); -
密文
密文 = bcryptjs.hashSync(明文字符串[,数字]);- 数字,将使用指定的轮数生成盐并将其使用。推荐 10
let pwd = bcryptjs.hashSync(password, 10) -
验证
bcryptjs.compareSync(明文,密文);- 返回值:通过返回
true,失败返回false
🌰:
let bcryptjs = require('bcryptjs');// 导入加密模块
app.use(express.urlencoded({ extended: false }))// 使用中间件处理post携带数据
app.post('/register', (req, res) => {
// 获取到数据
let { username, password, email } = req.body;
// 假设校验通过:(非空,正则校验....,用户名可用校验)
// 加密方法 hashSync(需要加密的字符串,盐)
let pwd = bcryptjs.hashSync(password, 10)
// 写入文件保存
require('fs').writeFile('./public/user.json', JSON.stringify({ username, pwd, email }), err => {
if (err) return res.send('注册失败');
res.redirect('/login.html');
})
})
app.post('/login', (req, res) => {
// 获取到数据
let { username, password } = req.body;
// 假设已经校验通过了
// 去到文件中获取保存的用户名和密码
let userObj = JSON.parse(require('fs').readFileSync('./public/user.json'))
// 密文密码的比对 bcryptjs.compareSync(明文,密文),返回布尔值
// 比对用户名和密码
if(userObj.username ==username&& bcryptjs.compareSync(password,userObj.pwd)){
res.send('登录成功')
}else{
res.send('登录失败')
}
})
token令牌
-
jwt:jsonwebtoken 生成token令牌
-
下载:
- 指令:
npm install jsonwebtoken
- 指令:
-
导入:
var jwt = require('jsonwebtoken'); -
生成token令牌
-
jwt.sign(加密对象, 秘钥, { expiresIn: 有效时间单位s})let secretStr = 'qwrey'; app.post('/login', (req, res) => { // 假设用户已经通过了登录验证(非空,正则,数据库校验....) // 登录验证通过后的到一个用户的id let userObj = { uid: 1 }; // 生成token令牌 let token = jwt.sign(userObj, secretStr, { expiresIn: 30 }) }
-
-
验证
jwt.verify(token, 秘钥, function(err, data) {});err验证失败的信息data验证成功的信息
🌰:
let jwt = require('jsonwebtoken');// 导入 jwt
app.use(express.urlencoded({ extended: false }));// 使用中间件post处理携带数据
app.use(require('cookie-parser')());// 使用cookie的中间件
let secretStr = 'qwreytryreyryutruywq';
app.post('/login', (req, res) => {
let { username, password } = req.body;
// 假设用户已经通过了登录验证(非空,正则,数据库校验....)
// 登录验证通过后的到一个用户的id
let userObj = { uid: 1 };
// 生成token令牌 jwt.sign(加密对象, 秘钥, { expiresIn: 有效时间单位s})
let token = jwt.sign(userObj, secretStr, { expiresIn: 30 })
// 将生成的token令牌存储到cookie中 是一种返回给前端的方式---或者直接写到响应数据中
res.cookie('authorization', token);
res.redirect('/index.html')
})
app.get('/verify', (req, res) => {
// 用户在cookie中自动携带了token
let token = req.cookies.authorization;
// token验证 jwt.verify(token,secretStr,(err,data)=>{})
jwt.verify(token, secretStr, (err, data) => {
// err 验证失败的信息
// data 验证成功的信息
console.log('err', err)
console.log('data', data)
if (err) return console.log('err', err)
res.send('验证通过,给你数据')
})
})
文件上传
-
multer :文件上传模块
-
下载
- 指令:
npm i multer
- 指令:
-
导入
let multer = require('multer'); -
创建文件上传器
-
multer({dest: 书写文件上传地址})let upload = multer({dest:'./public/upload/'})
-
-
文件上传中间件,使用在路由中
-
upload.single(参数) -
参数就是 文件上传的键名
app.post('/upload',upload.single('avatar'),(req,res)=>{}
-
-
🌰:
let jwt = require('jsonwebtoken');// 导入 jwt
let multer = require('multer');// 导入文件上传模块
// 创建一个文件上传器,multer({dest: 书写文件上传地址})
let upload = multer({dest:'./public/upload/'})
app.use(express.urlencoded({ extended: false }))// 使用中间件post处理携带数据
app.use(require('cookie-parser')());// 使用cookie的中间件
// upload.single(参数) 文件上传是一个中间件,使用在路由中,参数就是 文件上传的键名
app.post('/upload',upload.single('avatar'),(req,res)=>{
// 执行到此处的时候,文件已经上传成功了
// console.log( req.body ); console.log( req.file )
// 文件重命名 获取原文件名 获取文件后缀
let arr = req.file.originalname.split('.')
let extname = arr[arr.length-1];
// 时间戳_随机字符.+后缀
let rfileName = `${new Date().getTime()}_${Math.random().toString(36).slice(-6,-1)}.${extname}`
require('fs').renameSync('./public/upload/'+req.file.filename,'./public/upload/'+rfileName);
res.send('上传成功过');
})
/* {
fieldname: 'avatar',
originalname: 'zp.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './public/upload/',
filename: 'afc4b7db15a21a21bcdd9bd4f0b4a011',
path: 'public\\upload\\afc4b7db15a21a21bcdd9bd4f0b4a011',
size: 313531
}*/
- 🌰(H5-2211):上传图片
<form>
<label>
<img src="../img/avatar.png"><br>
<input type="file" name="avatar">
</label>
</form>
<script>
上传图片:
1、使用input:file,必须带上一个name值
2、需要form表单标签
3、获取标签
4、检验文件格式
5、需要FormData,搜集form表单里的数据当做参数传给服务器
服务器需要 const fm = new FormData(form)
6、发起请求,由于文件名解码后过长,必须是post请求
// 获取标签
const form = document.querySelector('form')
const img = document.querySelector('img')
const inp = document.querySelector('input')
inp.onchange = function () {
// inp.files[0] 图片上传的信息
console.dir(inp.files[0])
// 校验只能上传图片,如果不是图片则弹出提示并返回
const type = inp.files[0].type
if (!(/^image/.test(type))) return alert('只能上传图片')
// 搜集form表单中的数据
const fm = new FormData(form)
// 发起请求
const xhr = new XMLHttpRequest()
xhr.open('post', 'http://127.0.0.1:8080/api/upload/avatar')
xhr.send(fm)
// 接收服务器返回的内容
xhr.onload = function () {
const obj = JSON.parse(xhr.responseText)
if (obj.code == 200) {
img.src = obj.path
}
console.log(obj);
}
}
</script>
接👆:
// 导入路由表
const router = require('express').Router()
// 导入UUID,uuid处理唯一性
const { v4: uuidv4 } = require('uuid')
// 导入multer
const multer = require('multer')
// 配置仓库
const storageInfo = multer.diskStorage({
// desrination(req,file,cb){};filename(req,file,cb){}
// 参数1:请求
// 参数2:文件
// 参数3:回调
// 仓库
destination(req, file, cb) {
// 将文件放在public目录中
cb(null, './public')
},
// 文件名
filename(req, file, cb) {
const arr = file.originalname.split('.')
// 拼接文件名
cb(null, arr[0] + uuidv4() + '.' + arr[1])
}
})
// 获取文件操作
const updata = multer({ storage: storageInfo })
/*
使用文件上传中间件
请求地址
文件上传数量 updata.single("avatar")单个文件上传
*/
router.post('/avatar', updata.single("avatar"), (req, res) => {
// 文件路径返回给前端
res.send({
code: 200, path: 'http://127.0.0.1:8080/' + req.file.path
})
console.log(req.file.path);
})
// 导出
module.exports = router
UUID
-
解决唯一性
-
下载指令:
npm i uuid -
使用
🌰: const { v4: uuidv4 } = require('uuid'); console.log(uuidv4());
验证码
-
svg-captcha:验证码模块
-
下载
- 指令:
npm i svg-captcha
- 指令:
-
导入
let captcha = require('svg-captcha'); -
使用
let cap = captcha.create({ size: 4, noise: 3, background: 'pink', width: 100, height: 30 });- cap 是一个对象
- text是就是验证码值
- data: 就是验证码图片的svg图
- cap 是一个对象
邮件发送
-
下载
- 指令:
npm i nodemailer
- 指令:
-
导入
let nodeMailer = require('nodemailer'); -
创建邮箱发送器
let transport = nodeMailer.createTransport({ // 需要你发送放邮箱的 stmp 域名和密码和一些其他信息 // \node_modules\nodemailer\lib\well-known\services.json 找到对应的邮箱服务器 "host": "smtp.163.com", "port": 465, "secure": true, auth: { user: 'leoncoder@163.com', pass: 'BVTDENZKIWCCXBQV'// 授权码 } }) -
发送邮件
transport.sendMail({ from: 'leoncoder@163.com', // 从那个邮箱发送 to: ['接收方邮箱', '接收方邮箱'],// 接受方邮箱, 可以书写邮箱(字符串), 也可以写一个数组 // 邮件标题 subject: '标题', // 本次邮件的 超文本 内容 html: '<a href="http://baidu.com">点击领取大奖</a>', // 本次邮件的 文本 内容 // text: '' // text 和 html 只能使用一个 }, (err, data)=>{ console.log(err) console.log(data) res.send('ok') })
模板引擎
-
下载
- 指令:
npm i art-template express-art-template - 下载 art-template 和 express-art-template
- 只需要导入 express-art-template 就行
- 指令:
-
设置模板引擎文件的后缀
app.engine('html', require('express-art-template')); -
设置模板引擎的根目录
app.set('views', require('path').join(__dirname, 'muban')); -
设置模板引擎
- 使用的时候 可以省略后缀名,必须和之前设置的一致
app.set('view engine', 'html'); -
使用
app.get('/test', (req, res) => { res.render('test2', { name: 'zf', age: 20, detail: "<h1>我是标题h1</h1>", arr: [ { name: 'zf0', age: 18 }, { name: 'zf1', age: 28 }, { name: 'zf2', age: 38 }, { name: 'zf3', age: 48 } ] }) // express中必须使用res.render() 方法来响应模板文件 // 第一个参数是 模板文件 // 第二个参数必须是一个对象,对象中的数据是可以渲染到对应的模板文件中的 // res.render('test1') })
模板语法
-
模板中有两套渲染语法
{{ 数据 }}模板引擎中的<% 数据 %>原生的模板语法
-
模板引擎的语法
-
直接渲染变量数据
名字: {{ name }}<br> -
渲染html结构的数据
标题: {{@ detail }}<br> -
条件渲染
{{ if age>18 }} 成年了 {{ else }} 未成年 {{/if}} <br> -
循环数组
{{ each arr }} <!-- $value 就是每一个遍历的内容 $index 是对应的索引 --> {{ $value.name }}------{{ $value.age }}-----{{ $index }} <br> {{ /each }}<ul> {{ each arr v i }} <!-- v 就是每一个遍历的内容 i 是对应的索引 --> <li> {{ v.name }}------{{ v.age }}-----{{ i }}</li> {{ /each }} </ul> -
导入模板
{{ include './muban.html' }}
-
-
原生的语法
名字: <%= name %><br> 标题: <%- detail %><br> <% if(age>18){ %> 成年了 <% }else{ %> 未成年 <% } %> <br> 循环: <br> <% arr.forEach(function(v,i){ %> <!-- v 就是每一个遍历的内容 i 是对应的索引 --> <%= v.name %>------<%= v.age %>-----<%= i %> <br> <% }) %> <% include('./muban.html') %>
mongoDB
-
数据库分为两大类
- 关系型数据库----数据存储在磁盘中
- 数据库中的数据相互之间有一定的联系,可以使用联表查询
- 典型的关系型数据库: MySQL
- 非关系型数据库----数据存储在内存中
- 数据库中的数据相互可以没有联系
- 典型的非关系型数据库: mongoDB / redis
- 查询读取速度特别快
- 查询语法比较类似于 对象的语法操作
- 数据存储也都和 json格式数据比较像
- 关系型数据库----数据存储在磁盘中
-
测试
-
指令:
mongo -
退出mongoDB
- 指令:
exit
- 指令:
-
查看所有的数据库列表
- 指令:
show dbs - 列出的库都是非空的库
- 指令:
-
创建数据库或切换数据库
- 指令:
use 数据库名- use切换数据库时,若库存在则切换,如果数据库不存在则创建并切换
- use创建的数据库只是一个空的数据库,没有集合,所以
show dbs不显示空数据库。
- 指令:
-
查看当前所在数据库名称
- 指令:
db
- 指令:
-
给数据库中创建一个members的集合,并向集合中添加文档(行)数据:
-
指令:
db.表名/集合名.insert(JSON格式数据)- 表是不需要先行进行定义,当往表中插入记录后,表就自动出来了
- JSON格式数据:不是严格意义上的json数据,key名可以不使用引号包裹
db.users.insert({id:1,username:'zhangsan',age:18})
-
-
查看当前数据库中的集合列表
- 指令:
show tables或show collections
- 指令:
-
删除表
- 指令:
db.表名/集合名.drop()
- 指令:
-
删除库
- 指令:
db.dropDatabase()- 需要进入要删除的库,然后再去执行这个命令
- 指令:
总结:
- show dbs:查看数据库列表
- use db:使用/创建数据库
- db:查看当前库名
- db.表名.insert():新增数据&可能会创建出一个数据表
- show tables / show collections:查看当前库中的表列表
- db.表名.drop():删除指定的表
- db.dropDatabase():删除当前的库
增删改查
-
增
# 添加单条文档数据 db.表名/集合名.insertOne({ key: value , key: value...}) # 添加多条文档数据 db.表名/集合名.insertMany([{}, {}, {}]) # 可以添加单条也可以多条数据(为主) db.表名/集合名.insert( {} ) db.表名/集合名.insert([{}, {} ]) -
删
-
删除在实际开发的时候一般不用,正常做程序开发的时候所使用的删除实际上是修改。
-
删除分为:真删除(物理删除)、假删除(逻辑删除)。
-
删除集合中已存在的文档数据:
# 删除单条文档 db.表名/集合名.deleteOne({ key: value }) # 删除符合条件多条文档 db.表名/集合名.deleteMany({key: value}) # 删除全部数据(慎用) db.表名/集合名.deleteMany({})🌰: db.user.deleteOne({ id: 4 })
-
-
改
# 只修改单条文档 db.表名/集合名.updateOne({key:value},{$set:{key:value}}) # 修改符合条件所有文档数据 db.表名/集合名.updateMany({key:value},{$set:{key:value}}) ` 如果上述两个方法的条件一致,并且有多个符合条件的,那么: updateOne,不管有多少个符合条件的只修改第一个 updateMany,有多少改多少` # 字段的值的`自增和自减` db.表名/集合名.updateOne({key:value},{$inc:{key:1}}) db.表名/集合名.updateMany({key:value},{$inc:{key:1}}) db.表名/集合名.updateOne({key:value},{$inc:{key:-1}}) db.表名/集合名.updateMany({key:value},{$inc:{key:-1}}) # 自增是整数,自减是负数 # 更新和删除操作允许不写条件,但是不能不写第一个{} -
查
-
查询所有的数据
db.表名/集合名.find(); # 获取全部(推荐) db.表名/集合名.find({}); # 获取全部 // `{ }`用于条件限制,当没条件的时候,上述两个用法效果一致关于
_id{ "_id" : ObjectId("5c0fa4758878caa23d36c0fb"), "name" : "zhangsan" }
objectID类型 ObjectId对象对象数据组成:时间戳 |机器码|PID|计数器 系统自动生成 _id的键值我们可以自己输入,==但是不能重复==,因此为了避免工作的复杂建议不要人为的去干预_id
-
带条件查询
db.表名/集合名.find({key:value,key:value....}) 例:db.user.find({id:5})此处允许使用多个条件,如果有就在"{}"中多写几个,默认是“且”条件关系。
-
字段显示控制
db.表名/集合名.find(条件,{字段名:0或1,....}) # 0:不显示 # 1:显示 # _id字段,由于其是系统产生的,默认情况下是显示的 # 注意: 字段显示控制的1和0不能同时出现 例:db.user.find({},name:1,age:1) -
逻辑运算(稍微复杂一些)
### 条件表达式 # $gt 年龄大于5的 db.表名/集合名.find({age:{$gt:5}}); // age > 5 # $gte 年龄大于等于5的 db.表名/集合名.find({age:{$gte:5}}); // age >= 5 # $lt 年龄小于5的 db.表名/集合名.find({age:{$lt:5}}); // age < 5 # $lte 年龄小于等于5的 db.表名/集合名.find({age:{$lte:5}}); // age <= 5 # $ne 年龄不等于5的 db.表名/集合名.find({age:{$ne:5}}); // age != 5 # $in 在一个指定的数值中查询 $in 年龄在不在这几个指定数值当中 db.表名/集合名.find({age:{$in:[1,2,3]}) ## 且关系 and db.表名/集合名.find({age:{$lt:5},username:"user11"}) // age < 5 && username = 'user11' ## 或关系 or(有点绕) db.表名/集合名.find({$or:[{条件1},{条件2}]}) ## 例: db.表名/集合名.find({$or:[{age:{$ne:5}},{username: "user11"}]}); 🌰:需求:查询出年龄小于30或者gender为0的数据 db.members.find({$or:[{age:{$lt:30}},{gender:0}]}) -
模糊查询
db.表名/集合名.find({字段名:/正则/i})i 不区分大小写
不能加引号,否则就成了字符串,成了字符串就成了精确匹配 -
统计
#统计总记录数 db.表名/集合名.count(); // 统计所有的记录的总数 db.表名/集合名.find({}).count(); // 统计符合条件的结果的记录总数 db.表名/集合名.countDocuments({}); // 统计符合条件的结果的记录总数例:db.表名/集合名.find({age:{$gt:22}}).count();注意:count不能与find的顺序交换
-
排序
# 以age字段来升序 db.表名/集合名.find().sort({age:1}) # 以age字段来降序 db.表名/集合名.find().sort({age:-1})升序:1
降序:-1
-
分页(实用)
# 指定获取几条 skip/limit db.表名/集合名.find().limit(3); db.表名/集合名.find().skip(1).limit(3);skip表示起始位置,也就是从第几个开始limit表示获取的记录的个数(长度)skip与limit的顺序先后无所谓
-
nodejs中操作
通过第三方插件mongoose连接操作,mongoose是Node环境下==异步操作==mongodb数据库的扩展,仅限于Node环境下使用。
官网:www.mongoosejs.net/docs/index.…
-
下载
- 指令:
npm i mongoose
- 指令:
-
导入和配置
// 导入模块 const mongoose = require('mongoose') // 连接数据库 返回promise对象 mongoose.connect('mongodb://localhost:27017/数据库名', { useNewUrlParser: true, useUnifiedTopology: true }) // 库必须要先存在 // connect方法参2在新版本需添加,否则会有警告提示 // useNewUrlParser:当前URL字符串分析器已弃用,将在将来的版本中删除。要使用新的解析器,请将选项{usenewurlparser:true}传递给mongoclient.connect。 // useUnifiedTopology:当前服务器发现和监视引擎已弃用,将在将来的版本中删除。要使用新的服务器发现和监视引擎,请将选项{useUnifiedTopology:true}传递给mongoclient构造函数 -
使用mongoose创建Schema表
- 主要是来约束数据表中的数据
- 后期建表不再通过mongoDB的命令行的形式产生了,而是通过JavaScript代码实现
- 在定义schema时有使用到相关约束规则,可以查看:www.mongoosejs.net/docs/guide.…
// 创建用户集合规则 const UserSchema = new mongoose.Schema({ // 设置 字段及类型 name: { // 数据类型 type: String, // 必填字段 required: true, // minlength 用于字符串类型 minlength: 2, // 字段最小长度 maxlength: 6, // 字段最大长度 }, age: { type: Number, // 默认值 default: 10, // 字段最小值 min用于数字类型 min: 1, }, gender: { type: String, default: '保密', enum: ['男', '女', '保密'] // 表示gender只能是其中的一个 }, pwd: String, email: String, // 定义此字段为 字符串数组类型 hobbies: [String], }); -
使用创建的Schema创建模型操作数据
const Model = mongoose.model('User', UserSchema, 'users') // 参数1:model名称,模型名一般会和表名一样 // 参数2:schema名称 // 参数3:操作的数据集合(表名),如果参数3没有填写则以 参1的复数形式为操作数据集合名称- 模型curd相关方法
Model.insertMany({key:value}) // 写入数据 Model.deleteMany({条件},err=>{}) // 删除多条数据 Model.deleteOne({条件},err=>{}) // 删除一条数据 Model.countDocuments({条件}) // 统计 Model.find({条件},{可选字段返回:0/1},{skip:0,limit:10}) // 查询 Model.findOne({条件},{可选字段返回:0/1}) // 查询 Model.updateMany({条件},{$set:{key:value}},res=>{}) // 修改多条数据 Model.updateOne({条件},{$set:{key:value}},res=>{}) // 修改一条数据
socket
-
http无法轻松实现实时应用:
http是无状态协议,服务器只会响应来自客户端的请求,但是它与客户端之间不具备`持续连接`。我们可以实现捕获浏览器上的事件,通过事件跟服务器进行数据交互,使用ajax技术。但是反过来却不可能实现:服务器发生了一个事件,通过这个事件主动跟客户端进行交互,只有客户端主动请求服务器,服务器才会根据客户端的请求响应到客户端。 但是聊天室确实存在。聊天室是要保持客户端和服务器持续连接,且服务器能主动给客户端发送消息,实现方式如下: 长轮询:客户端每隔很短的时间,都会对服务器发出请求,查看是否有新的消息,只要轮询速度足够快,就能给人造成交互是实时进行的感觉。这种做法实属无奈,实际上会对服务器、客户端双方都造成了大量的性能浪费。 长连接:客户端跟服务器连接一次,连接上之后就不断开,服务器不给客户端响应,服务器有了新数据就将数据发回去,又有了新数据,就将数据发回来,而一直保持阻塞状态。这种做法也会造成大量的性能浪费。 -
H5提供了一个新技术解决这些问题:websocket
websocket协议能够让浏览器和服务器进行全双工实时通信,全双工就是互相之间都能主动发送消息了,服务器也能主动给客户端发送消息。 websocket的原理:利用http请求产生握手,http头部中含有websocket协议的请求,所以握手之后,二者转用tcp协议进行交流,tcp是一个比较底层的协议,可以实现实时通信。现在的qq使用的就是这个协议。 所以websocket协议需要浏览器支持,也需要服务器支持。 支持websocket协议的浏览器:Chrome4、FireFox4、Safari5 支持websocket协议的服务器:Node0、Apache7.0.2、Nginx1.3 可以看出,nodejs天生就支持websocket协议,但是从底层一步一步搭建nodejs服务器来处理tcp协议的工程是很庞大的,所以现在有一个通用的第三方模块来处理websocket - socket.io官网:socket.io/
-
下载:
- 指令:
npm i socket.io
- 指令:
举个🌰:搭建一个聊天室
首先需要创建一个web服务器:
const http = require('http')
const server = http.createServer((req,res)=>{
res.setHeader('content-type','text/html;charset=utf8')
res.end("这是web服务器")
})
server.listen(3000)
接下来导入socket.io
并通过socket.io将web服务器转换成一个长连接服务器,且将客户端需要一个js文件路径暴露出去:
const socketIO = require('socket.io')
const io = socketIO(server)
此时,我们通过服务器就能访问到一个地址:http://locahost:3000/socket.io/socket.io.js,最终能响应得到一个js文件地址。
客户端通过这个地址就能跟服务器进行即时通信了。
客户端代码:
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io()
</script>
客户端导入的这个js文件中,暴露了io函数,调用得到一个socket对象。
socket其实一个套接字,类似于插座,此时服务器有一个插座,客户端有一个插头,只要能将两边连接在一起就能进行通信了。
得到的socket这个套接字对象,他的使用方法是使用事件的机制:
socket对象有一个emit方法,用来触发事件 - 发送消息
socket对象有一个on方法,来绑定事件 - 监听是否消息接收到
// 接收消息
socket.on('message',message=>{
// 监听message事件,只要对方触发了message事件,message就是对方发送过来的数据
console.log(message)
})
// 发送消息
socket.emit('send',数据) // 表示触发了事件send,并在触发事件的时候将数据传送了过去
服务器的操作:
我们创建好的io对象需要绑定一个固定的事件 - connection,如果有客户端连接过来,就会触发这个事件
io.on('connection',socket=>{
// 有客户端连接来,会得到一个socket对象,这个socket跟客户端的socket是同一个,就表示插头和插座连接在一起了,所有这个socket也可以进行绑定事件和触发事件
// 通信
socket.emit('message',数据)
socket.on('send',数据)
})
上面的message事件和send事件都是自定义的,不是固定的,且客户端和服务器进行交互的时候,使用的都是js,所以他们之间进行交互的数据,只要符合js的数据类型就能进行,比如字符串、数字、对象、数组 。。。
此时只是实现了一个客户端和一个服务器之间的即时通信,如果要多个客户端和一个服务器进行通信的话,还需要服务器将数据进行广播:
io.emit('message',数据)
服务器进行响应的时候,不能只用socket进行发送数据,需要使用io,就代表所有客户端了。
jQuery
jQuery是一个前端库,也是一个方法库- 他里面封装着一些列的方法供我们使用
- 我们常用的一些方法它里面都有,我们可以直接拿来使用就行了
jQuery之所以好用,很多人愿意使用,是因为他的几个优点太强大了- 优质的选择器和筛选器
- 好用的隐式迭代
- 强大的链式编程
- 因为这些东西的出现,很多时候我们要做的事情被 “一行代码解决”
jQuery 的使用
-
官网 : jquery.com/
-
中文网站 : jquery.cuishifeng.cn/
-
我们要使用
jQuery首先要下载一个- 可以去官网下载,也可以直接百度搜索下载,都可以
- node中下载指令:
npm i jquery
-
然后就是再页面里面引入
jQuery.js就行了<script src="./node_modules/jquery/dist/jquery.js"></script> -
jQuery向全局暴露的两个接口就是jQuery和$,这两个是一样的
选择器和筛选器
- 选择器和筛选器就是用来帮我们获取 DOM 元素的
选择器
-
语法:
$(选择器)jQuery(选择器)-
选择器的写法和css中一模一样
-
返回值: 是一个jq的伪数组集合对象,集合中的是每一个根据选择器获取的页面元素
const ele = jQuery('#box') const ele = $('#box') const eles = $('.a') const eles = $('ul > li')
-
特殊选择器
-
直接找到第一个
$('li:first') // 找到所有 li 中的第一个 -
直接找到最后一个
$('li:last') // 找到所有 li 中的最后一个 -
直接找到第几个
$('li:eq(3)') // 找到所有 li 中索引为 3 的那个 -
找到所有奇数个
$('li:odd') // 找到所有 li 中索引为 奇数 的 -
找到所有偶数个
$('li:even') // 找到所有 li 中索引为 偶数 的
筛选器
- jq的筛选器是jq集合对象的方法,作用是对象之前集合中的元素二次筛选
-
找到所有元素中的第一个
$('li').first() -
找到所有元素中的最后一个
$('li').last() -
找到某一个元素的下一个兄弟元素
$('li:eq(3)').next() -
找到某一个元素的上一个兄弟元素
$('li:eq(3)').prev() -
找到某一个元素的后面的所有兄弟元素
$('li:eq(3)').nextAll() -
找到某一个元素的前面的所有兄弟元素
$('li:eq(3)').prevAll() -
找到某一个元素的父元素
$('li:eq(3)').parent() -
找到某一个元素的所有结构父级,一直到 html
$('li:eq(3)').parents() -
找到其他兄弟元素
$('#fid').siblings() -
找到第几个(索引)元素
$('li').eq(1) -
找到后代相匹配的元素
$('ul').find('.cls1') -
获取索引
$('.cls1').index() -
找到一组元素中的某一个
// 在 li 的所有父级里面找到所有 body 标签 $('li').parents().find('body') // 找到 div 标签下所有后代元素中所有类名为 box 的元素 $('div').find('.box')
-
元素内容操作
-
text()获取设置标签的文本内容console.log( $('#dv').text() ) // 获取 $('#dv').text('888') // 设置 覆盖性设置 $('#dv').text('<h1>888</h1>') // 设置 覆盖性设置 -
html()获取设置标签的超文本内容console.log( $('#dv').html() ) // 获取 $('#dv').html('888') // 设置 覆盖性设置 $('#dv').html('<h1>888</h1>') // 设置 覆盖性设置 -
val()获取设置表单标签的内容console.log( $('input').val() ) // 获取 $('input').val('888') // 设置 覆盖性设置
属性操作
-
获取设置属性
-
attr()可以设置所有属性console.log( $('div').attr('id') ) // 获取属性名对应的属性值 console.log( $('div').attr('index') ) // 获取属性名对应的属性值 // 设置属性 $('div').attr('sid','123'); $('div').attr('id','666'); -
prop()可以获取设置属性- prop获取不到自定义属性
- 通过prop设置的自定义属性不会在标签中显示,而且只能通过prop获取
console.log( $('div').prop('id') ) $('div').prop('id',999);
-
-
remveAttr(属性名)移除属性$('div').removeAttr('id') $('div').removeAttr('index') -
removeProp(属性)移除属性- removeProp只能移除通过prop设置的自定义属性
$('div').removeProp('id') $('div').removeProp('index')
类名操作
-
添加类名
$('div').addClass('c666'); -
移除类名
$('div').removeClass('c2'); -
判断类名是否存在
console.log( $('div').hasClass('c1') ) console.log( $('div').hasClass('c7') ) -
类名切换
- 有则删除,无则添加
document.onclick = ()=>{ $('div').toggleClass('c7'); }
样式操作
-
获取样式
console.log( $('div').css('width') ) console.log( $('div').css('background-color') ) -
设置样式
-
设置单个样式
$('div').css('width',200) -
批量设置样式
$('div').css({ width:200, height:300, backgroundColor:'pink' })
-
事件绑定
-
on(事件类型,事件处理函数)$('.out').on('click',()=>console.log( 'out' ))$('.inner').on('click',()=>{ console.log('inner'); return false; // jq的事件中阻止冒泡 })$('a').on('click',()=>{ console.log( 'a' ) return false; // jq中阻止默认行为 })- jq的处理函数除了**
return false;**也可以使用事件对象阻止默认行为和阻止事件传播
- jq的处理函数除了**
-
on(事件类型,选择器,事件处理函数)事件委托将inner的点击事件委托给out来绑定 $('.out').on('click','.inner',function(){ console.log( '点击的是inner' ) console.log( this ) // 是触发事件的元素 .inner }) -
on(事件类型,非字符串类型数据,事件处理函数)给事件处理函数传递数据$('.out').on('click',{a:1,b:2},function (e) { // e 就是事件对象 // console.log( e ) // 给处理函数传递的数据在事件对象的data属性中 console.log( e.data ) }) -
on(事件类型,选择器,数据,事件处理函数)给事件处理函数传递数据- 第二个参数
''表示不做事件委托
$('.out').on('click','','{a:1,b:2}',function (e) { // e 就是事件对象 // console.log( e ) // 给处理函数传递的数据在事件对象的data属性中 console.log( e.data ) }) - 第二个参数
-
on({事件类型:事件处理函数,事件类型:事件处理函数....})元素批量事件绑定$('.out').on({ click:()=>console.log( 'click' ), dblclick:()=>console.log( '双击' ), mouseenter:()=>console.log( '移入' ) }) -
one()给元素绑定事件的方式和on用法一样- 但是one绑定的事件只能触发一次
$('.out').one('click',()=>console.log( 666 )) -
off(事件类型,事件处理函数)事件解绑off(事件类型)解绑元素的所有处理函数off(事件类型,事件处理函数)解绑对应的处理函数
$('.out').off('click') // 解绑所有处理函数 $('.out').off('click',f2) // 解绑f2处理函数 -
trigger(事件类型)通过代码触发事件$('.out').on('click', () => console.log('click')); setTimeout(() => { $('.out').trigger('click'); }, 2000) -
常用事件
-
jq已将我们常用的事件封装成了方法: 比如
click()dbclick()input()...$('.inner').click(()=>console.log( 'click' )) -
一般一个方法就是一个事件
-
-
特殊事件
- jq页面加载事件: 当页面将html结构加载完毕的时候触发
- 语法:
$(window).ready(函数) - 简写:
$(函数)
-
hover()表示两个事件---鼠标移入移除事件-
一般一个方法就是一个事件
$('.inner').hover(function () { // 鼠标移入事件处理函数 console.log( 'enter' ) },function () { // 鼠标移出事件处理函数 console.log( 'out' ) })
-
节点操作
-
创建元素节点
$('要创建元素的闭合标签')console.log( $('<p></p>') ) console.log( $('<p>1234</p>') ) console.log( $('<p><b>1234</b></p>') ) -
向元素内插入节点 父子关系
-
父节点.append(子节点)将子节点追加到父节点里面的最后$('div').append($('<p><b>666</b></p>')) -
子节点.appendTo(父节点)将子节点追加到父节点里面的最后$('<p>666</p>').appendTo($('div')) -
父节点.prepend(子节点)将子节点追加到父节点里面的最前面$('div').prepend($('<p><b>666</b></p>')) -
子节点.prependTo(父节点)将子节点追加到父节点里面的最前面$('<p>888</p>').prependTo($('div'))
-
-
向元素的前后追加元素 兄弟关系
-
节点A.after(节点B)将节点B追加到节点A的后面$('div').after($('<p>9999</p>')) -
节点B.insertAfter(节点A)将节点B追加到节点A的后面$('<p>0000</p>').insertAfter($('div')); -
节点A.before(节点B)将节点B追加到节点A的前面$('div').before($('<p>9999</p>')) -
节点B.insertBefore(节点A)将节点B追加到节点A的前面$('<p>0000</p>').insertBefore($('div'));
-
-
删除节点
-
empty()移除元素的所有内容$('div').empty() -
remove()将元素移除$('div').remove()
-
-
元素节点替换
-
元素A.replaceWith(元素B)将元素B替换掉元素A$('div>span').replaceWith($('<p>6666</p>')); -
元素B.replaceAll(元素A)将元素B替换掉元素A$('<p>888</p>').replaceAll($('div>span'));
-
-
元素克隆
-
clone()- 参数1: 默认为false, 表示默认元素本身的事件不会克隆
- 参数2: 默认跟随参数1, 默认值也是false, 表示默认元素的子元素的事件不会克隆
let cDiv = $('.c').clone(true, false) // 将克隆的元素追加到body中 $('body').append(cDiv);
-
元素的尺寸
-
获取设置元素的宽高尺寸 (内容的宽高)
height()width()
// 获取 console.log( $('div').height() ) console.log( $('div').width() ) // 设置 $('div').height(200) $('div').width(100) -
获取设置元素的宽高尺寸 (内容的宽高+padding)
innerHeight()innerWidth()
// 获取 console.log( $('div').innerHeight() ) console.log( $('div').innerWidth() ) // 设置 $('div').innerHeight(200) $('div').innerWidth(0) -
取设置元素的宽高尺寸 (内容的宽高+padding+border)
outerHeight()outerWidth()- 参数:如果传入参数true,则将margin纳入计算中
// 获取 console.log( $('div').outerHeight() ) console.log( $('div').outerWidth() ) // 如果传入一个参数true,则获取的是 内容的宽高+padding+border+margin的尺寸 console.log( $('div').outerHeight(true) ) console.log( $('div').outerWidth(true) ) // 设置 $('div').outerHeight(200) $('div').outerWidth(100) // 如果传入二个参数true,则设置的是 内容的宽高+padding+border+margin的尺寸 console.log( $('div').outerHeight(100,true) ) console.log( $('div').outerWidth(200,true) )
元素的位置
-
获取设置元素的位置定位 (相对于页面的)
offset()
console.log( $('div').offset() ) // {top: 108, left: 8} console.log( $('span').offset() ) // {top: 128, left: 18} $('span').offset({top:100,left:-20}) -
获取元素相对于定位父级的定位
position()
console.log( $('span').position() ); // {top: 20, left: 10} $('span').position({top:100}) // 不能设置,无效
动画
-
显示隐藏动画
-
show()显示动画- 参数1: 运动时间 ms 选填
- 参数2: 运动曲线( linear swing) 选填
- 参数2: 运动结束的回调 选填
$('div').css('display', 'none'); $('button').click(() => $('div').show()) $('button').click(() => $('div').show(5000, 'linear', function () { console.log(this) })) -
hide()隐藏动画- 参数1: 运动时间 ms 选填
- 参数2: 运动曲线( linear swing) 选填
- 参数2: 运动结束的回调 选填
$('button').click(()=> $('div').hide()) $('button').click(()=> $('div').hide(3000,function(){console.log( this )})) -
toggle()显示隐藏的切换动画- 参数1: 运动时间 ms 选填
- 参数2: 运动曲线( linear swing) 选填
- 参数2: 运动结束的回调 选填
$('button').click(()=>$('div').toggle(1000))
-
-
折叠动画
-
slideDwon()下拉显示动画- 参数: 与👆**
显示隐藏动画**一致
$('div').css('display','none'); $('button').click(()=>$('div').slideDown(1000)) - 参数: 与👆**
-
slideUp()收起隐藏动画- 参数: 与👆**
显示隐藏动画**一致
$('button').click(()=>$('div').slideUp(1000)) - 参数: 与👆**
-
slideToggle()折叠动画(显示隐藏)- 参数: 与👆**
显示隐藏动画**一致
$('button').click(()=>$('div').slideToggle(1000)) - 参数: 与👆**
-
-
淡入淡出动画(透明度)
-
fadeIn()淡入(显示----透明度变为1)- 参数: 与👆**
显示隐藏动画**一致
$('div').css('display','none'); $('button').click(()=>$('div').fadeIn(3000)) - 参数: 与👆**
-
fadeOut()淡出(隐藏----透明度变为0)- 参数: 与👆**
显示隐藏动画**一致
$('button').click(()=>$('div').fadeOut(3000)) - 参数: 与👆**
-
fadeToggle()淡入淡出切换- 参数: 与👆**
显示隐藏动画**一致
$('button').click(()=>$('div').fadeToggle(3000)) - 参数: 与👆**
-
fadeTo()切换到指定透明度- 参数1: 时间 ms
- 参数2: 指定透明度
- 参数3: 运动曲线
- 参数4: 回调函数
$('button').click(()=>$('div').fadeTo(3000,.3))
-
多库并存
jq暴露了两个内容----$ 和 jQuery
console.log($ === jQuery)
假设 页面中导入了多个方法库, 而且也暴露了$ 或 jQuery
console.log( $===jQuery ) // false // 此时$就不具有jq的功能
-
交出$的控制权
jQuery.noConflict()- 交出就不在具有jq的功能,$**就可以被别的方法库来使用
-
交出jQuery的控制权
jQuery.noConflict(true)- 返回值通过变量接收,该变量又具有jq的功能
let my_$ = jQuery.noConflict(true);
获取时间戳
now()
console.log($.now())
插件扩展
在jq的基础上给 $ 和 jq 的集合对象添加方法
-
给jq的 $ , jQuery 添加方法
- 语法:
$.extend({})或者jQuery.extend({}) - 参数对象 中书写的方法函数,可以让$,jQuery使用
let obj = { getMax(...arg) { return Math.max(...arg); } } $.extend(obj); console.log( $.getMax(1,2,3,4,5) ) // 使用 - 语法:
-
给jq的元素集合添加方法
语法: $.fn.extend({})- 参数对象 中书写的方法函数,可以让jq的所有元素集合使用
// 给jq的元素结合添加一个 设置多选框全部够选的方法功能 $.fn.extend({ allSelect() { // console.log( this ) // 此处的this就是调用此方法的jq集合对象 // 返回原来的jq集合, 以保证我们添加的方法可以链式调用 return this.each((i, t) => { $(t).prop('checked', true); }) } })
遍历jq元素集合
each
$('input').each((i,t)=>{
// t 表示集合中的每一个元素
// i 就是对应的索引
console.log(i,t)
})
jq的ajax
-
$.get(请求地址,请求携带的参数,请求处理函数)$.get('http://localhost:8090/jq','name=zs&age=17',(res)=>{ console.log( res ) }) $.get('http://localhost:8090/jq',{name:'zs',age:18},(res)=>{ console.log( res ) }) -
$.post(请求地址,请求携带的参数,请求处理函数)$.post('http://localhost:8090/jq','name=zs&age=17',(res)=>{ console.log( res ) }) $.post('http://localhost:8090/jq',{name:'ls',age:18},(res)=>{ console.log( res ) }) -
$ajax({})/* $.ajax({ url: 请求地址,必填 method: 请求方式 选填 默认get data: 请求携带的数据 dataType: 请求响应的数据是否需要json转换 success: 请求成功的回调函数 error: 请求失败的回调函数 }) */ $.ajax({ url: 'http://localhost:8090/jq', data: { name: 'zs', age: 17 }, method: 'get', success(res) { console.log(res) } })
bootstrap
bootstrap 是基于类的前端UI框架
-
使用步骤
-
下载:v3.bootcss.com/ 或
npm i bootstrap -
引入包:
<link rel="stylesheet" href="bootstrap.css"> -
使用:写标签,加类名
按钮🌰: <span class="btn btn-primary btn-sm">按钮</span> <a href="javascript:;" class="btn btn-danger btn-md">按钮</a> <div class="btn btn-warning btn-lg">按钮</div>表格🌰: <table class="table table-bordered"> <tr class="success"> <td></td> <td></td> </tr> <tr class="info"> <td></td> <td></td> </tr> </table>字体图标🌰: <span class="glyphicon glyphicon-heart"></span> <i class="glyphicon glyphicon-refresh"></i>
-
-
bootstrap栅格布局
-
屏幕大小:大屏>=1200px ,992px<=中屏<1200px,768px<=小屏<992px,超小屏<768px
-
类名:
container:响应式容器-版心,会随着屏幕大小的改变而改变 - 左右各有padding:15pxrow:表示行,靠左对齐,左右各有margin:-15px;col:表示列,表示在什么样的屏幕下占据父元素宽度的12分之几col-lg-数字:表示在大屏下占十二分之几col-md-数字:表示在中屏下占十二分之几col-sm-数字:表示在小屏下占十二分之几col-xs-数字:表示在超小屏下占十二分之几
hidden-lg/md/sm/xs在对应的屏幕下是隐藏的
栅格布局🌰:<-- 颜色样式已省略 --> <div class="container"> <div class="row"> <div class="red col-lg-2 hidden-md hidden-sm hidden-xs"></div> <div class="yellow col-lg-2 hidden-md hidden-sm hidden-xs"></div> <div class="green col-lg-2 col-md-3 hidden-sm hidden-xs"></div> <div class="blue col-lg-2 col-md-3 col-sm-4 hidden-xs"></div> <div class="orange col-lg-2 col-md-3 col-sm-4 col-xs-6"></div> <div class="pink col-lg-2 col-md-3 col-sm-4 col-xs-6"></div> </div> </div>
-
H5
h5 是html5 的简称, 就是html的第五版
h5新增了一些标签和接口(提供给js使用的),css3
h5主要是为了移动端的优化,用户体验更好--pc端对h5不友好
语义化标签
-
所谓语义化标签,其实是有特殊的意义,但不一定会有特殊的表现
-
html5中的语义化标签都是块级元素
<header>头部</header> <nav>导航栏</nav> <section>块级内容</section> <article>内容区域</article> <aside>侧边栏</aside> <footer>底部</footer>
表单标签
<fieldset></fieldset>:表单集合标签,一般用于把表单内几个表单元素包裹在一起,当做一个整体来设置,并且会出现一个边框- **
disabled**属性,可以让包裹在内的所有表单元素都禁用 legend:出现在fieldset标签的边框上,作为表单的标题
- **
表单新属性
- 颜色选择框:
type="color" - 电话号码输入框:
type="tel" - 搜索输入框:
type="searth" - 拉杆:
type="range" - 数字输入框:
type="number" - 邮件输入框:
type="email" - 网址输入框:
type="url" - 日期选择框:
type="date" - 月份选择框:
type="month" - 周选择框:
type="week" - 时间选择框:
type="time" - 年月日时分选择框:
type="datetime-local"required必填项min最小值max最大值minlength最小长度maxlength最大长度step步长placeholder:占位autocomplete:提示出现,值是on或offpattern:值是一个正则,可以让表单数据按照正则来约束 - 正则不需要斜杠multiple:用于type为file的时候,可以让文件选择器选择多个文件- **
datalist**标签,里面放多个option标签,但要跟input关联,input的list属性要和datalist标签的id值保持一致,可以形成一个可输入的下拉列表,option的内容和value都会显示。
邮箱: <input type="email" name="email" placeholder="请输入邮箱" autocomplete="off"> <br>
数字: <input type="number" max="10" min="1" step="2" value="1"> <br>
电话: <input type="tel" required value="1"> <br>
搜索: <input type="search" pattern="^\d+$"> <br>
网址: <input type="url" > <br>
日期: <input type="date" > <br>
月份: <input type="month" > <br>
几周: <input type="week" > <br>
进度: <input type="range" max="100" min="1" value="20"> <br>
颜色: <input type="color" > <br>
文件: <input type="file" multiple> <br>
<-- 提示选项:datalist,list 用于设置跟哪个datalist进行绑定 -->
请输入水果网址: <input type="text" list="ls">
<datalist id="ls">
<option value="www.pinguo.com">苹果</option>
<option value="www.xigua.com">西瓜</option>
<option value="www.xiangjiao.com">香蕉</option>
</datalist>
多媒体标签
video
-
只接受几种视屏格式:ogg、mp4、avi
-
基本使用:
<video src="视屏文件路径"></video> <!-- 兼容写法 --> <video> <source src="路径1" type="video/mp4"></source> <source src="路径2" type="video/ogg"></source> <source src="路径3" type="video/avi"></source> </video>- **
controls**属性,出现默认的控制面板 - **
autoplay**属性,自动播放 - **
loop**属性,循环播放 - **
width和height**属性,用来设置视屏可视区域的尺寸,但是宽和高一直会保持等比,所以设置一个就行了,如果都设置了,会出现黑边,但可视区域是等比的
- **
audio
-
只接受ogg和mp3格式,使用方式和video是一样的
-
基本使用:
<audio src="视屏文件路径"></audio> <!-- 兼容写法 --> <audio> <source src="路径1" type="audio/mp3"></source> <source src="路径2" type="audio/ogg"></source> </audio>- **
controls**属性,出现默认的控制面板 - **
autoplay**属性,自动播放 - **
loop**属性,循环播放
- **
多媒体标签的api
在谷歌浏览器中,默认不能自动播放,默认直接调用play方法播放,需要一个自定义按钮来解决或设置video静音
-
方法
video/audio.play()播放video/audio.pause()暂停
-
属性
video.duration视屏总时长video.muted设置媒体静音,值为true或false,获取媒体是否静音video.volume获取媒体当前声音(01),设置声音(01)video.currentTime获取媒体当前时间,设置当前时间,单位秒video/audio.paused查看媒体是否暂停video/audio.playbackRate获取/设置播放倍速
-
事件
loadstart:视屏开始加载时触发progress:浏览器正在下载视屏时触发 - 相当于在加载canplay:媒体加载完毕,可以播放的时候触发play:视屏正在播放的时候触发pause:视屏暂停的时候触发seeking:视屏开始要跳到新位置的时候触发seeked:视屏已经跳到新位置的时候触发waiting:视屏加载等待时触发timeupdate:只要播放时间更改就会触发ended:媒体播放结束时触发error:视屏播放错误时触发volumechange:视屏音量改变时触发ratechange:视屏播放速度更改时触发
canvas
**canvas**是html5的一个标签,代表一个画布,可以在上面进行绘画、动画等操作。画布默认大小是300*150
canvas标签本身只是画布,要实现上面有文字、线条等呈现,需要使用js实现。总之,画布上一切的呈现,都需要使用js来实现。canvas标签本质上就是一张图片,只是一张空白图片。
画布大小不能使用样式控制,用样式调整的是一个可视区域,其实真实的大小,还是一样的,只是在画布上画内容的话,会等比例放大。调整画布大小,需要在标签上直接添加width和height属性。
<canvas style="background-color: #fff; border: 1px solid red;" width="800" height="800"></canvas>
canvas标签也是可以放文字的,只是当canvas标签不被浏览器支持的时候,会显示,例如ie8
初体验
-
获取canvas元素
var canvas = document.querySelector('canvas') -
获取这个元素的工具箱 - 上下文
- 工具箱中包含了很多工具:直线工具、弧形工具、文本工具、矩形工具...... 我们需要依赖这些工具进行绘画
var ctx = canvas.getContext('2d') -
画图形
-
重新开始画布路径
ctx.beginPath() -
将画笔放到一个指定像素点,默认直线工具
ctx.moveTo(x轴坐标,y轴坐标) ctx.moveTo(50,50) -
以线的形式,将画笔移动到终点
ctx.lineTo(200,100) -
已经有线了,但页面没有显示
-
填充--会自动闭合
ctx.fill() -
描边(划线)--默认黑色,线的宽度是1px
ctx.stroke()
此时在画布中出现了一条线,但是发现线的粗细不是1px,颜色也不是黑色。
这是因为,canvas绘制线条,会将线条的中心点跟坐标点的位置进行对齐,这样的话,不管是线的开始对准坐标点还是线的结束对准中心点都不合适,所以浏览器干脆将线强行拉伸到 坐标点-1~坐标点+1,也就是本来1px的线,被强制拉伸到了2px,这样的话,颜色也就浅了。
我们可以将线的宽度设置为2px做验证,因为2px就不用被拉伸了
ctx.lineWith = 2 // 值是数字ctx.lineTo(200,100)结果就是2px宽的线,纯黑色了。
基于这个问题,我们以后在绘图的时候,尽量将像素设置为双数。
-
-
线条宽度
ctx.lineWidth = 4; -
设置填充颜色
ctx.fillStyle = 'skyblue'; -
设置描边颜色
ctx.strokeStyle = 'red'
-
画三角形
-
闭合:
-
手动闭合,再次画一次开始的线段
-
自动闭合 - 需要在描边之前闭合
ctx.closePath() // 会自动将开始和结束的边进行闭合 -
依靠填充的方式闭合
ctx.fill() // 填充颜色默认是黑色
描边和填充可以一起使用,也可以单独使用,但是都有各自的规则:
描边会把填充的内容扩大
描边的颜色默认是黑色,使用
ctx.strokeStyle来设置描边颜色填充的颜色默认是黑色,使用
ctx.fillStyle来设置填充颜色 -
回形填充
需求:大正方形:200 * 200;小正方形:100 * 100;出现在画布的最中心;线宽2;
获取画布的宽度和高度:
ctx.canvas.width
ctx.canvas.height
通过线段描边,得到两个正方形,填充规则 - 非零填充规则:
在任何一个填充的区域,向外(到外围canvas的区域)拉一条线,数数经过了几个顺时针和逆时针进行计算:
顺时针+1,逆时针-1
最后得到的数字不是0,就填充;是0,就不填充
线段两端样式
ctx.lineCap = 值;butt- 默认是没有 - 不会超出线段区域round- 圆型,会超出线段区域square- 矩形形状,会超出线段区域
线段与线段的交接处样式
ctx.lineJoin = 值;miter- 默认round- 圆弧形状bevel- 平角形状
画虚线
需要在绘制线条之前,设置线条的样式为虚线:
-
ctx.setLineDash(参数)参数: 数组 - 虚线方案,在数组中描述线条和空白的长度,然后不停的重复 两个值:第一个值是线条长度,第二个值是空白长度 - 重复 三个值:第一个值是线条长度,第二个值空白长度,第三个值是线条的长度;接下来是第二个值的空白长度,第二个值是线条的长度,第三个值是空白的长度 - 重复 四个值:第一个值是线条长度,第二个值是空白长度,第三个值是线条的长度,第四个值是空白长度 - 重复 总结: 数组中有奇数个元素,那重复的个数就是 2*奇数个 数组中有偶数个元素,那重复的个数就是偶数个 -
获取虚线的方案:
ctx.getLineDash() // 获取到的是一个数组,数组中记录了一段不重复的虚线方案
练习:利用循环画纯色渐变的线条:
ctx.lineWidth = 10
for(var i=0;i<256;i++){
ctx.beginPath()
ctx.moveTo(100+i,100)
ctx.lineTo(100+i+1,100)
ctx.strokeStyle = `rgb(255,${255-i},${255-i})`
ctx.closePath()
ctx.stroke()
}
画矩形
-
设置矩形的起点和宽高:
ctx.rect(起点x,起点y,宽,高) context.rect(20,20,150,80); -
绘制矩形并描边
ctx.strokeRect(起点x,起点y,宽,高) ctx.strokeRect(200,20,150,80) -
绘制矩形并填充
ctx.fillRect(起点x,起点y,宽,高) ctx.fillRect(20,200,200,100)
当进行多个矩形填充不同颜色的时候,不需要结束路径,因为绘制矩形没有开启新的路径,绘制形状互相是独立的。
-
清除矩形:
- 清除矩形是将指定范围内的东西都清除掉
ctx.clearRect(起点x,起点y,宽,高)
渐变
设置颜色的时候是可以设置渐变的。渐变跟形状无关,在画布中确定好渐变的区域和颜色之后,在这个区域内的图形,填充或描边时可以将颜色设置成渐变。
-
设置渐变方案:
ctx.createLinearGradient(开始x,开始y,结束x,结束y) // 返回渐变方案 -
设置渐变颜色
渐变方案.addColorStop(数字,颜色) 取值: 范围是0~1,0代表开始,1代表结束渐变方案可以直接赋值给填充或描边颜色的 🌰: // 设置渐变方案 var linearGradient = ctx.createLinearGradient(100,100,500,100); // 添加渐变颜色 linearGradient.addColorStop(0,'red'); linearGradient.addColorStop(0.5,'green'); linearGradient.addColorStop(1,'blue'); // 将渐变方案设置给填充颜色 ctx.fillStyle = linearGradient; // 绘制矩形 ctx.fillRect(150,150,500,200)
画弧线
画弧线是画一个路径,后面需要填充或描边。
ctx.arc(圆心x,圆心y,半径,开始弧度,结束弧度)
弧度:当圆形上的边跟半径相等的时候就是一个弧长,弧度其实就是弧长的个数。
弧度和角度的换算:
周长 = π * 半径 * 2
一周长是360度,1弧长是半径,所以:
1弧长对应的角度 = 180 / π
新角度/计算出来的角度 = 新弧长 / 1弧长
新弧长 = 1弧长 * 新角度 / 计算出来的角度
新弧长 = r * 60 * π / 180
弧度 = 角度 * π / 180
横向x轴向右的角度是0度,顺时针是正角度,所以画一个0度到60度的弧形:
// 圆心是100*100,半径是100
ctx.arc(100,100,100,0,60 * Math.PI/180)
ctx.stroke()
ctx.fill()
将这个弧形和半径进行填充就能得到一个扇形
ctx.moveTo(100,100)
ctx.arc(100,100,100,0,Math.PI/3)
ctx.stroke()
ctx.fill()
画文字
ctx.strokeText(文本,文字开始x,文字开始y) // 描边文字
ctx.fillText(文本,文字开始x,文字开始y) // 填充文字
-
文字大小:
ctx.font = '字号大小 字体' // 字号大小就是 数字px 🌰: ctx.font = '50px 楷体'; -
上下对齐方式:
ctx.textBaseLine = 值; /* 取值: 默认是baseline bottom:底线对齐 top:顶线对齐 middle:中线对齐 */ 🌰: ctx.textBaseline = 'bottom'; -
左右对齐方式:
ctx.textAlign = 值; /* 取值:基于我们设置的文字坐标来对齐的 left - 默认值,左对齐 right - 右对齐 */ 🌰: context.textAlign = 'left'; -
获取文本内容的总宽度:
ctx.measureText(文本内容) // 获取到一个对象,其中包含width属性就是文字的宽度
SASS
-
官网:www.sass.hk
-
号称世界上最成熟、最稳定、最强大的专业级CSS扩展语言!
-
sass是一个 css 的预编译工具 -
也就是能够 更优雅 的书写 css
-
sass写出来的东西 浏览器不认识 -
依旧要转换成 css 在浏览器中运行
-
这个时候就需要一个工具来帮我们做
安装 sass 环境
-
以前的
sass需要依赖一个ruby的环境 -
现在的
sass需要依赖一个python的环境 -
但是我们的 node 强大了以后,我们只需要依赖
node环境也可以 -
需要我们使用 npm 安装一个全局的
sass环境就可以了# 安装全局 sass 环境 $ npm install sass -g
编译 sass
-
有了全局的
sass环境以后 -
我们就可以对
sass的文件进行编译了 -
sass的文件后缀有两种,一种是.sass一种是.scss -
他们两个的区别就是有没有
{}和; -
.scss文件h1 { width: 100px; height: 200px; } -
.sass文件h1 width: 100px height: 200px -
我们比较常用的还是
.scss文件 -
因为
.sass我们写不习惯 -
不管里面的内容是什么,至少这个
.scss或者.sass文件浏览器就不认识 -
我们要用指令把 这两种 文件变成 css 文件
# 把 index.scss 编译,输出成 index.css $ sass index.scss index.css -
这样我们就能得到一个 css 文件,在页面里面引入一个 css 文件就可以了
实时编译
-
我们刚才的编译方式只能编译一次
-
当你修改了文件以后要从新执行一遍指令才可以
-
实时编译就是随着你文件的修改,自动从新编译成 css 文件
-
也是使用指令来完成
# 实时监控 index.scss 文件,只要发生修改就自动编译,并放在 index.css 文件里面 $ sass --watch index.scss:index.css -
然后你只要修改
index.scss文件的内容,index.css文件中的内容会自动更新
实时监控目录
-
之前的实时监控只能监控一个文件
-
但是我们有可能要写很多的文件
-
所以我们要准备一个文件夹,里面放的全部都是 sass 文件
-
实时的把里面的每一个文件都编译到 css 文件夹里面
-
依旧是使用指令的形式来完成
# 实时监控 sass 这个目录,只要有变化,就会实时响应在 css 文件夹下 $ sass --watch sass:css -
这样,只要你修改 sass 文件夹下的内容,就会实时的相应在 css 文件夹中
-
你新添加一个文件也会实时响应
-
但是你删除一个文件,css 文件夹中不会自动删除,需要我们自己手动删除
sass 语法
- 我们能编译
sass文件了,接下来我们就该学习一下sass的语法了 - 为什么他这么强大,这么好用,都是靠强大的语法
.sass和.scss文件的语法是一样的,只不过区别就是{}和;
变量
-
定义一个变量,在后面的代码中使用
-
使用
$来定义变量// 定义一个 $c 作为变量,值是 红色 $c: red; h1 { // 在使用 $c 这个变量 color: $c; } -
上面定义的变量全局都可以使用
-
我们也可以在规则块内定义私有变量
h1 { // 这个 $w 变量只能在 h1 这个规则块中使用 $w: 100px; width: $w; }
嵌套
-
sass里面我们最常用到的就是嵌套了 -
而且相当的好用
h1 { width: 100px; div { width: 200px; } } // 编译结果 h1 { width: 100px; } h1 div { width: 200px; } -
这个就是嵌套,理论上可以无限嵌套下去
ul { width: 100px; li { width: 90px; div { width: 80px; p { width: 70px; span: { color: red; } } } } }
嵌套中的 &
-
在嵌套中还有一个标识符是
&我们可以使用 -
先来看一个例子
div { width: 100px; height: 100px; :hover { width: 200px; } } // 我想的是 div 被鼠标悬停的时候 width 变成 200 // 但是编译结果却是 div { width: 100px; height: 100px; } div :hover { width: 200px; } -
和预想的结果不一样了
-
这个时候就要用到
&来连接了div { width: 100px; height: 100px; &:hover { width: 200px; } } // 编译结果 div { width: 100px; height: 100px; } div:hover { width: 200px; } -
这个时候就和我需要的一样了
群组嵌套
-
群组嵌套就是多个标签同时嵌套(一对多)
div { width: 100px; .box1, .box2, .box3 { color: red; } } // 编译结果 div { width: 100px; } div .box1, div .box2, div .box3 { color: red; } -
还有一种就是多个标签同时嵌套一个标签(多对一)
h1, h2, h3 { width: 100px; .box { color: red; } } // 编译结果 h1, h2, h3 { width: 100px; } h1 .box, h2 .box, h3 .box { color: red; }
嵌套属性
-
在
scss里面还有一种特殊的嵌套 -
叫做 属性嵌套
-
和选择器嵌套不一样,是写属性的时候使用的
div { border: { style: solid; width: 10px; color: pink; } } // 编译结果 div { border-style: solid; border-width: 10px; border-color: pink; } -
这个属性嵌套还可以有一些特殊使用
div { border: 1px solid #333 { bottom: none; } } // 编译结果 div { border: 1px solid #333; border-bottom: none; }
混入
-
也叫 混合器
-
其实就是定义一个 “函数” 在
scss文件中使用// 定义一个混合器使用 @mixin 关键字 @mixin radius { -webkit-border-radius: 10px; -moz-border-radius: 10px; -ms-border-radius: 10px; -o-border-radius: 10px; border-radius: 10px; } -
上面是定义好的一个混合器
-
他是不会被编译的,只有当你使用了他以后,才会被编译
// 使用混合器使用 @include 关键字 div { width: 100px; height: 100px; @include radius; } -
这个就是把刚才定义的混合器拿过来使用
-
编译结果
div { width: 100px; height: 100px; -webkit-border-radius: 10px; -moz-border-radius: 10px; -ms-border-radius: 10px; -o-border-radius: 10px; border-radius: 10px; }
混合器传参
-
我们既然说了,混合器就像一个 “函数” 一样,那么就一定可以像 “函数” 一样传递参数
-
和 “函数” 的使用方式一样,在定时的时候是 “形参”,在调用的时候是 “实参”
// 定义混合器 @mixin my_transition($pro, $dur, $delay, $tim) { -webkit-transition: $pro $dur $delay $tim; -moz-transition: $pro $dur $delay $tim; -ms-transition: $pro $dur $delay $tim; -o-transition: $pro $dur $delay $tim; transition: $pro $dur $delay $tim; } -
使用这个混合器的时候传递 “实参”
div { width: 100px; height: 100px; @include my_transition(all, 1s, 0s, linear); } -
编译结果
div { width: 100px; height: 100px; -webkit-transition: all 1s 0s linear; -moz-transition: all 1s 0s linear; -ms-transition: all 1s 0s linear; -o-transition: all 1s 0s linear; transition: all 1s 0s linear; } -
写了多少个 “形参”,那么调用的时候就要传递多少个 “实参”
-
不然会报错的
参数默认值
-
我们在定义混合器的时候,也可以给一些参数写一些默认值
-
这样一来,就可以不传递 “实参” 了
// 设置一些带有默认值的参数 @mixin my_transition($dur: 1s, $pro: all, $delay: 0s, $tim: linear) { -webkit-transition: $dur $pro $delay $tim; -moz-transition: $dur $pro $delay $tim; -ms-transition: $dur $pro $delay $tim; -o-transition: $dur $pro $delay $tim; transition: $dur $pro $delay $tim; } -
使用的时候,如果你不传递,那么就是使用默认值
div { width: 100px; height: 100px; // 使用的时候,只传递一个,剩下的使用默认值 @include my_transition(2s); } -
编译结果
div { width: 100px; height: 100px; -webkit-transition: 2s all 0s linear; -moz-transition: 2s all 0s linear; -ms-transition: 2s all 0s linear; -o-transition: 2s all 0s linear; transition: 2s all 0s linear; }
继承
-
在
sass里面使用继承可以大大的提高开发效率 -
其实继承很简单,就是把之前写过的选择器里面的内容直接拿过来一份
div { width: 100px; height: 100px; background-color: pink; } -
这个是之前写过的一个规则样式表
-
接下来我要写另外一个样式了,发现我要写的一些内容和之前这个 div 一样,并且还有一些我自己的内容
-
那么我就可以把这个样式表先继承下来,再写我自己的内容就好了
p { @extend div; font-size: 20px; color: red; } -
编译结果
div, p { width: 100px; height: 100px; background-color: pink; } p { font-size: 20px; color: red; }
注释
-
在
scss文件中的注释分为几种-
编译的时候不会被编译的注释
// 我是一个普通注释,在编译的时候,我就被过滤了 -
编译的时候会被编译的注释
/* 我在编译的时候,会被一起编译过去 */ -
强力注释
/*! 我是一个强力注释,不光编译的时候会被编译过去,将来压缩文件的时候也会存在 */
-
导入文件
-
我们刚才学过了定义变量,定义混合器
-
而这两个内容在定义过以后,如果没有使用,是不会被编译出内容的
-
所以我们可以把变量单独写一个文件,混合器单独写一个文件,然后直接导入后使用
// 我是 variable.scss $w: 100px; $h: 200px; $c: pink; // 我是 mixin.scss @mixin my_transition($dur: 1s, $pro: all, $delay: 0s, $tim: linear) { -webkit-transition: $dur $pro $delay $tim; -moz-transition: $dur $pro $delay $tim; -ms-transition: $dur $pro $delay $tim; -o-transition: $dur $pro $delay $tim; transition: $dur $pro $delay $tim; } @mixin radius { -webkit-border-radius: 10px; -moz-border-radius: 10px; -ms-border-radius: 10px; -o-border-radius: 10px; border-radius: 10px; } -
然后在我们的主要文件中把这个两个文件导入进来就行了
// 我是 index.scss @import './variable.scss'; @import './mixin.scss'; div { width: $w; height: $h; background-color: $c; @include radius; } h1 { @include my_transition; } -
编译结果
div { width: 100px; height: 200px; background-color: pink; -webkit-border-radius: 10px; -moz-border-radius: 10px; -ms-border-radius: 10px; -o-border-radius: 10px; border-radius: 10px; } h1 { -webkit-transition: 1s all 0s linear; -moz-transition: 1s all 0s linear; -ms-transition: 1s all 0s linear; -o-transition: 1s all 0s linear; transition: 1s all 0s linear; }