目录
一、ajax技术
Ajax即Asynchronous Javascript And XML(异步JavaScript和XML)在 2005年被Jesse James Garrett提出的新术语,用来描述一种使用现有技术集合的‘新’方法,包括: HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要的XMLHttpRequest。 使用Ajax技术网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面,这使得程序能够更快地回应用户的操作。
简单的理解:
Asynchronous Javascript And XML
异步 js 和 XML
js和html的数据交互方式,天生就是异步形式
二、ajax的五个步骤
ajax技术有专门的固定的步骤过程,必须严格按照ajax的步骤过程执行
第一步:使用ajax构造函数创建ajax实例化对象对象
const xhr = new XMLHttpRequest();
第二步:通过 ajax对象的open() 方法,设定 请求方式 和 请求地址
ajax对象.open(参数1 , 参数2);
参数1 请求的方式 get / post
参数2 请求的url地址路径
如果是get方式,可以携带请求参数
xhr.open('get' , 'url地址?键名=键值&&键名=键值...');
如果是post方式,不可以携带请求参数
xhr.open('post , 'url地址'');
第三步:如果是post请求方式,必须要设定请求头格式
xhr.setRequestHeader('Content-Type' , 'application/x-www-form-urlencoded');
第四步:通过 ajax对象的send() 方法,发送ajax请求
按照ajax对象.open() 设定的 请求方式 和 请求url地址 发送 请求
get方式:xhr.send();
post方式:xhr.send('键名=键值&键名=键值...');
post方式携带的参数要写在ajax对象的send() 方法里
第五步:接收ajax请求结果
根据结果 动态渲染生成页面
ajax对象.onload是ajax请求结束执行的函数程序,ajax请求是一个异步程序会执行所有的同步程序,执行完成之后,再执行异步的ajax请求
xhr.onload = function(){
在 xhr.response 或者 xhr.responseText 中 存储 响应体结果
如果是 json字符串 使用 JSON.parse() 还原为对应的数据结构
}
三、测试
首先要运行服务器,打开服务器接口文档,查看相关信息,根据信息然后书写代码
这是我的接口文档部分信息
3.1、测试1--ajax请求
首写先创建一个button标签 <button>发送请求</button>
//获取button标签对象
const oBtn = document.querySelector('button');
//给button标签添加点击事件
oBtn.addEventListener('click' , ()=>{
//发送ajax请求
//第一步:通关ajax构造函数创建实例化对象
const xhr = new XMLHttpRequest();
//第二步:通过 ajax对象的open() 方法,设定 请求方式 和 请求地址
//这里的请求方式就是接口文档的请求方式:get
//这里的请求地址就是接口文档的请求基准地址和请求地址
xhr.open('get' , 'http://localhost:8888/test/first');
//第四步:通过 ajax对象的send() 方法,发送ajax请求
xhr.send();
//第五步:接收ajax请求结果
xhr.onload = function(){
//在 xhr.response 或者 xhr.responseText 中 存储 响应体结果
console.log( xhr.response );
}
})
打开服务器,运行以上js代码,发送请求,结果如下(这里的结果就是接口文档的响应数据)
3.2、测试2--ajax请求和json字符串
我们先看代码,然后再了解json字符串
//获取button标签对象
const oBtn = document.querySelector('button');
//给button标签添加点击事件
oBtn.addEventListener('click' , ()=>{
const xhr = new XMLHttpRequest();
xhr.open('get' , 'http://localhost:8888/test/second');
xhr.send();
xhr.onload = ()=>{
//如果是 json字符串 使用 JSON.parse() 还原为对应的数据结构
console.log(JSON.parse(xhr.response));
}
})
结果:
3.2.1、json字符串
json字符串是字符串的一种,是有特殊语法格式的字符串,一般用于不同计算机语言之间传输数据。所有的计算机语言中,json字符串是通用的字符串。
例如:
JavaScript中数组的语法形式是
const arr = [
{ name:'张三' , age:18 , sex:'男'}
];
php中 数组的语法形式是
$arr = [
[ 'name'=>'张三' , 'age'=>18 , 'sex'=>'男' ]
];
后端将php数组转为json字符串,返回给我们一个json字符串,接收json字符串,再还原为js数组
不同的计算机语言之间,数据类型的语法格式不同,如果要发送数据,需要先转化为json字符串,传输给其他计算机语言,其他计算机语言接收json字符串,再还原为对应的数据结构
json字符串的操作语法
JSON.parse( json字符串 )
将json字符串还原为对应的数据结构
JSON.stringify( 其他数据结构 )
将 其他数据结构 转化为 json字符串
例:let jsonStr = JSON.stringify([{ name:'张三' , age:18 , sex:'男' }]);
3.2.2、json字符串的深入了解
1、json外部文件,扩展名是json的文件,就是json外部文件
2、手写json字符串的语法规范
字符串必须都要添加定界符,定界符必须是双引号,不能是单引号,最后一个单元之后不要加逗号
下面是一段json字符串举例:
[
{"name":"张三","age":18,"sex":"男","addr":"北京"},
{"name":"李四","age":17,"sex":"女","addr":"上海"},
{"name":"王五","age":16,"sex":"男","addr":"广州"},
{"name":"赵六","age":15,"sex":"女","addr":"重庆"},
{"name":"刘七","age":14,"sex":"男","addr":"天津"}
]
使用ajax方法可以向json文件发起请求获取其中的数据,但是必须通过服务器运行请求不能是直接请求本地json文件,请求外部json文件读取数据,只能是get方式,不能是post方式
3.3、测试3--ajax_get请求携带参数
//获取button标签对象
const oBtn = document.querySelector('button');
//给button标签添加点击事件
oBtn.addEventListener( 'click' , ()=>{
const xhr = new XMLHttpRequest();
//get方式 通过 设定 open() 携带参数
//携带参数的语法 和 超链接 携带参数的语法相同
//'url地址?键名=键值&键名=键值...'
xhr.open('get' , 'http://localhost:8888/test/third?name=张三&age=18');
xhr.send();
xhr.onload = ()=>{
console.log(JSON.parse(xhr.response));
}
})
结果:
3.4、测试4--ajax_post请求携带参数
//获取button标签对象
const oBtn = document.querySelector('button');
//给button标签添加点击事件
oBtn.addEventListener( 'click' , ()=>{
//发送ajax请求
//第一步:通关ajax构造函数创建实例化对象
const xhr = new XMLHttpRequest();
//第二步:通过 ajax对象的open() 方法,设定 请求方式 和 请求地址
xhr.open('post' , 'http://localhost:8888/test/fourth');
//第三步:如果是post请求方式,必须要设定 请求头
//这是 post方式 固定的设定的代码
xhr.setRequestHeader('Content-Type' , 'application/x-www-form-urlencoded');
//第四步:通过 ajax对象的send() 方法,发送ajax请求
xhr.send('name=王昭没有君啊&age=22');
//第五步:接收ajax请求结果
xhr.onload = ()=>{
console.log(JSON.parse(xhr.response));
}
})
结果:
四、封装一个简单的ajax请求
实际项目中,我们不可能像以上四个请求一样,用一遍ajax请求,就写一遍ajax请求,我们一定是把它封装为一个函数,什么时候需要ajax请求,我们就调用封装好的函数。
我们目前就执行一个简单的ajax封装,实际项目中,我们都会使用别人封装好的ajax,在封装之前我们还需要了解一些知识
封装时,考虑ajax的兼容处理
创建ajax对象
let xhr = {};
if(XMLHttpRequest){
//标准浏览器
xhr = new XMLHttpRequest();
}else{
//兼容IE低版本
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
4.1、ajax对象中的属性数据
ajax对象.response ajax对象.responseText
存储响应体数据
ajax对象.readyState
ajax请求的状态码,ajax执行步骤的状态码,表示ajax请求执行到哪一步了
0 ajax对象创建了,没有进行任何设定
1 ajax对象设定了open() ,也就是是设定了请求方式和请求地址
2 ajax对象执行了send() ,发送了
3 服务器给ajax请求返回了响应报文
4 ajax获取解析了 响应报文中的数据:http状态码、http状态描述、响应体数据
ajax对象.status
http协议的状态码
http响应行中http状态码获取,存储在ajax对象的属性中
如果ajax对象.status数据是200 - 299,表示请求结束,并且请求成功
ajax对象.statusText
http协议的状态描述
将http响应行中http状态描述获取,存储在ajax对象的属性中
当ajax状态码是4,表示ajax解析了响应报文中的数据
可以判断http状态码是200 - 299 表示请求成功
ajax对象.onreadystatechange
当ajax状态码,改变时触发函数程序
标准浏览器 和 低版本IE浏览器都有的函数方法
4.2、封装ajax请求函数
封装的函数,写在一个ajax.js文件里
//封装的ajax请求函数
//参数1:请求url地址
//参数2:请求方式 默认值 是get
//参数3:携带参数 默认值 是空字符串
//参数4:请求结束并且请求成功执行的回调函数,默认值是空函数
function myAjax( url, type = 'get', data = '', callback = function(){} ){
//创建ajax对象
//封装时,考虑ajax的兼容处理
let xhr;
if( XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//判断请求方式
if( type.toLowerCase() === 'get'){
//get方式
xhr.open( 'get' , `${url}?${data}` );
xhr.send();
}else{
//post方式请求
xhr.open( 'post' , url );
xhr.setRequestHeader('Content-Type' , 'application/x-www-form-urlencoded');
xhr.send(data);
}
//接收 请求结果
xhr.onreadystatechange = function(){
//ajax状态码是4 http状态码是200 - 299
//if(xhr.readyState === 4 && ( xhr.status >= 200 && xhr.status <= 299)){}
if( xhr.readyState === 4 && /^2\d{2}$/.test( xhr.status)){
//ajax请求结束,并且成功
//调用回调函数,赋值的数据是响应体结果
//也就是xhr.respone中存储的响应体结果数据
callback( xhr.response);
}
}
}
回调函数
以参数的形式 输入 要执行的函数程序,为了在 另一个函数中调用执行其他的函数程序,回调函数的主要原因是 不清楚要执行的具体函数程序是什么,因此使用参数的形式 执行回调函数,输入什么 参数 回调函数就执行什么程序
带有参数的回调函数
赋值的函数中 带有参数,也就是 callback 调用的函数 带有参数,需要在调用执行 callback 时赋值实参,实参是ajax请求的响应体结果,赋值的函数就可以通过操作函数中的形参,操作 callback 赋值的 响应体数据
封装ajax函数中,参数4 回调函数的作用
1、对于ajax请求返回的响应体结果,具体执行什么操作,我们并不确定
具体执行的程序,通过回调函数的形式执行,也就是参数4输入什么函数程序就执行什么程序
2、对于ajax请求返回的响应体结果数据
每次执行ajax请求,返回的响应体数据都不一样,需要通过回调函数在封装的ajax函数中调用执行时赋值响应体数据,再通过回调函数的具体程序来 操作 调用执行 具体的程序代码
4.3、利用封装的ajax函数
运行服务器,根据接口文档,进行相关的操作
向 http://localhost:8888/users/register 发送请求
请求方式是 post 方式
携带 4个 参数
username 账号 4-11 位
password 密码1 6-12 位
rpassword 密码2
nickname 昵称
我是将ajax.js文件,和这个html放在同一个文件夹中,将外部ajax.js文件导入到html文件中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
账号 : <input type="text"><br>
密码1: <input type="text"><br>
密码2: <input type="text"><br>
昵称 : <input type="text"><br>
<button>注册</button>
<!-- 加载外部文件ajax.js-->
<script src="ajax.js"></script>
<script>
// 获取标签对象
const oBtn = document.querySelector('button');
const oName = document.querySelectorAll('input')[0];
const oPwd1 = document.querySelectorAll('input')[1];
const oPwd2 = document.querySelectorAll('input')[2];
const oNick = document.querySelectorAll('input')[3];
//点击事件
oBtn.addEventListener('ckick' , ()=>{
//获取标签对象的数据
let name = oName.value ;
let pwd1 = oPwd1.value ;
let pwd2 = oPwd2.value ;
let nick = oNick.value ;
//使用函数执行ajax请求
myAjax('http://localhost:8888/users/register', 'post', `username=${name}&password=${pwd1}&rpassword=${pwd2}&nickname=${nick}`, function(response){
//将json字符串结果还原为对应得数据类型
response = JSON.parse(response);
})
//根据结果执行不同的程序
if( response.code === 0){
//注册失败,原因是用户名重复
console.log('对不起,您注册失败,原因是用户名重复');
}else if(response.code === 1){
//注册成功
console.log('恭喜您,注册成功');
}
})
</script>
</body>
</html>
五、回调地狱
回调地狱就是在回调函数中,继续调用新的执行的回调函数
本质就是回调函数的嵌套使用
myAjax( 地址 , 方式 , 参数 , function( response1 ){
根据 第一次的请求结果 再次 发起 第二次请求
myAjax( 地址 , 方式 , 参数 , function( response2 ){
根据 第二次的请求结果 再次 发起 第三次请求
myAjax( 地址 , 方式 , 参数 , function( response3){
根据 第三次的请求结果 执行对应的程序
})
})
})
这样写的话,在实际项目中,都是大型项目,代码量极其大,导致代码看起来极其臃肿,也不利于维护和开发
解决回调地狱的终极目标
将 嵌套的语法形式 写成 并列的语法形式
myAjax( 地址 , 方式 , 参数 , function( response1 ){})
myAjax( 地址 , 方式 , 参数 , function( response2 ){})
myAjax( 地址 , 方式 , 参数 , function( response3 ){})
但是ajax请求是异步程序,如果是并列的语法形式会同时开始执行
需要通过JavaScript语法操作,让回调函数写成并列的语法形式,又按照顺序一个一个执行
语法使用的技术是
promise
是ES6新增的语法
只是从形式上将嵌套语法写成类似于并列的语法,没有从根本上将程序写成并列形式,并且按照顺序执行
async await
是ES7新增的语法,配合promise语法,从根本上,将程序定义成并列形式,并且按照顺序执行
5.1、promise语法形式1
promise是ES6新增的语法形式,专门用于解决回调地狱
promise的本质是通过promise构造函数创建promise实例化对象,通过promise实例化对象完成执行异步程序,通过promise实例化对象的函数方法,设定 请求成功 和 请求失败 执行的函数程序
promise的基本步骤过程
1、通过promise构造函数创建promise实例化对象,promise构造函数需要定义一个参数,参数是一个匿名函数
const p = new Promise(function( 参数1, 参数2){});
2、匿名函数又有两个参数
参数1 执行异步请求 执行成功时,触发的函数程序
参数2 执行异步请求 执行失败时,触发的函数程序
3、promise实例化对象的函数方法,设定参数1参数2执行的具体的函数程序
参数1执行的函数程序
promise实例化对象.then( 函数程序 )
参数2执行的函数程序
promise实例化对象.catch( 函数程序 )
promise的三个基本状态
promise的执行有三个基本状态
状态1 peading 异步请求还在执行过程中
状态2 fulfilled 异步程序执行结束并且执行成功
状态3 rejected 异步程序执行结束并且执行失败
基本操作语法
const promise对象1 = new Promise(function( 参数1,参数2){
异步程序
执行成功 参数1(实参);
执行失败 参数2(实参);
})
promise对象1.then( function( 参数 ){
const promise对象2 = new Promise( function(参数1 , 参数2){
异步程序
执行成功 参数1( 实参 );
执行失败 参数2( 实参 );
})
promise对象2.then(function(){
const promise对象3 = new Promise( function(参数1 , 参数2){
异步程序
执行成功 参数1( 实参 );
执行失败 参数2( 实参 );
})
promise对象3.then();
promise对象3.catch();
});
promise对象1.catch( function( 参数 ){
执行失败的函数程序
})
promise语法形式1,看起来更加的复杂繁琐,我们只是先了解一下它的逻辑,方便我们理解promise语法形式2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 使用 promise来执行异步程序
// 参数1 表示请求 成功 执行的回调函数
// 参数2 表示请求 失败 执行的回调函数
const p1 = new Promise(function( fulfilled1 , rejected1 ){
const xhr = new XMLHttpRequest();
xhr.open( 'post' , 'http://localhost:8888/users/register' );
xhr.setRequestHeader('Content-Type' , 'application/x-www-form-urlencoded');
xhr.send('username=123456777&password=123456&rpassword=123456&nickname=123456');
xhr.onload = function(){
//请求成功ajax状态码是4,http状态码是200-299
if(xhr.readyState === 4 && /^2\d{2}$/.test( xhr .status )){
//请求成功执行的函数程序
fulfilled1( xhr.response );
}else{
//请求失败执行的函数程序
rejected1( xhr.statusText);
}
}
})
// 通过 promise实例化对象的函数方法
// 设定 参数1 参数2 执行的具体的函数程序
// promise实例化对象.then() 设定 请求成功 执行的函数程序
p1.then(funcion( response1 ){
//将json字符串还原为对应的数据结构
response1 = JSON.parse(response1);
consele.log( response1 );
// 发起第二次请求 再次创建 promise对象
// 通过 promise对象.then()定义成功执行的函数程序
// 通过 promise对象.catch()定义失败执行的函数程序
//然后在这里再执行一遍这样的程序
......
})
//promise实例化对象.catch() 设定请求失败,执行的函数程序
p1.catch(function( result1 ){
console.log( result1 );
})
</script>
</body>
</html>
5.2、 promise语法形式2
将promise封装成一个函数,函数的return返回值是promise对象,通过返回值定义 .then 和 .catch,封装成的函数文件名称是myPromiseAjax.js,封装成的函数只是一个简单的函数,实际项目中插件里的功能更强更全面
//封装一个promise程序执行ajax请求
//参数1 请求的url地址
//参数2 请求的方式
//参数3 携带的参数
function myPromiseAjax(url, type = 'get', data = ''){
//创建一个promise对象
const p = new Promise(function(fulfilled , rejected){
//创建一个ajax对象,考虑兼容
let xhr;
if(XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
//判断请求方式
if(type.toLowerCase() === 'get'){
xhr.open('get' , `${url}?${data}`);
xhr.send();
}else{
xhr.open('post' , url);
xhr.setRequestHeader('Content-Type' , 'application/x-www-form-urlencoded');
xhr.send(data);
}
//接收请求结果
xhr.onreadystatechange = function(){
//当ajax状态码是4时,判断http状态码
if( xhr.readyState === 4){
//如果http状态码是200-299
if(/^2\d{2}$/.test(xhr.status)){
//请求成功
fulfilled( xhr.response );
}else if(/^(4|5)\d{2}$/.test( xhr.status )){
// 请求失败
rejected( xhr.statusText );
}
}
}
})
// return 返回这个promise对象
return p;
}
promise语法规范规定
promise实例化对象.then( 匿名函数)
匿名函数的执行结果就是promise实例化对象.then()的执行结果
myPromiseAjax()封装的promise函数执行结果,返回值是promise实例化对象
const p1 = myPromiseAjax();
p1中存储的就是promise实例化对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>请求</button>
//导入外部js文件
<script src="myPromiseAjax.js"></script>
<script>
//点击发起请求
const oBtn = document.querySelector('button');
oBtn.addEventListener('click' , function(){
//第一次调用函数执行生成promise对象
//p1存储的是第一次请求生成的promise实例化对象
const p1 = myPromiseAjax('http://localhost:8888/users/register', 'post' , 'username=李四&password=123456&rpassword=123456&nickname=123456');
});
//通过实例化对象.then定义请求成功的回调函数
p1.then( function(response1){
response1 = JSON.parse( response1 );
console.log(response1);
//第二次请求直接return新的请求
return myPromiseAjax( 'http://localhost:8888/users/login' , 'post' , 'username=李四&password=123456' );
})
//p1.then() 执行结果,就是一个新的promise对象也就是p2
//可以写成p1.then().then()
.then(function(response2){
response2 = JSON.parse( response2 );
console.log( response2 );
return myPromiseAjax( 'http://localhost:8888/users/logout' , 'get' , `id=${response2.user.id}`);
})
//p1.then().then()执行结果,又是一个新的promise对象,也就是 p3
//可以写成p1.then().then().then()
.then(function(response3){
response3 = JSON.parse( response3 );
console.log( response3 );
})
//通过实例化对象.catch定义请求失败的回调函数
p1.catch();
</script>
</body>
</html>
promise的本质并不能真的将异步程序写成并列的语法形式,并且按照顺序执行
本质上异步程序还是嵌套的语法形式,只是通过.then .catch将回调函数定义在异步请求之外设定,效果是看上去好像是一个一个执行程序
六、完美解决回调地狱的方法
async和await是ES7新增的语法规范。配合promise封装函数执行程序
语法形式:
通过async定义一个函数,在async定义的函数中,使用await调用封装的promise函数,执行结果不再是return的promise实例化对象,执行结果是异步请求的响应体数据,可以直接定义下一个异步请求,await会按照顺序一个一个的执行
函数调用()
async function 函数(){
第一次调用
const 变量1 = await promise封装函数();
变量1存储的是ajax请求的响应体结果,不再是实例化
第二次调用
直接调用,不需要在使用promise对象.then()
await 会按照顺序执行 一个一个的 异步程序
const 变量2 = await promise封装函数();
第三次调用
const 变量3 = await promise封装函数();
}
在以后的程序中我们会大量的运用这种方法,promise、async和await是我们必须要掌握的,并且了解它们的基本运行机制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>请求</button>
<!--导入外部js文件-->
<script src="myPromiseAjax.js"></script>
<script>
const oBtn = document.querySelector('button');
oBtn.addEventListener('click' , function(){
//调用执行的async封装的函数
getAjax();
})
//使用async定义封装一个函数
async function getAjax(){
//使用 await 调用 promise封装的函数
//await 获取的是 异步请求 执行的 响应体结果 不再是 promise对象
//不再需要 执行 promise对象.then() 执行成功触发的函数程序
//可以直接写 下一次调用
const response1 = JSON.parse( await myPromiseAjax('http://localhost:8888/users/register' , 'post' , 'username=123456bbb&password=123456&rpassword=123456&nickname=123456'));
console.log(response1);
//可以直接定义第二次请求 await 会按照顺序执行程序代码
const response2 = JSON.parse( await myPromiseAjax('http://localhost:8888/users/login' , 'post' , 'username=123456bbb&password=123456'));
console.log(response2);
//可以直接定义第三次请求 await 会按照顺序执行程序代码
const response3 = JSON.parse( await myPromiseAjax('http://localhost:8888/users/logout' , 'get' , `id=${response2.user.id}`));
console.log(response3);
}
</script>
</body>
</html>