1 ajax基础
1.1 传统网站中存在的问题
网站加载慢的情况下,页面加载时间长,用户只能等待
表单提交之后如果有一项不符合要求,需要重新填写所有的表单内容
页面跳转需要重新加载页面,造成资源浪费,增加用户等待的时间
1.2 ajax概述
ajax是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网页应用的体验
1.3 ajax的应用场景
页面上拉加载更多数据
列表数据无刷新分页
表单项离开焦点进行数据验证
搜索框提示文字下拉列表
1.4 ajax运行环境
ajax技术需要运行在网站环境中才能生效(即必须通过网站服务器打开才能生效,不能直接打卡html页面),课程中使用node创建的服务器作为网站服务器,通过localhost域名的方式进行访问
2 ajax运行原理及其实现
Ajax 相当于浏览器发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验。
ajax也是javascript代码,写在script标签中
2.1 实现ajax
- 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 1.创建ajax对象
var xhr = new XMLHttpRequest();
// 2.设置ajax对象的请求方式和请求地址
/*
xhr.open(参数1,参数2)
- 参数1:请求方式get/post
- 参数2:请求地址(即服务器端的路由地址)
*/
xhr.open('get', 'http://localhost:3000/first');
//3.向服务器发送请求
xhr.send();
// 4.发送清酒结束会自动调用onload()响应函数,接收服务器端的响应
xhr.onload = function () {
console.log(xhr.responseText);
} - 服务器端
1
2
3app.get('/first', (req, res) => {
res.send('hello Ajax');
}) - 执行结果
2.2 服务器端响应的数据格式
- 客户端
服务器端大多数情况下会以JSON 对象作为响应数据的格式。当客户端拿到响应数据时,要将 JSON 数据和 HTML 字符串进行拼接,然后将拼接的结果展示在页面中。
在 http 请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输。
- 在客户端接收到的内容需要JSON.parse()的方式转化为JSON对象,再进行字符串的拼接显示(BOM操作)
- JSON.parse() // 将 json 字符串转换为json对象
实现
- 服务器
1
2
3app.get('/responseJson', (req, res) => {
// send()可以返回字符串也可以返回对象
res.send({ name: '张三' });}) - 客户端
1
2
3
4
5
6
7
8
9xhr.onload = function () {
console.log(xhr.responseText);//{"name":"张三"}
console.log(typeof xhr.responseText)//string
//服务器端返回的json数据在客户端是一个json字符串,需要通过JSON.parse()转化为json对象
var jsonobj = JSON.parse(xhr.responseText);
console.log(jsonobj.name);//张三
// 拼接到html页面
document.body.innerHTML = `<h2>${jsonobj.name}</h2>`
}2.3 请求参数传递
2.3.1 get请求参数
- 服务器
使用ajax请求,请求参数需要自己拼接
1
xhr.open('get', 'http://www.example.com?name=zhangsan&age=20');
ajax发送的表单请求时并没有提交form表单,他的请求方式和提交地址都通过ajax的open()方法进行指定
当向服务器端提交的请求不是form表单的请求时,提交按钮可以不是submit类型
实现
- 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<input type="text" name="username" id="name">
<input type="text" name="age" id="age">
<input type="button" value="提交" id="btn">
<script>
// 获取dom元素添加地点击事件
var username = document.querySelector('#name');
var age = document.querySelector('#age');
var btn = document.querySelector('#btn');
// 为button按钮添加事件
btn.onclick = function () {
// 创建ajax对象
var xhr = new XMLHttpRequest();
/*
ajax中不能像传统网站那样提交表单之后发送请求的值会直接拼接在请求地址的参数位置,需要我们自己拼接
获取待发送的请求值
*/
var nameValue = username.value;
var ageValue = age.value;
var params = "username=" + nameValue + "&age=" + ageValue;
// 设置ajax对象的请求方式和发送地址
xhr.open('get', 'http://localhost:3000/get?' + params);
//发送请求
xhr.send();
// 获取服务器端响应的数据
xhr.onload = function () {
console.log(xhr.responseText);
}
} - 服务器端
1
2app.get('/get', (req, res) => {
res.send(req.query);})
- 客户端
执行结果
2.3.2 post请求参数:
post请求参数在ajax中是放在请求体中的,并且必须设置报文的请求参数类型content-type,请求参数放在send()方法中
- xhr.setRequestHeader()
1
2xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send('name=zhangsan&age=20');
- xhr.setRequestHeader()
请求报文
- 在http请求和响应过程中传递的数据块叫做报文包括要传送的数据和一些附加信息,这些数据和附加信息要遵循规定好的规则
- 报文分为报文头和报文体
- 报文头:存放键值对信息
- 报文体:存储发送的内容
实现
客户端
1
2
3
4
5
6
7
8// 设置ajax对象的请求方式和发送地址
xhr.open('post', 'http://localhost:3000/post?');
// 设置报文头的内容类型(注意根据请求参数的不同设置不同的内容类型)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
//发送请求
xhr.send(params);服务器端
1
2
3
4// 4.post请求参数html
app.post('/post', (req, res) => {
res.send(req.body);
})执行结果
2.3.3 post/get二者的比较:
get请求参数是放在地址栏后面,post请求参数是放在请求体中的
post请求参数在传递过程中是放在send(请求参数)方法中,post请求必须明确在报文中明确设置请求参数的内容类型content-type
二者的请求方式不同
2.3.4 请求参数的格式
默认的请求参数格式:application/x-www-form-urlencoded
- 这种请求参数格式get/post方式都可以接收
- post接收参数设置:app.use(bodyParser.urlencoded({ extended: false }));
1
name=zhangsan&age=20&sex=男
application/json的请求参数只能在post方式中传递,并且在接收的过程中body-parser要设置为json格式
- post接收参数设置:app.use(bodyParser.json());
- 在请求头中指定 Content-Type 属性的值是 application/json,告诉服务器端当前请求参数的格式是 json。
- json格式数据传递在传递中需要通过JSON.stringify()方法先转化为json字符串形式才能进行参数的传递
1
{name: 'zhangsan', age: '20', sex: '男'}
get 请求是不能提交 json 对象数据格式的,传统网站的表单提交也是不支持 json 对象数据格式的。
ps:请求参数在传递过程中只能传递字符串格式
实现
- 客户端
1
2
3
4
5
6
7
8
9
10
11params = { username: nameValue, age: ageValue };//json格式请求参数
// 设置ajax对象的请求方式和发送地址
xhr.open('post', 'http://localhost:3000/json?');
// 设置报文头的内容类型(注意根据请求参数的不同设置不同的内容类型)
xhr.setRequestHeader('Content-Type', 'application/json')
//发送请求,请求参数传递只能是字符串形式JSON.stringify()转化为json字符串形式
xhr.send(JSON.stringify(params));
// 获取服务器端响应的数据
xhr.onload = function () {
console.log(xhr.responseText);
} - 服务器端
1
2
3app.use(bodyParser.json());
app.post('/json', (req, res) => {
res.send(req.body);})
- 客户端
结果展示
2.3.5 获取服务器端的响应的另外一种方式
ajax状态码
在创建ajax对象,配置ajax对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是ajax状态码。
- 0:请求未初始化(还没有调用open())
- 1:请求已经建立,但是还没有发送(还没有调用send())
- 2:请求已经发送
- 3:请求正在处理中,通常响应中已经有部分数据可以用了
- 4:响应已经完成,可以获取并使用服务器的响应了
通过xhr.readyState属性获取ajax的状态码
问题:2.3.4的状态码都是在send()方法中进行的,不能直接通过xhr.readyState属性的方式得到,所以引入当状态码发生变化时自动触发事件onreadystatechange
onreadystatechange 事件
当 Ajax 状态码发生变化时将自动触发该事件。
在事件处理函数中可以获取 Ajax 状态码并对其进行判断,当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。
实现
- 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13var xhr = new XMLHttpRequest();
xhr.open('get', 'http://localhost:3000/readystate');
// 1.此时已经对ajax做了配置但是还没有发送请求
console.log(xhr.readyState);//获取当前状态码
// 当ajax发生变化的时候自动发响应事件onreadystatechange,在send()发送过程中会同时发生状态码2,3,4的情况,
xhr.onreadystatechange = function () {
console.log(xhr.readyState);
// 当状态码是4的时候表示当前send发送已经结束
if (xhr.readyState == 4) {
console.log(xhr.responseText);
}
}
xhr.send(); - 服务器端
1
2app.get('/readystate', (req, res) => {
res.send("ok");})
- 客户端
执行结果
获取服务器端响应两种方式的比较
onload不兼容低版本额IE浏览器
onload响应只调用一次放在send()方法之后;onreadystatechange响应放在send()方法之前调用多次
推荐使用onload响应方式
2.3.6 ajax错误处理
.网络畅通,服务器端能接收到请求,服务器端返回的结果不是预期结果。(非200状态码)
- 可以判断服务器端返回的状态码,分别进行处理。xhr.status 获取http状态码
1
2
3
4
5
6
7
8
9
10
11
12
13
14var button = document.querySelector('#button');
button.onclick = function () {
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://localhost:3000/error');
xhr.send();
xhr.onload = function () {
// 获取服务器端响应到客户端的请求
console.log(xhr.responseText);
// 返回服务器端向客户端发送的状态码
if (xhr.status == 400) {
alert("服务器请求错误")
}
}
}
- 可以判断服务器端返回的状态码,分别进行处理。xhr.status 获取http状态码
网络畅通,服务器端没有接收到请求,返回404状态码。
- 检查请求地址是否错误。
网络畅通,服务器端能接收到请求,服务器端返回500状态码。
- 服务器端错误,找后端程序员进行沟通。
网络中断,请求无法发送到服务器端。
ajax状态码表示的是ajax请求的过程状态,是有ajax对象返回的
http状态码表示请求的处理结果,是由服务器端返回的
2.3.7 ajax在低版本 IE 浏览器的缓存问题
问题:在低版本的 IE 浏览器中,Ajax 请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依然拿到的是缓存中的旧数据
解决方案
使用ajax请求向服务器端发送代码属于异步请求(受网络因素的影响等,)
3.1 同步异步概述
同步:代码逐行执行
异步:异步代码虽然需要花费时间去执行,但程序不会等待异步代码执行完成后再继续执行后续代码,而是直接执行后续代码,当后续代码执行完成后再回头看异步代码是否返回结果,如果已有返回结果,再调用事先准备好的回调函数处理异步代码执行的结果。(就是onload事件处理函数)
注意:回调函数是在请求执行结束之后自动调用的回调函数(请求发送不成功,该函数是不会调用的)
实例
1
2
3
4
5console.log('before');
setTimeout(
() => { console.log('last');
}, 2000);
console.log('after');3.2 ajax封装
问题:发送一次请求代码过多,发送多次请求代码冗余且重复。
解决方案:将请求代码封装到函数中,发请求时调用函数即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function ajax(options) {
var xhr = new XMLHttpRequest();
xhr.open(options.type, options.url);
xhr.send();
// 请求发送成功才会执行
xhr.onload = function () {
// 将返回值传递给自定义函数
options.success(xhr.responseText)
}
}
// 调用封装好的ajax函数
ajax({
type: 'get',
url: 'http://localhost:3000/first',
// 自定义请求发送成功时触发onload事件的操作
success: function (data) {
console.log("success函数" + data);
}
})ajax封装需要考虑的两个问题
请求参数位置的问题
- 将请求参数传递到ajax函数内部,在函数内部根据请求方式的不同将请求参数放置在不同的位置
- get:放置在请求地址的后面
- post:放置在请求体中
- 将请求参数传递到ajax函数内部,在函数内部根据请求方式的不同将请求参数放置在不同的位置
请求参数格式的问题
- application/x-www-form-urlencoded
- application/json
解决方案:分别对不同的请求方式来进行处理
- 1.分别对不同的请求方式做判断(post/get)
- 2.分别对不同的请求参数格式进行判断
- 3.分别对不同的http状态码进行判断
- 4.分别对服务器端响应的数据进行判断,根据内容类型的不同对不同的响应数据做转换
- xhr.getResponseHeader(‘Content-Type’);获取响应数据的内容类型,
- 5.设置ajax函数的默认值,在使用中使用默认值,
- Object.assign(defaults, options);使用此方法覆盖defaults(当options已经赋值存在的情况下)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86function ajax(options) {
// 5.设置ajax函数的默认值
var defaults = {
type: 'get',
url: '',
header: {
'Content-Type': 'application/json'
},
data: {},
// 自定义请求发送成功时触发onload事件的操作
success: function () { },
// 请求失败时触发的函数
error: function () { }
}
// 用后者覆盖前者(前提是后者存在的情况下)
Object.assign(defaults, options);//
var xhr = new XMLHttpRequest();
// 定义一个变量保存参数
var params = '';
// 拼接参数
for (var attr in defaults.data) {
params += attr + '=' + defaults.data[attr] + '&';
}
// 去掉最后一个&符号
params = params.substr(0, params.length - 1);
// 1.如果是get方式将params拼接在url
if (defaults.type == 'get') {
defaults.url += '?' + params;
}
xhr.open(defaults.type, defaults.url);
// 如果是post,将请求参数放在请求体中
if (defaults.type == 'post') {
// 设置内容类型
var contentType = defaults.header['Content-Type'];
xhr.setRequestHeader('Content-Type', contentType);
//2. 如果参数内容类型为json格式
if (contentType == 'application/json') {
xhr.send(JSON.stringify(defaults.data))
} else {
// 非json格式内容类型
xhr.send(params);
}
} else {
xhr.send();
}
// 请求发送成功才会执行
xhr.onload = function () {
// 4.获取响应头中的数据,根据内容类型的不同对不同的响应数据做转换
// console.log(xhr.getResponseHeader('Content-Type'));//application/json; charset=utf-8
var contentType = xhr.getResponseHeader('Content-Type');
var contentText = xhr.responseText;
if (contentType.includes('application/json')) {
contentText = JSON.parse(contentText);
}
// 3.对http状态码进行判断
if (xhr.status == 200) {
defaults.success(contentText, xhr);//将xhr一起返回
} else {
defaults.error(contentText, xhr);
}
}
// 当网络中断时
xhr.onerror = function () {
// 调用失败回调函数并且将xhr对象传递给回调函数
defaults.error(xhr);}
}
// 调用封装好的ajax函数
ajax({
url: 'http://localhost:3000/first',
data: {
name: '张三',
age: '12'
},
// 自定义请求发送成功时触发onload事件的操作
success: function (data) {
console.log("success函数");
console.log(data)
},
// 请求失败时触发的函数
error: function (data, xhr) {
console.log("error函数");
console.log(data);
console.log(xhr);
}
})
- Object.assign(defaults, options);使用此方法覆盖defaults(当options已经赋值存在的情况下)
对象中获取字符串格式的属性名通过[]的方式,通过点.的方式获取不符合js语法
4 模板引擎
模板引擎传统的方式是在服务器端将html和模板引擎拼接好在发送给客户端,现在用ajax技术在客户端就可以完成html和模板引擎的拼接
在客户端进行拼接也需要模板引擎,无论是在客户端模板引擎还是服务器端模板引擎都是数据与模板引擎的拼接
- 通过npm安装的是服务器端的art-template模板引擎
- 下载的js文件的引入使用是客户端的art-template模板引擎
art-template模板引擎客户端和服务器端的都有
使用
- 1.下载 art-template 模板引擎库文件并在 HTML 页面中引入库文件
- 模板引入以后就存在template()方法
1
<script src="./js/template-web.js"></script>
- 模板引入以后就存在template()方法
- 2.准备 art-template 模板
- 模板这里设置id值用来标识不同的模板
- 设置type属性为text/html可以正常显示模板中的标签
1
2
3<script id="tpl" type="text/html">
<div class="box"></div>
</script>
- 3.告诉模板引擎将哪一个模板和哪个数据进行拼接
- 返回的是拼接好的字符串
1
var html = template('tpl', {username: 'zhangsan', age: '20'});//返回值:<h1>zhangsan 12</h1>
- 返回的是拼接好的字符串
- 4.将拼接好的html字符串添加到页面中
1
document.getElementById('container').innerHTML = html;
- 5.通过模板语法告诉模板引擎,数据和html字符串要如何拼接
- 注意:这里使用的模板语法和nodejs服务器端使用的模板语法一样
1
2
3<script id="tpl" type="text/html">
<div class="box"> {{ username }} </div>
</script>
- 注意:这里使用的模板语法和nodejs服务器端使用的模板语法一样
- 1.下载 art-template 模板引擎库文件并在 HTML 页面中引入库文件
完整代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<script src="/js/template-web.js"></script>
<div id="container"></div>
<!-- 准备art-template的模板 -->
<script type="text/html" id='tel'>
<h1>{{ username }} {{ age }}</h1>
</script>
<script>
/*
template()方法告诉模板引擎将哪个数据和哪个模板进行拼接
- 模板id:用来标识不同的模板
- 数据 对象类型
- 方法的返回值就是拼接好的字符串
- template()方法在将模板的js文件引入就存在了
*/
var html = template('tel', { username: 'zhangsan', age: 12 });
// 将拼接好的字符串存放在dom元素中
document.getElementById('container').innerHTML = html;
</script>5 案例
case 1 邮箱地址验证
验证邮箱地址是否唯一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 获取dom元素对象
var emailInfo = document.querySelector('#email');
// 获取错误信息提示
var info = document.querySelector('#info');
// 为input设置焦点失去响应时间啊in
emailInfo.onblur = function () {
// 焦点失去判断输入的内容是否符合正则表达式
var email = this.value;
var reg = /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/;
// 不满足正则表达式
if (!reg.test(email)) {
info.innerHTML = '邮箱地址格式不符合要求';
info.className = 'bg-danger';//设置样式
// 阻止程序向下执行
return;
}
// ajax向服务器端放请求
ajax({
type: 'get',
url: '/verifyEmailAdress',
data: {
email: email
},
success: function (result) {
// 执行成功,这里的result返回的是一个json对象
info.innerHTML = result.message;
info.className = 'bg-success';
},
error: function (result) {
info.innerHTML = result.message;
info.className = 'bg-danger';
}
})
}case 2 搜索框输入自动提示
根据用户在文本框中输入的关键字,匹配相关内容
- 设置了延时定时器解决了oninput事件每次输入字符都想服务器发送请求的问题
- 输入的内容为空的时候隐藏下拉列表
- oninput事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56<div class="container">
<div class="form-group">
<input type="text" class="form-control" placeholder="请输入搜索关键字" id="search">
<ul class="list-group" id="list-box">
</ul>
</div>
</div>
<script src="/js/ajax.js"></script>
<script src="/js/template-web.js"></script>
<!-- 设置模板 -->
<script type="text/html" id='tpl'>
{{each result}}
<li class="list-group-item">{{$value}}</li>
{{/each}}
</script>
<script>
// 获取搜索框
var searchInp = document.querySelector('#search');
// 获取提示容器的变量
var listBox = document.querySelector('#list-box');
// 开启定时器,解决不断向服务器发送请求的问题,设置延时定时器
var timer = null;
// 用户向文本框中输入时触发oninput事件(每次输入都会触发事件)
searchInp.oninput = function () {
// 清除上一次设置的定时器
clearTimeout(timer);
// 获取用户输入的内容
var key = this.value;
// 如果用户没有在搜索框中输入内容
if (key.trim().length == 0) {
// 没有输入内容的时候,将提示下拉框隐藏
listBox.style.display = 'none';
// 阻止程序向下执行
return;
}
// 开启定时器
setTimeout(function () {
// 向服务器端发送请求获取和用户输入关键字相关的内容
ajax({
type: 'get',
url: '/searchAutoPrompt',
data: {
key: key
},
success: function (result) {
// 使用模板引擎拼接字符串
var html = template('tpl', { result: result });
// 将拼接好的字符串显示在页面中
listBox.innerHTML = html;
// 显示ul容器
listBox.style.display = 'block';
}
})
}, 800)
}
</script>case 3 省市区联动
获取省份信息
根据省份id获取城市信息
根据城市id获取县城信息
- 为每个省份添加onchange事件,在为每个城市添加onchange事件
- 在选择了省份的下拉框后进行点击事件需要先清除县城的数据为空放入空数组不能直接设置为null
1 | <div class="container"> |
本文链接: https://sparkparis.github.io/2020/04/22/Ajax%E7%AC%94%E8%AE%B01/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!