1、模板引擎概述
作用:使用模板引擎提供的模板语法,可以将数据和HTML拼接起来
实际上是实现在客户端做数据拼接
art-template模板引擎
官网:http://aui.github.io/art-template/zh-cn/
2、art-template模板引擎使用步骤
- 1、下载art-template模板引擎库文件并在html页面中引入库文件
<script src="./js/template-web.js"></script>
- 2、准备art-template模板
模板实际是一个代码片段
注意这个奇怪的使用方法,使用了‘text/html’的type,浏览器就会将相应所处的script标签内的内容当作html代码来解析了
<script id="tp1" type="text/html">
<div class="box"></div>
</script>
- 3、告诉模板引擎将哪一个模板和哪个数据进行拼接
var html=template('tp1',{username:'zhangsan',age:'20'})
- 4、将拼接好的html字符串添加到页面中
获取html中的容器,并将拼接好的html字符串放入容器内的innerHtml中即可
document.getElementById('container').innerHTML=html
- 5、通过模板语法(mustache语法)告诉模板引擎,数据和html字符串要如何拼接
<script id="tp1" type="text/html">
<div class="box">{{username}}</div>
</script>
案例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将模板引擎的库文件引入当前页面-->
<script src="./js/template-web.js"></script>
<title>Title</title>
</head>
<body>
here is my server
<div id="container"></div>
<!--2、准备art-template模板,实际就是代码段,没有单独的文件-->
<script id="tpl" type="text/html">
<div class="box">{{username}}</div>
<div class="box">{{age}}</div>
</script>
<script type="text/javascript">
//3、告诉模板引擎将哪个数据和哪个模板进行拼接
//1)模板id,2)数据 对象类型
//方法的返回值就是拼接好的字符串
var html=template('tpl',{username:'zhangsan',age:'20'})
console.log(html)
document.getElementById('container').innerHTML=html
</script>
</body>
</html>
3、验证邮箱地址唯一性
- 1、获取文本框并未其添加离开焦点事件
- 2、离开焦点时,检测用户输入的邮箱地址是否符合规则(使用正则)
- 3、如果不符合规则,阻止程序向下执行,并给出提示信息
- 4、想服务器端发送请求,检测邮箱地址是否被被人注册
- 5、根据服务器端返回值决定客户端显示何种提示信息
案例代码
需要做三个准备工作
- 1、提前封装好网络请求函数,笔记01里里面已经封装
- 2、使用正则表达式验证邮箱的合规性
- 3、在node里的app.js里面创建路由,并验证邮件地址是否唯一
app.js
//引入express框架
const express=require('express')
//引入路径处理模块
const path=require('path')
//使用post方式传参必须的模块
const bodyParser= require('body-parser')
//引入文件读取模块
const fs=require('fs')
//创建web服务器
const app=express();
//静态资源访问服务器功能
app.use(express.static(path.join(__dirname,'public')))
//解析post请求头,调用json的解析方法
app.use(bodyParser.json())
//创建路由
app.get('/verifyEmailAdress',(req,res)=>{
if (req.query.email=='zhangsan@126.com'){
res.send('email is ok')
}else{
res.send('email is unlaw')
}
})
//监听端口
app.listen(8848);
//控制台提示输出
console.log('服务器启动成功')
ajax.js(提前封装好的网络请求函数)
//封装一个ajax函数
function ajax(options){
//声明一个默认参数对象,存储默认参数
let defults={
type: 'get',
url:'',
data:{},
header:{
'Content-Type':'application/x-www-form-urlencoded'
},
success:()=>{},
error:()=>{}
}
//使用defaults覆盖options对象。用Object.assign()进行覆盖
//两个参数,用第二个参数覆盖第一个参数,使用options对象中的属性覆盖defaults对象中的属性
Object.assign(defults,options);
//创建ajax对象
let xhr=new XMLHttpRequest()
//在get传参方式下,对json数据对象转换成字符串
var params='';
//将对象转换成字符串格式
for (var attr in defults.data){
params += attr+'='+defults.data[attr]+'&'
}
//截取多串的&符,从第一个到倒数第二个
params=params.substr(0,params.length-1);
//对请求方式进行判断,如果为get则,将params放入open方法的url字符串里,如果是post,则将params放入send方法中
if (defults.type=='get'){
defults.url = defults.url+'?'+params
}
//配置ajax对象
xhr.open(defults.type,defults.url)
if (defults.type=='post'){
let contentType = defults.header['Content-Type'];
//如果为post还要调用setRequestHeader方法,设置请求参数格式的类型
xhr.setRequestHeader('Content-Type',contentType);//由于有横杠,所以对对象的访问不使用点句法,而使用[]+引号
//判断传入的文件头是否为json对象
if (contentType === 'applictaion/json'){
xhr.send(JSON.stringify(defults.data));
}else{
//如果不是json格式,则send普通参数
xhr.send(params);
}
}else{
//发送请求
xhr.send();
}
//处理响应信息
xhr.onload=()=>{
//xhr.getResponseHeader()获取响应头中的数据
let contentType= xhr.getResponseHeader('Content-Type')
//暂存服务器响应会的数据内容
let responseText=xhr.responseText;
//如果响应类型中包含'application/json'则说明服务器返回的是json数据
if (contentType.includes('application/json')){
responseText=JSON.parse(responseText)
}
//当http状态码等于200的时候
if (xhr.status==200){
//请求成功,调用处理成功情况的函数
options.success(responseText,xhr);
}else{
//请求失败,调用处理失败情况的函数
//把服务器返回的信息传给error函数
options.error(responseText,xhr)
}
}
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<style type="text/css">
.bg-danger{background-color:lemonchiffon; color: red}
.bg-success{background-color:greenyellow; color: black}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div class="container">
<div class="form-group">
<label>邮箱地址</label>
<input id="email" type="email" class="form-control" placeholder="请输入邮箱地址"/>
</div>
<!--错误的话添加bg-danger类名,正确添加bg-success类名-->
<p id="info"></p>
</div>
<script type="text/javascript">
let email=document.getElementById("email")
let info=document.getElementById("info")
email.onblur=function (){
let email=this.value;
//验证邮箱地址的正则表达式
let reg=/^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/
//验证用户输入的email是否正确
if(!reg.test(email)){
//阻止程序向下执行
info.innerText='您输入的邮箱地址有误'
info.className='bg-danger';
return;
}
info.className='bg-success';
//向服务器端发送请求
ajax({
type:'get',
data:{
email:email
},
url:'http://localhost:8848/verifyEmailAdress',
success:(result)=>{
console.log(result)
info.innerHTML=result;
},
error:(result)=>{
console.log(result)
info.innerHTML=result;
}
});
}
</script>
</body>
</html>
4、搜索框内容自动提示
- 1、获取搜索框并为其添加用户事件
- 2、获取用户输入的关键字
- 3、向服务器端发送请求并携带关键字作为请求参数
- 4、响应数据显示在搜索框底部
案例:
1、需要引入上例封装好的ajax网络请求函数
2、需要引入auitemplate库
3、node创建网络访问接口
node的app.js
app.get('/serachAutoPrompt',(req,res)=>{
let datas=['黑马程序员','黑马官网','黑马社区'];
console.log(req.query.key)
let thekey = req.query.key;
if (thekey=='黑' || thekey=='黑马'){
res.send(datas)
}
})
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.bg-danger{background-color:lemonchiffon; color: red}
.bg-success{background-color:greenyellow; color: black}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div class="container">
<div class="form-group">
<label>邮箱地址</label>
<input id="search" type="text" class="form-control" placeholder="请输入搜索关键字"/>
</div>
<ul class="list-group" id="list-box">
</ul>
</div>
<!--创建模板-->
<script id="tpl" type="text/html">
{{each resualt}}//模板语法
<li class="list-group-item">{{$value}}</li>
{{/each}}
</script>
<script type="text/javascript">
let serachInp=document.getElementById("search")
let listbox=document.getElementById("list-box")
//输入过程中触发
serachInp.oninput=function(){
//获取用户输入的内容
let key=this.value;
//向服务器端发送请求,索取和用户输入关键字相关的内容
ajax({
type:'get',
url:'http://localhost:8848/serachAutoPrompt',
data:{
key:key,
},
success:(result)=>{
let html=template('tpl',{resualt:result})
listbox.innerHTML=html;
listbox.style.display='block';
}
})
}
</script>
</body>
</html>
5、使用定时器防抖(过于频繁访问服务器)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.bg-danger{background-color:lemonchiffon; color: red}
.bg-success{background-color:greenyellow; color: black}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div class="container">
<div class="form-group">
<label>邮箱地址</label>
<input id="search" type="text" class="form-control" placeholder="请输入搜索关键字"/>
</div>
<ul class="list-group" id="list-box">
</ul>
</div>
<!--创建模板-->
<script id="tpl" type="text/html">
{{each resualt}}
<li class="list-group-item">{{$value}}</li>
{{/each}}
</script>
<script type="text/javascript">
let serachInp=document.getElementById("search")
let listbox=document.getElementById("list-box")
//存储定时器变量
let timer=null;
//输入过程中触发
serachInp.oninput=function(){
//每次触发时先把上次的定时器清除掉
clearTimeout(timer);
//获取用户输入的内容
let key=this.value;
//如果用户没有在搜索框输入内容
if(key.trim().length==0){
//将提示下拉框隐藏
listbox.style.display='none';
//阻止程序向下执行
return;
}
//使用定时器来防抖(过于频繁的访问服务器)
timer=setTimeout(()=>{
//向服务器端发送请求,索取和用户输入关键字相关的内容
ajax({
type:'get',
url:'http://localhost:8848/serachAutoPrompt',
data:{
key:key,
},
success:(result)=>{
console.log(result);
let html=template('tpl',{resualt:result})
listbox.innerHTML=html;
listbox.style.display='block';
}
})
},800)
}
</script>
</body>
</html>
6、省市区三级联动
- 1、通过接口获取省份信息
- 2、使用JavaScript获取到省市区下拉框元素
- 3、将服务器返回的省份信息显示在下拉框中
- 4、为下拉框元素添加表单值改变事件(onchange)
- 5、当用户选择省份时,更具省份id获取城市信息
本例一部分运算放在服务器端完成,实际上可以一次性在访问省份路由的情况下就返回给一个全局变量,然后三级联动的逻辑全部在客户端完成,就可以有效地降低服务器端的压力
案例代码:
node里app.js编写数据和路由
//----城市数据----
var mycities=[
{
pid:'001',
provinceName:'北京市',
cities:[
{
id:'101',
cityName:'北京',
areas:['东城区', '西城区', '崇文区']
}
]
},
{
pid:'002',
provinceName:'天津市',
cities:[
{
id:'201',
cityName:'天津',
areas:['和平区', '河东区', '河西区']
}
]
},
{
pid:'003',
provinceName:'河北省',
cities:[
{
id:'301',
cityName:'石家庄',
areas:['长安区', '桥东区', '桥西区']
},
{
id:'302',
cityName:'唐山',
areas:['路南区', '路北区', '古冶区', '开平区', '丰南区']
},
]
}
]
//----省市区三级联动--之省份路由----
app.get('/province',(req,res)=>{
res.send(mycities)
})
//----省市区三级联动--之城市路由----
app.get('/cities',(req,res)=>{
let pid=req.query.pid;
for (var index in mycities){
if (mycities[index].pid==pid){
res.send(mycities[index].cities)
}
}
})
//----省市区三级联动--之区县路由----
app.get('/areas',(req,res)=>{
let id=req.query.id;
for(var index1 in mycities){
for (var index2 in mycities[index1].cities){
if (mycities[index1].cities[index2].id==id){
res.send(mycities[index1].cities[index2].areas)
}
}
}
})
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.form-inline{display: flex}
.form-group{flex: 1;}
.bg-success{background-color:greenyellow; color: black}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div class="form-inline">
<div class="form-group">
<select class="from-control" id="province">
</select>
</div>
<div class="form-group">
<select class="from-control" id="city">
<option>请选择城市</option>
</select>
</div>
<div class="form-group">
<select class="from-control" id="area">
<option>请选择县市</option>
</select>
</div>
</div>
<!--创建省份模板-->
<script type="text/html" id="provinceTpl">
<option>请选择省份</option>
{{each resualtProvince}}
<option value="{{$value.pid}}">{{$value.provinceName}}</option><!--$value表示循环过程的当前项-->
{{/each}}
</script>
<!--创建城市模板-->
<script type="text/html" id="cityTpl">
<option>请选择城市</option>
{{each resualtCity}}
<option value="{{$value.id}}">{{$value.cityName}}</option><!--$value表示循环过程的当前项-->
{{/each}}
</script>
<!--创建区县模板-->
<script type="text/html" id="areaTpl">
<option>请选择区县</option>
{{each resualtArea}}
<option>{{$value}}</option><!--$value表示循环过程的当前项-->
{{/each}}
</script>
<script type="text/javascript">
//获取省市区下拉框元素
let province=document.getElementById('province');
let city=document.getElementById('city');
let area=document.getElementById('area');
//获取省份信息
ajax({
type:'get',
url:'http://localhost:8848/province',
success:(data)=>{
let html=template('provinceTpl',{resualtProvince:data})
//将拼接好的html字符串放入省份下拉框
province.innerHTML=html;
return;
}
})
//为省份下拉框添加值改变事件
province.onchange=function(){
//获取省份id
var pid=this.value;
//清空线程下拉框的数据
let html=template('areaTpl',{resualtArea:[]})
area.innerHTML=html;
//根据省份pid获取城市信息
ajax({
data: {
pid:pid
},
url:'http://localhost:8848/cities',
success:(cities)=>{
let html=template('cityTpl',{resualtCity:cities})
city.innerHTML=html;
}
})
}
//为城市下拉框添加值改变事件
city.onchange=function(){
//获取城市id
var id=this.value;
//根据城市id获取城市信息
ajax({
data: {
id:id,
},
url:'http://localhost:8848/areas',
success:(areas)=>{
let html=template('areaTpl',{resualtArea:areas})
area.innerHTML=html;
}
})
}
</script>
</body>
</html>
7、formData对象的作用
- 1、模拟html表单,相当于将html表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。
- 2、异步上传二进制文件
步骤
- 1、准备html表单
<form id="form">
<input type="text" name="username"/>
<input type="text" name="password"/>
<input type="button" />
</form>
- 2、将html表单转换为fromData对象
使用formData构造函数就可以将表单转换为formdata对象
let form=document.getElementById('form')
let fromData=new FormData(form)
- 3、提交表单对象
xhr.send(fromData)
ps:不能使用get请求,只能使用post请求
8、formData读写模块 formidable的安装
cpm install --save formidable
9、简单的formdata传参案例
1、node的app.js中引入formidable及相关路由
app.js
//引入express框架
const express=require('express')
//引入路径处理模块
const path=require('path')
//使用post方式传参必须的模块
//const bodyParser= require('body-parser')
//引入formdata处理模块
const formidable=require('formidable')
//引入文件读取模块
const fs=require('fs')
//创建web服务器
const app=express();
//静态资源访问服务器功能
app.use(express.static(path.join(__dirname,'public')))
//解析post请求头,调用json的解析方法
//app.use(bodyParser.json())
//配置formdata路由
app.post('/fromData',(req,res)=>{
//创建formidable表单解析对象
const form=new formidable.IncomingForm();
//解析客户端传递过来的FormData对象
form.parse(req,(err,fields,files)=>{
res.send(fields)
})
})
//监听端口
app.listen(8848);
//控制台提示输出
console.log('服务器启动成功')
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
</style>
<title>Title</title>
</head>
<body>
here is my server
<div>
<form id="form">
<input type="text" name="username"/><br/>
<input type="password" name="password"/><br/>
<input type="button" id="btn" value="提交">
</form>
</div>
<script>
let form=document.getElementById('form')
let btn=document.getElementById('btn')
btn.onclick=function(){
let fromData=new FormData(form)//内置构造函数创建一个formdata对象
//创建ajax对象
let xhr=new XMLHttpRequest();
//对ajax对象进行配置
xhr.open('post','http://localhost:8848/fromData')
//发送ajax请求
xhr.send(fromData)
//监听ahr的onload时间
xhr.onload=function(){
//对http状态码进行判断
if (xhr.status==200){
console.log(xhr.responseText)
}
}
}
</script>
</body>
</html>
10、FormData对象的实例方法
- 1、获取表单对象中属性的值
fromData.get('key')//fromData是FormData的一个实例,key是表单控件中的属性名称(name属性的值)
- 2、设置表单对象中属性的值
如果set设置的表单属性值是原本存在的,则会覆盖手动提交的值,如果表单属性值是不存在的,则会额外多建立一个属性值进行提交
fromData.set(key,value)
- 3、删除表单对象中属性的值
fromData.delete('key')
- 4、向表单对象中追加值
fromData.append('key','value')
- 5、set和append的区别
set方法与append方法的区别是,在属性名已存在的情况下,set会覆盖已有键名的值,append会保留两个值。但服务器只能接收最后一个,如果需要同时接受两个或以上的同名属性,需要在服务器中进行设置(如何设置老师没讲,也不想研究)
案例
<div>
<form id="form">
<input type="text" name="username"/><br/>
<input type="password" name="password"/><br/>
<input type="button" id="btn" value="提交">
</form>
</div>
<script>
let form=document.getElementById('form')
let btn=document.getElementById('btn')
btn.onclick=function(){
let fromData=new FormData(form)//内置构造函数创建一个formdata对象
let uname=fromData.get('username')
console.log(uname)
fromData.set('password','666666')
fromData.set('gender','male')
fromData.delete('gender')
fromData.append('key','value')//也可以从空的formData上进行append(添加)
fromData.append('password','8888')
fromData.append('password','9999')
}
</script>
11、FormData二进制文件上传
<input type="file" id="file"/>
//创建空表单formdata对象
let fromData = new FormData(form)
//将用户的二进制文件追加到表单对象中
fromData.append('myfile', this.files[0])//从fiels数组中选择
案例代码
1、创建node的app.js中的路由逻辑,仍然需要使用formidable模块
//配置upload路由
app.post('/upload',(req,res)=>{
//创建formidable表单解析对象
const form=new formidable.IncomingForm();
//设置上传文件的存储路径,需要使用path默认模块这里使用参数形式拼接文件路径
form.uploadDir=path.join(__dirname,'public','uploads')
//保留上传文件的后缀,使用fromidable对象的keepExtensions属性
form.keepExtensions=true;
//解析客户端传递过来的FormData对象
form.parse(req,(err,fields,files)=>{
res.send('file is upload successed')
})
})
2、html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
</style>
<title>Title</title>
</head>
<body>
here is my server
<div>
<form id="form">
<input type="file" id="file"/>
<input type="button" id="btn" value="提交">
</form>
</div>
<script>
let form=document.getElementById('form')
let file=document.getElementById('file')
//let btn=document.getElementById('btn')
//当用户选择文件时
file.onchange=function() {
//创建空表单formdata对象
let fromData = new FormData()
//将用户的二进制文件追加到表单对象中
fromData.append('myfile', this.files[0])//从fiels数组中选择
//创建ajax对象
let xhr=new XMLHttpRequest();
//对ajax对象进行配置
xhr.open('post','http://localhost:8848/upload')
//发送ajax请求
xhr.send(fromData)
//监听ahr的onload时间
xhr.onload=function(){
//对http状态码进行判断
if (xhr.status==200){
console.log(xhr.responseText)
}
}
}
</script>
</body>
</html>
12、FormData文件上传进度展示
文件上传过程中持续触发onprogress事件,具体位置为xhr.upload.onprogress
核心代码
file.onchange=function() {
//文件上传过程中持续触发onprogress事件
xhr.upload.onprogress=function(event){
//当前上传文件大小/文件总大小,再将结果转换为百分数
//将结果赋值给进度条的宽度属性
bar.style.width=(event.loaded/event.total)*100+'%';
}
}
案例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div>
<form id="form">
<input type="file" id="file"/><br/>
<div class="progress">
<div class="progress_bar" style="width: 0;" id="bar">0%</div>
</div>
</form>
</div>
<script>
let file=document.getElementById('file')
let bar=document.getElementById('bar');
//当用户选择文件时
file.onchange=function() {
//创建空表单formdata对象
let fromData = new FormData()
//将用户的二进制文件追加到表单对象中
fromData.append('myfile', this.files[0])//从fiels数组中选择
//创建ajax对象
let xhr=new XMLHttpRequest();
//对ajax对象进行配置
xhr.open('post','http://localhost:8848/upload')
//文件上传过程中持续触发onprogress事件
xhr.upload.onprogress=function(event){
console.log(event)
//当前上传文件大小/文件总大小,再将结果转换为百分数,大小有默认的属性(load,total)
//将结果赋值给进度条的宽度属性
let result=event.loaded/event.total*100+'%';
bar.style.width=result;
//将百分比显示在进度条中
bar.innerText=result;
}
//发送ajax请求
xhr.send(fromData)
//监听ahr的onload时间
xhr.onload=function(){
//对http状态码进行判断
if (xhr.status==200){
console.log(xhr.responseText)
}
}
}
</script>
</body>
</html>
13、FormData文件上传图片即时预览
在我们将图片上传到服务器端以后,服务器端通常都会将图片地址作为响应数据传递到客户端,客户端可以从响应数据获取图片地址,然后将图片再显示在页面中。
1、改造原有node的app.js的原有路由
//配置upload路由
app.post('/upload',(req,res)=>{
//创建formidable表单解析对象
const form=new formidable.IncomingForm();
//设置上传文件的存储路径,需要使用path默认模块这里使用参数形式拼接文件路径
form.uploadDir=path.join(__dirname,'public','uploads')
//保留上传文件的后缀,使用fromidable对象的keepExtensions属性
form.keepExtensions=true;
//解析客户端传递过来的FormData对象
form.parse(req,(err,fields,files)=>{
//将客户端传递过来的文件地址响应到客户端
//这里的attrName是从客户端的fromdata里append过来的
res.send(
{
path:files.attrName.path.split('public')[1]
})
})
})
2、html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div>
<form id="form">
<input type="file" id="file"/><br/>
<div class="progress">
<div class="progress_bar" style="width: 0;" id="bar">0%</div>
</div>
<div id="inputimg"></div>
</form>
</div>
<script>
let file=document.getElementById('file')
let bar=document.getElementById('bar');
let inputimg=document.getElementById('inputimg');
//当用户选择文件时
file.onchange=function() {
//创建空表单formdata对象
let fromData = new FormData()
//将用户的二进制文件追加到表单对象中
fromData.append('attrName', this.files[0])//从fiels数组中选择
//创建ajax对象
let xhr=new XMLHttpRequest();
//对ajax对象进行配置
xhr.open('post','http://localhost:8848/upload')
//文件上传过程中持续触发onprogress事件
xhr.upload.onprogress=function(event){
console.log(event)
//当前上传文件大小/文件总大小,再将结果转换为百分数,大小有默认的属性(load,total)
//将结果赋值给进度条的宽度属性
let result=event.loaded/event.total*100+'%';
bar.style.width=result;
//将百分比显示在进度条中
bar.innerText=result;
}
//发送ajax请求
xhr.send(fromData)
//监听ahr的onload时间
xhr.onload=function(){
//对http状态码进行判断
if (xhr.status==200){
let result=JSON.parse(xhr.responseText)
//动态创建img表单
let img=document.createElement('img');
//给图片标签设置src属性
img.src=result.path;
console.log(img.src)
//img加载时,才将img加载带盒子里
img.onload=function(){
inputimg.appendChild(img)
}
}
}
}
</script>
</body>
</html>
14、同源政策之ajax请求限制(跨域问题)
ajax只能想自己的服务器发送请求,不能同时向两个服务器发送请求。即A、B两个网站,A站的html不能同时向A、B网站发送请求,只能请求A网站(跨域问题)
1、什么是同源:
如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源
2、同源政策的目的:
同源政策是为了保护用户信息的安全,防止恶意的网站窃取数据。如a客户端的cookie,b网站不能访问
此外,无法想飞同源地址发送ajax请求,如果请求,浏览器就会报错(报CROS错误)
15、使用JSONP解决同源限制问题
jsonp是json with padding的缩写,不属于ajax请求,但它可以模拟ajax请求,用于向非同源客户端发送请求
- 1、将不同源的服务器端请求卸载script表情的src属性中
因为script标签不受同源政策的约束,可以请求非同源地址
<script src='www.example.com'></script>
- 2、服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数
const data='fn({name:'zhangsan',age:'20'})'
res.send(data)
- 3、要在客户端全局作用域下定义函数fn
function fun(data){ }
- 4、在fn函数内部对服务器返回的数据进行处理
function fn (data){ console.log(data)}
案例代码
服务器1(端口号8848)html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div>
<form id="form">
</form>
</div>
<script>
//这里是全局作用的处理函数,用来处理服务器返回的数据(函数调用及实参共同组成的字符串)
function fn(data){
console.log('客户端的函数被调用了')
console.log(data)
}
</script>
<!--将非同源服务器端的请求地址卸载script标签的src属性中-->
<script src="http://localhost:8888/test">
</script>
</body>
</html>
服务器2(端口号8888)app.js代码
//创建路由
app.get('/test', (req, res) => {
//将函数调用写在字符串中,返回给1号客户端,由1号客户端来调用这个函数
//函数放在字符串里的同时,可以给函数一个实参
const resualt='fn({name:"zhangsan",age:"25"})'
res.send(resualt)
})
//监听端口
app.listen(8888);
16、jsonp代码优化----客户端需要将函数名称传递到服务器端。
服务器1(端口号8848)html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server
<div>
<form id="form">
</form>
</div>
<script>
//这里是全局作用的处理函数,用来处理服务器返回的数据(函数调用及实参共同组成的字符串)
function fn(data){
console.log('客户端的函数被调用了')
console.log(data)
}
</script>
<!--将非同源服务器端的请求地址卸载script标签的src属性中-->
<script src="http://localhost:8888/better?callback=fn">
</script>
</body>
</html>
服务器2(端口号8888)app.js代码
app.get('/better', (req, res) => {
//接收客户端传递过来的函数名
const fnName = req.query.callback;
//将函数调用写在字符串中,返回给1号客户端,由1号客户端来调用这个函数
//函数放在字符串里的同时,可以给函数一个实参
//将函数名称对应的函数调用代码返回给客户端
const resualt=fnName+'({name:"zhangsan",age:"25",address:"beijing"})'
res.send(resualt)
})
//监听端口
app.listen(8888);
17、jsonp代码优化----将script请求的发送变成动态请求。
如动态添加一个按钮,来决定是否发送动态请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<button id="btn">点我发送请求</button>
<script>
//这里是全局作用的处理函数,用来处理服务器返回的数据(函数调用及实参共同组成的字符串)
function fn(data){
console.log('客户端的函数被调用了')
console.log(data)
}
</script>
<script>
var btn=document.getElementById('btn');
btn.onclick=function(){
//创建script标签
var script=document.createElement('script')
//设置src属性,并将非同源服务器端的请求地址卸载script标签的src属性中
script.src="http://localhost:8888/better?callback=fn";
//将script标签添加到页面中
document.body.appendChild(script);
//监听script标签的onload时间,一旦加载后就可以将script标签从body中删除
script.onload=function(){
//将body标签的script标签删除掉
document.body.removeChild(script)
}
}
</script>
</body>
</html>
18、jsonp代码优化----封装jsonp函数,方便请求发送
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<button id="btn">点我发送请求</button>
<script>
//这里是全局作用的处理函数,用来处理服务器返回的数据(函数调用及实参共同组成的字符串)
function fn(data){
console.log('客户端的函数被调用了')
console.log(data)
}
</script>
<script>
var btn=document.getElementById('btn');
btn.onclick=function(){
jsonp({
//请求地址
url:"http://localhost:8888/better?callback=fn"
})
}
//封装jsonp函数,方便日后调用
function jsonp(options){
//动态创建script标签
var script=document.createElement('script')
//设置src属性,并将非同源服务器端的请求地址卸载script标签的src属性中
script.src=options.url;
//将script标签添加到页面中
document.body.appendChild(script);
//监听script标签的onload时间,一旦加载后就可以将script标签从body中删除
script.onload=function(){
//将body标签的script标签删除掉
document.body.removeChild(script)
}
}
</script>
</body>
</html>
19、jsonp代码优化----随机函数名及函数作用域提升
函数作用域提升:将函数挂载到window上,
window.fnName=options.success
随机名:使用随机函数拼接
let fnName='myjsonp'+Math.random().toString().replace('.','')
案例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<button id="btn">11111点我发送请求</button>
<button id="btn2">2222点我发送请求</button>
<script>
</script>
<script>
var btn=document.getElementById('btn');
btn.onclick=function(){
jsonp({
//请求地址
url:"http://localhost:8888/better",
success:function (data) {
console.log('123')
console.log(data)
}
})
}
//封装jsonp函数,方便日后调用
function jsonp(options){
//动态创建script标签
let script=document.createElement('script')
//拼接随机函数名
let fnName='myjsonp'+Math.random().toString().replace('.','')
//options.success已经不是一个全局函数了,需要把他变为全部函数
//解决办法是将其挂载到window对象上
window[fnName]=options.success
//设置src属性,并将非同源服务器端的请求地址卸载script标签的src属性中
script.src=options.url+'?callback='+fnName;
//将script标签添加到页面中
document.body.appendChild(script);
//监听script标签的onload时间,一旦加载后就可以将script标签从body中删除
script.onload=function(){
//将body标签的script标签删除掉
document.body.removeChild(script)
}
}
</script>
</body>
</html>
21、jsonp代码优化----将需要传递的多个参数整合进jsonp函数里
使用遍历+拼接实参的方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<style type="text/css">
.progress{width: 100%; height: 20px; border: 1px solid gray;}
.progress_bar{height: 20px; background-color: cornflowerblue}
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<button id="btn">11111点我发送请求</button>
<button id="btn2">2222点我发送请求</button>
<script>
</script>
<script>
var btn=document.getElementById('btn');
btn.onclick=function(){
jsonp({
//请求地址
url:"http://localhost:8888/better",
data:{
name:'lisi',
age:30,
address:'hebei'
},
success:function (data) {
console.log('123')
console.log(data)
}
})
}
//封装jsonp函数,方便日后调用
function jsonp(options){
//动态创建script标签
let script=document.createElement('script')
//拼接字符串的变量,处理从服务器过来的对象,将对象转换成字符串(?key1=value1 &key2=value2形式)
var params='';
for (var attr in options.data){
params += '&'+attr+'='+options.data[attr];
}
//拼接随机函数名
let fnName='myjsonp'+Math.random().toString().replace('.','')
//options.success已经不是一个全局函数了,需要把他变为全部函数
//解决办法是将其挂载到window对象上
window[fnName]=options.success
//设置src属性,并将非同源服务器端的请求地址卸载script标签的src属性中
script.src=options.url+'?callback='+fnName+params;
//将script标签添加到页面中
document.body.appendChild(script);
//监听script标签的onload时间,一旦加载后就可以将script标签从body中删除
script.onload=function(){
//将body标签的script标签删除掉
document.body.removeChild(script)
}
}
22、jsonp代码优化----服务器端代码优化
服务器2app.js代码
app.get('/better', (req, res) => {
//直接使用jsonp方法把客户端需要的数据返回回去
res.jsonp({name:"zhangsan",age:"25",address:"beijing",university:'pku'})
})
//监听端口
app.listen(8888);
23、腾讯天气案例
请求地址:https://wis.qq.com/weather/common
请求方式:get 支持jsonp
参数名:
source:string类型 pc xw
weather_type:string类型,forecast_1h未来48小时,forecast_24h 未来7天
province:string类型 省份
city:string类型,城市
返回值:
{
"data":{
//48小时天气预报
"forecast_1h":{
'0':{
"degree":"9",//温度
"update_time":"20210220200000",//时间
"weather":"阴",//天气名称
"weather_code":"02",//天气码
"weather_short":"阴",//天气简要名称
"wind_direction":"微风",//风向
"wind_power":"3"//风力级别
}
}
},
"message":"OK",
"status":200
}
PS:这里需要提前封装好的jsop跨域访问js(上例)和网络请求ajax.js昨天的例子
在这里可以下载到:
ajax.js
jsonp.js
案例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<script src="./js/jsonp.js"></script>
<style type="text/css">
.wholebox{width:600px; border: 1px solid gray;}
.table{width:100%;}
.myth{ background-color: #ccc }
#box{ border-collapse:collapse; text-align: center }
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<div class="wholebox">
<table class="table" align="center" id="box" border="1">
</table>
</div>
<!--表格模板-->
<script type="text/html" id="tpl">
<tr class="myth">
<th>时间</th><th>温度</th><th>天气</th><th>风向</th><th>风力</th>
</tr>
{{each info}}
<tr>
<td>{{dateFormat($value.update_time)}}</td>
<td>{{$value.degree}}</td>
<td>{{$value.weather}}</td>
<td>{{$value.wind_direction}}</td>
<td>{{$value.wind_power}}</td>
</tr>
{{/each}}
</script>
<script>
//获取box控件
let box=document.getElementById('box')
//自定义日期格式化函数
function dateFormat(date){
let year=date.substr(0,4);
let month=date.substr(4,2)
let day=date.substr(6,2)
let hour=date.substr(8,2)
return year+'年'+month+'月'+day+'日'+hour+'时';
}
//向模板开放外部变量
template.defaults.imports.dateFormat=dateFormat;//dateFormat是自己定义的函数
//向服务器端获取天气信息
jsonp({
url:'https://wis.qq.com/weather/common',
data:{
source:'pc',
weather_type:'forecast_1h',
province:'北京市',
city:'北京'
},
success:(data)=>{
let html = template('tpl',{info:data.data.forecast_1h})
box.innerHTML=html;
}
})
</script>
</body>
</html>
24、CROS跨域资源共享
CORS:全称为Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送ajax请求,克服了ajax只能同源使用的限制。
说白了就是在服务器端开始一个access-control-access-origin白名单,这个白名单在 ‘Access-Control-Allow-Origin’ header 头里
因此只需要编辑node里的路由拦截逻辑就可以了
app.use((req, res,next) => {
//1、允许那些客户端访问我,都是设置在响应头里,第一个参数是响应名称,第二个参数响应名称对应的值
//'*'星号代表所有的客户端都可以访问我
res.header('Access-Control-Allow-Origin','*')
//2、允许客户端使用哪些方法访问我,使用逗号分割方法
res.header('Access-Control-Allow-Methods','get,post')
res.send('成功实现跨域访问')
})
案例代码
node的app.js设置路由拦截逻辑
//设置拦截所有请求,这样就不用再在每个路由里面单独编写跨域逻辑
app.use((req, res,next) => {//nex是下一个路由
//1、允许那些客户端访问我,都是设置在响应头里,第一个参数是响应名称,第二个参数响应名称对应的值
//'*'星号代表所有的客户端都可以访问我
res.header('Access-Control-Allow-Origin','*')
//2、允许客户端使用哪些方法访问我,使用逗号分割方法
res.header('Access-Control-Allow-Methods','get,post')
next();
res.send('成功实现跨域访问')
})
//监听端口
app.listen(8888);
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<script src="./js/jsonp.js"></script>
<style type="text/css">
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<div >
<button id="btn">点我发送请求</button>
</div>
<script>
//获取按钮
let btn=document.getElementById('btn')
//为按钮添加点击事件
btn.onclick=function(){
ajax({
type:'get',
url:'http://localhost:8888/cross',
success:function(data){
console.log(data)
}
})
}
</script>
</body>
</html>
25、访问非同源数据(跨域问题)的另一种服务器端解决方案
同源政策是浏览器给与ajax技术的限制,服务器端是不存在同源政策的限制。
以下方法实际上就是用服务器做数据桥
需要使用request第三方模块(名字就叫request)
添加该模块
npm install request
案例代码
1号服务器(8848端口)html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<script src="./js/jsonp.js"></script>
<style type="text/css">
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<div >
<button id="btn">点我发送请求</button>
</div>
<script>
//获取按钮
let btn=document.getElementById('btn')
//为按钮添加点击事件
btn.onclick=function(){
ajax({
type:'get',
url:'http://localhost:8848/server',
success:function(data){
console.log(data)
}
})
}
</script>
</body>
</html>
1号服务器(8848端口)服务器端node的app.js代码
//引入向其他数据库请求数据的模块request
const request=require('request');
app.get('/server',(req,res)=>{
//向2号服务器请求数据
//request有两个参数,第一个参数是访问的第2号服务器的路由地址,第二个参数是处理反馈的回调函数
//回调函数有三个参数,err是错误信息,response是服务器响应信息,body是2号服务器响应的信息体
request('http://localhost:8888/cross',(err,response,body)=>{
res.send(body)
})
})
//监听端口
app.listen(8848);
2号服务器(8888端口)服务器端node的app.js代码
一定要在路由拦截那写next方法
//设置拦截所有请求,这样就不用再在每个路由里面单独编写跨域逻辑
app.use((req, res, next) => {
//1、允许那些客户端访问我,都是设置在响应头里,第一个参数是响应名称,第二个参数响应名称对应的值
//'*'星号代表所有的客户端都可以访问我
res.header('Access-Control-Allow-Origin','*')
//2、允许客户端使用哪些方法访问我,使用逗号分割方法
res.header('Access-Control-Allow-Methods', 'get,post')
//next不写的话,不会往下访问一个路由,非常重要
next();
//res.send('跨域成功')
})
app.get('/cross', (req, res) => {
res.send('我是2号服务器的corss路由')
})
//监听端口
app.listen(8888);
26、cookie复习
cookie实际就是一个身份识别技术
27、withCredentials属性实现跨域登录
在使用ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息‘’
withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false
需要在node服务器端设置:
Access-Control-Allow-Credentials:true允许客户端发送请求时携带cookie
客户端也需要做响应的修改
PS:由于这里使用到了express-session,需要提前引入express-session模块,安装模块使用:
npm install express-session -S
并引入这个模块
const session = require('express-session')
案例代码
- 1、第2台服务器node的app.js文件
注意跨域设置里没有使用*星号,而是使用了整个域名头部: res.header(‘Access-Control-Allow-Origin’,‘http://localhost:8848’)
ps:checklogin的服务器端代码没有找到,自己写不出来,唉
//设置拦截所有请求,这样就不用再在每个路由里面单独编写跨域逻辑
app.use((req, res, next) => {
//1、允许那些客户端访问我,都是设置在响应头里,第一个参数是响应名称,第二个参数响应名称对应的值
//'*'星号代表所有的客户端都可以访问我
res.header('Access-Control-Allow-Origin','http://localhost:8848')
//2、允许客户端使用哪些方法访问我,使用逗号分割方法
res.header('Access-Control-Allow-Methods', 'get,post')
//3、设置跨域登录响应头,允许客户端请求携带跨域信息
res.header('Access-Control-Allow-Credentials',true)//设置前一个参数的值为true
//next不写的话,不会往下访问一个路由,非常重要
next();
//res.send('跨域成功')
})
app.get('/cross', (req, res) => {
res.send('我是2号服务器的corss路由')
})
app.post('/login', (req, res) => {
//创建表单解析对象
let form = formidable.IncomingForm();
//解析表单
form.parse(req, (err, fields, file) => {
//接收客户端传递过来的用户名和密码
const { username, password } = fields;
//用户名密码比对
if (username == 'zhangsan' && password == '123456') {
//设置session
session.isLogin = true;//要引入express-session,使用创建的session变量,而不是req.session
res.send({ message: '登录成功' })
} else {
res.send({ message: '登录失败,用户名或密码错误' })
}
})
})
app.get('/checklogin', (req, res) => {
/*
if (req.session.username == 'zhangsan' && req.session.password == '123456') {
res.send({ message: '检测登录成功' })
} else {
res.send({ message: '检测登录失败,用户名或密码错误' })
}
*/
})
//监听端口
app.listen(8888);
- 2、第1台服务器html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--1、将之前封装好的网络请求js导入-->
<script src="./js/ajax.js"></script>
<script src="./js/template-web.js"></script>
<script src="./js/jsonp.js"></script>
<style type="text/css">
</style>
<title>Title</title>
</head>
<body>
here is my server<br/>
<div >
<form id="loginFrom">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username" class="from-control" placeholder="请输入用户名">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" class="from-control" placeholder="请输入密码">
</div>
<input type="button" class="btn" value="登录" id="loginbtn"/>
<input type="button" class="btn" value="检测用户登录状态" id="checklogin"/>
</form>
</div>
<script>
//获取按钮
let loginbtn=document.getElementById('loginbtn')
let checklogin=document.getElementById('checklogin')
let loginFrom=document.getElementById('loginFrom')
//为登录按钮添加点击事件
loginbtn.onclick=function(){
//使用formData打包表单数据
let formData=new FormData(loginFrom)
//创建ajax对象
let xhr=new XMLHttpRequest();
//对ajax对象进行设置,向第2台服务器发送登录请求
xhr.open('post','http://localhost:8888/login')
//设置跨域登录属性,当发送跨域请求时,携带cookie信息
xhr.withCredentials=true;
//发送请求,并传递请求参数
xhr.send(formData)
//监听服务器端的响应内容
xhr.onload=function(){
console.log(xhr.responseText)
}
}
//为检测用户登录状态按钮添加点击事件
checklogin.onclick=function(){
//创建ajax对象
let xhr=new XMLHttpRequest();
//对ajax对象进行设置,向第2台服务器发送登录请求
xhr.open('get','http://localhost:8888/checklogin')
//设置跨域登录属性,当发送跨域请求时,携带cookie信息
xhr.withCredentials=true;
//发送请求,并传递请求参数
xhr.send()
//监听服务器端的响应内容
xhr.onload=function(){
console.log(xhr.responseText)
}
}
</script>
</body>
</html>