一、Ajax基础
传统网站中存在的问题
- 网速慢的情况下,页面加载时间长,用户只能等待
- 表单提交后,如果一项内容不合格,需要重新填写所有表单内容
- 页面跳转,重新加载页面,造成资源浪费,增加用户等待时间
1.1 Ajax概述
他是浏览器提供的一套方法,可以实现页面无刷新更新数据,在不刷新页面的情况下和服务器进行交互,
主要目的:提高用户浏览器网站应用的体验
1.2 Ajax的应用场景
- 1. 页面上拉加载更多数据
- 2. 列表数据无刷新分页
- 3. 表单项离开焦点数据验证
- 4. 搜索框提示文字下拉列表
1.3 Ajax的运行环境
Ajax技术需要运行在网站环境中才能生效,也就是说要能以localhost域名的方式打开页面,不能直接双击HTML文件打开
二、Ajax运行原理及实现
2.1 运行原理:
实现步骤:
//1.创建Ajax对象 var xhr = new XMLHttpRequest(); //2. 告诉Ajax对象要向哪发送请求,以什么方式发送请求 //1)请求方式 2)请求地址(其实就是服务器短的路由地址) xhr.open('get', 'http://localhost:3000/first'); //3.发送请求 xhr.send(); //4.获取服务器端响应到客户端的数据 // 由于服务器返回数据的时间是不确定的,我们需要绑定一个onload事件,当xhr对象接受到服务器返回的数据之后,自动触发onload事件 xhr.onload = function(){ // xhr对象下面的responseText存储的就是响应的数据 console.log(xhr.responseText) }
2.2 服务器端响应的数据格式:
在真实项目中,服务器端大多数情况下会以JSON对象作为响应数据的格式,当客户端拿到响应数据时,要把JSON数据和HTML字符串进行拼接,然后将拼接的结果展示在页面中
在http 请求和响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输
JSON.parse(参数) //将JSON字符串转换为JSON对象,JSON是window对象下面的一个属性
服务器端返回的json格式的数据到了客户端这边就变成了json格式的字符串,
- 需要先转换为json对象
- 然后和HTML拼接
- 再使用DOM方法放到页面中即可
var responseText = JSON.parse(xhr.responseText) //将JSON字符串转换为JSON对象 var str = '<h2>'+ responseText.name +'</h2>'; // 和HTML拼接 document.body.innerHTML = str // 使用DOM方法放到页面中
2.3 请求参数传递
- GET请求方式
xhr.open('get', 'http://www.example.com?'+params);
//获取用户在文本框中输入的值 var nameValue = username.value; var ageValue = age.value; // 要拼接成 username=123&age=456 var params = 'username='+ nameValue +'&age=' +ageValue; // 配置Ajax对象 xhr.open('get', 'http://localhost:3000/get?'+ params);
- POST请求方式
post请求方式必须设置请求报文
报文:在http请求和响应的过程中传递的数据块就叫做报文,报文包括报文头和报文体,这些数据和信息要遵守规定好的格式
报文头:存储一些键值对信息,可以理解为客户端向服务端说的一些话
报文体:主要存储一些信息,比如post请求参数就是存储在报文体中
报文头和报文体在请求的过程中一起发送到服务器
1.post请求参数时,拼接出来的参数一定要放在send方法中, 然后就相当于把请求参数放到了请求体中
2.必须明确请求参数的格式类型
//获取用户在文本框中输入的值 var nameValue = username.value; var ageValue = age.value; // 要拼接成 username=123&age=456 var params = 'username='+ nameValue +'&age=' +ageValue; // 配置Ajax对象 xhr.open('post', 'http://localhost:3000/post');
// 设置请求参数的格式类型 xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded'); // 发送请求 xhr.send(params);
在服务器端的app.js需要添加以下代码
// post请求需要的第三方包,从而可以使用req.body const bodyParser = require('body-parser'); // 解析 application/x-www-form-urlencoded 这种格式的请求参数 app.use(bodyParser.urlencoded()); app.post('/post', (req, res) =>{ // 通过req.body 获取客户端传递给服务器端的post请求参数 res.send(req.body); })
2.4 传递json格式的请求参数
请求参数的格式有两种
1. application/x-www-form-urlencoded
name=zhangsan&age=26&sex=男
2. application/json
{name: 'zhangsan', age:'20', sex:'男'}
当传递json格式类型的请求参数的时候
1. 在请求头中指定 Content-Type 属性的值为 application/json,告诉服务器端当前请求参数的格式是json
xhr.setRequestHeader('Content-Type','application/json');
2. json格式类型的参数必须使用POST请求方式,get请求不能提交json对象数据格式,传统网站表单提交也不支持json对象数据格式
3.将json对象转换为json字符串进行传递
JSON.stringify({name:'lisa', age:50})
4. 在服务器端解析请求参数的格式需要引入第三方包,并且针对不同的格式设置不同的解析方式
// post请求需要的第三方包 const bodyParser = require('body-parser'); // 解析 application/x-www-form-urlencoded 这种格式的请求参数 app.use(bodyParser.urlencoded()); // 解析 application/json 这种格式的请求参数 app.use(bodyParser.json());
// 字符串拼接格式 app.post('/post', (req, res) => { res.send(req.body); }); // json格式 app.post('/json', (req, res) => { res.send(req.body); });
2.5 获取服务器端的响应
Ajax状态码:在创建Ajax对象、配置Ajax对象、发送请求一级接受完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是Ajax状态码
0:请求未初始化(还没有调用open())
1:请求已经建立,但是还没有发送(还没有调用send())
2:请求已经发送
3:请求正在处理中,通常响应中已经有部分数据可以用了
4:响应已经完成,可以获取并使用服务器的响应了
xhr.readyState // 获取Ajax状态码
两种获取服务器端响应方式的区别
推荐使用onload,除非需要兼容IE低版本
2.6 Ajax错误处理
Ajax状态码:表示Ajax请求过程状态 Ajax对象返回的
http状态码:表示请求的处理结果,是服务器端返回的
2.7 低版本IE浏览器的缓存问题
问题:在低版本的IE浏览器中,Ajax请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请求会真正的发送到服务器端,后续的请求都会从浏览器的缓存中获取结果,即使服务器端的数据更新了,客户端依然拿到的是缓存中的旧数据
解决方案:在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同
xhr.open('get', 'http://www.example.com?t=' + Math.random());
三、Ajax异步编程
3.1
同步:代码逐行完成,一行代码完成之后才执行下一行代码
异步:一个人事情做了一二班,转而去做其他事情,当其他事情做完之后,再回过头来继续做之前未完成的事情
异步代码虽然需要花时间去执行,但程序不会等待异步代码执行完成之后再继续执行后续代码,而是直接执行后续代码,当后续代码执行完成后再回头看异步代码是否返回结果,如果已有返回结果,再调用事先准备好的回调函数处理异步代码执行结果
事件处理函数就是一个回调函数
xhr.onload = function(){ console.log(2) // xhr对象下面的responseText存储的就是响应的数据 console.log(xhr.responseText) } console.log(1)
// 会先输出1,在输出2
3.2 Ajax封装
问题:发送一次请求代码过多,发送多次请求代码冗余且重复
解决方案:将请求的代码封装到函数中,发送请求时调用函数即可
(1)最基本的封装
function ajax(options){ //创建ajax对象 var xhr = new XMLHttpRequest(); //配置ajax对象 xhr.open(options.type, options.url); // 发送请求 xhr.send(); // 获取响应结果,监听xhr对象下面的onload事件
// 当xhr对象接受完响应数据后被触发 xhr.onload = function(){
// 对于下面返回的服务器结果,我们想把这个数据放到函数体的外面,让用户决定它的使用方式,在下面传递的对象中创建了一个success函数
// 在这里调用success函数,并且把请求的结果传递到函数中作为形参 options.success(xhr.responseText); } } // 上面封装了一个ajax函数,下面调用这个函数,并且传递了一个实参,实参中包含了请求方式和请求地址 ajax({ // type代表请求方式 type: 'get', // 请求地址 url: 'http://localhost:3000/first', // success这个函数在请求成功之后被调用,请求成功也就是执行onload事件的时候就代表请求成功,也就是在onload事件处理函数被触发就代表请求成功, // 下面这个函数是一个定义,data是一个形参,接受服务器响应结果 success: function(data){ console.log('这里是success函数'+data); } })
(2)发送请求时需要传递参数
要考虑的问题:
- 请求参数位置的问题:将请求参数传递到ajax函数内部,在函数内部根据请求方式的不同将请求参数放置在不同的位置
get 放在请求地址的后面;post 放在send方法中
- 请求参数格式的问题:
application/x-www-form-urlencoded 参数名称=参数值&参数名称=参数值 name=zhangsan&age=20 application/json {name: 'zhangsan', age: 20}
1. 传递对象数据类型对于函数的调用者更加友好
2. 在函数内部对象数据类型转换为字符串数据类型更加方便
<script type="text/javascript"> function ajax(options){ // 存储的是默认值,在调用这个函数的时候,我们不希望每次都传递大量参数,因此设置一个默认值 // 如果传递了这个参数,就覆盖掉默认值,否则使用默认值 var defaults = { type:'get', url:'', data:{}, header:{ 'Content-Type': 'application/x-www-form-urlencoded' }, success:function(){}, error:function(){} }; // 使用options 对象中的属性覆盖defaults 对象中的属性 Object.assign(defaults, options); //创建ajax对象 var xhr = new XMLHttpRequest(); // 拼接请求参数的变量 var params = ''; // 循环用户传递进来的对象格式参数 for(var attr in defaults.data){ params += attr + '=' + defaults.data[attr] + '&'; } // 将参数最后面的 & 截取掉,并将结果重新赋值给params params = params.substr(0,params.length-1); // 判断请求方式 if(defaults.type == 'get'){ defaults.url = defaults.url + '?' + params; } //配置ajax对象 xhr.open(defaults.type, defaults.url); // 如果请求方式为post if(defaults.type == 'post'){ // 用户希望的向服务器段传递的请求参数的类型 var contentType = defaults.header['Content-Type'] // post请求需要明确 设置请求方式格式的类型 xhr.setRequestHeader('Content-Type', contentType); // 如果传递的是json类型的参数,只能使用post请求方式 if(contentType == 'application/json'){ // 将json对象转换为json字符串进行传递 xhr.send(JSON.stringify(defaults.data)) }else{ // 向服务器传递普通类型的请求参数 xhr.send(params); } }else{ // 如果请求方式是get xhr.send(); } // 获取响应结果,监听xhr对象下面的onload事件 xhr.onload = function(){ // 使用ajax技术向服务器发送请求的时候,一般都会返回json类型的数据,但是客户端拿到的是json字符串 // 要想使用这个数据就需要把字符串转换为json对象,我们想把这个过程封装到函数当中,这样函数的调用者就不需要关心这个细节了,拿到的就直接是json对象 // 但是在函数内部定义这个函数的时候,我们不知道服务器端返回的数据是什么类型的,通过xhr.getResponseHeader获取返回的数据,Content-Type就代表返回的数据类型 // xhr.getResponseHeader() // 获取响应头当中的数据 var contentType = xhr.getResponseHeader('Content-Type'); // 服务器端返回的数据 var responseText = xhr.responseText // 如果响应类型中包含application/json if(contentType.includes('application/json')){ // 将json字符串转换为json对象 responseText = JSON.parse(responseText) } // 当http状态码等于200的时候 if(xhr.status == 200){ // 请求成功,调用处理成功情况的函数 // xhr对象中包含很多信息,因此可以吧xhr也传递出去 defaults.success(responseText, xhr); }else{ // 请求失败 调用处理失败情况的函数 defaults.error(responseText, xhr); } } } // 上面封装了一个ajax函数,下面调用这个函数,并且传递了一个实参,实参中包含了请求方式和请求地址 ajax({ // type代表请求方式 type: 'get', // 请求地址 url: 'http://localhost:3000/responseData', data: { name: 'zhangsan', age: 20 }, header: { 'Content-Type': 'application/json' }, // success这个函数在请求成功之后被调用,请求成功也就是执行onload事件的时候就代表请求成功,也就是在onload中调用success函数 // 下面这个函数是一个定义,data是一个形参 success: function(data){ console.log('这里是success函数'); console.log(data) }, error: function(data, xhr){ console.log('这里是error函数,'+ data); console.log(xhr) } }) </script>