AJAX (阿贾克斯)
ajax,全名 Asynchronous javascript and xml;异步javascript和xml技术
页面动态无刷新技术
在之前的学习中,我们如果想要将数据传递给服务器端,就需要通过form表单进行传递,而form表单提交之后,会对整个页面进行重新加载渲染.如果页面内容较多,例如图片,视频,则会严重影响用户体验.ajax可以很好地解决这个问题
Ajax 使用 XMLHttpRequest 对象与服务器端脚本进行通信,支持“异步”通信。
Ajax 可以在不必刷新页面的情况下发送以及接收各种格式的信息,包括 JSON,XML,HTML,甚至文本文件,让您可以根据用户事件更新页面的某些部分。
AJAX的工作原理
- 页面通过javascript调用浏览器提供的XMLHttpRequest接口
- XMLHttpRequest向 web服务器发送请求
- 后台服务器根据前端请求,到相应的数据库系统获取对应的数据
- 服务器将获取到的数据原路返回给XMLHttpRequest
- XMLHttpRequest通过回调函数来接收服务器端返回的数据
- javascript将回调函数里的数据渲染到界面上
AJAX的优点
- 通过 AJAX,可以创建更好、更快以及更友好的 WEB 应用程序
- 减少数据传输
- 减轻 Web 服务器负荷
- 页面无刷新,提供更好客户体验度的程序
AJAX的缺点
- 存书签问题
- 由于执行动作URL不跳转,所以无法存书签,无法收藏分享链接(可以通过浏览器hash来判断和加载不同动作)
- 浏览器前进后退问题
- 浏览器中的操作无法正常前进后退按钮(HTML5后,可以通过window.history.state实现)。
- SEO问题
- 搜索引擎无法抓取和收录Ajax内部请求的内容,不利
- 网络延迟问题
- 使用一个可视化的组件来告诉用户系统正在进行后台操作并且正在读取数据和内容
XMLHttpRequest
XMLHttpRequest(XHR)对象是 AJAX 程序的核心对象,可以在 JavaScript 中实现与服务器之间的数据发送和接收。
XMLHttpRequest属性列表
属性 | 描述 |
---|---|
open(method,url,async) | 规定请求的类型、URL 以及是否异步处理请求。method:请求的类型;GET 或 POST url:文件在服务器上的位置 async:true(异步)或 false(同步) |
send(string) | 将请求发送到服务器。string:仅用于 POST 请求 |
setRequestHeader(header,value) | 向请求添加 HTTP 头。header: 规定头的名称value: 规定头的值 |
onreadystatechange | 事件。存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。 |
readyState | 存有 XMLHttpRequest 的状态,从 0 到 4 发生变化: 0: 请求未初始化 1: 服务器连接已建立 2: 请求已接收 3: 请求处理中 4: 请求已完成,且响应已就绪 |
status | 服务器响应的状态码: 2xx;3xx;4xx;5xx |
responseText | 获得字符串形式的响应数据。 |
responseXML | 获得 XML 形式的响应数据。 |
XMLHttpRequest 使用
在使用之前,我们需要先启动之前我们下载的node服务器,如果没有下载的,点这里下载
启动服务器成功之后,创建一个 ajax.html文件,内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="startBTN">发起请求</button>
</body>
</html>
<script>
var startBTN = document.getElementById("startBTN");
startBTN.onclick = function(){
loadData();
}
function loadData(){
//1.创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
//2. 监听XMLHttpRequest的状态
xhr.onreadystatechange = function(){
//5.当xhr的状态等于4的时候,表示请求完成
//status等于200的时候,表示请求成功
if(xhr.readyState === 4 && xhr.status === 200){
//6.获取请求的结果
var result = xhr.responseText;
console.log("请求的结果",result);
}
}
//3.配置请求信息
xhr.open("GET","http://localhost:3000/json",true);
//4.发起请求
xhr.send();
}
</script>
输出结果
{
"title": "极火网",
"website": "www.githuo.com",
"author": "朱蒙托"
}
总结上面例子中的 注释的ajax流程
- 创建XMLHttpRequest对象
- 监听XMLHttpRequest的状态
- 配置请求信息
- 发起请求
- 判断请求状态
- 获取请求结果
GET请求
get请求的特点是,请求的参数会存储在请求链接的后面,
格式如下
http://xxxx.com/xxx?key1=value1&key2=vlaue2
我们以GET请求去访问 用户的信息接口,我们直接复制上个例子的代码,只需要修改第三步的内容
xhr.open("GET","http://localhost:3000/userInfo?username=githuo",true);
输出结果为
[
{
"username": "githuo",
"age": "33"
}
]
POST请求
post请求的参数,会被放在请求体了,用户无法在历史记录里查看请求的参数,因此比较安全
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="startBTN">发起请求</button>
</body>
</html>
<script>
var startBTN = document.getElementById("startBTN");
startBTN.onclick = function(){
loadData();
}
function loadData(){
//1.创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
//2. 监听XMLHttpRequest的状态
xhr.onreadystatechange = function(){
//5.当xhr的状态等于4的时候,表示请求完成
//status等于200的时候,表示请求成功
if(xhr.readyState === 4 && xhr.status === 200){
//6.获取请求的结果
var result = xhr.responseText;
console.log("请求的结果",result);
}
}
//3.配置请求信息
xhr.open("POST","http://localhost:3000/login",true);
// post请求必须加这行
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
var user = 'username=zmt&password=123';
//4.发起请求,将user信息作为send的参数进行传递
xhr.send(user);
}
</script>
输出结果
{
"message": "登录成功",
"username": "zmt"
}
setRequestHeader(header,value)
自定义HTTP头部信息。需在open()方法之后和send()之前调用,它的作用是 告诉 后台程序,我们提交的数据是以什么格式提交的.后台程序接收到之后,就会根据不同的类型对数据进行处理.
- 发送表单数据
// post请求必须加这行 xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); var user = 'username=zmt&password=123'; xhr.send(user);
- 发送json格式数据
xhr.setRequestHeader("Content-type", "application/json");
var user = '{"username":"zmt","password": "123"}';
//4.发起请求
xhr.send(user);
- 发送纯文本
你要通过文本的方式 将 结构性的数据内容发送到服务器,服务器需要特殊处理,才可以,否则无法获取你想要的数据
xhr.setRequestHeader("Content-type", "text/plain");
var user = '{"username":"zmt","password": "123"}';
//4.发起请求
xhr.send(user);
- 发送html文本
和纯文本内容一样,需要进行特殊处理,对后台不友好
xhr.setRequestHeader("Content-type", "text/html");
var user = '<div id="username">zmt</div><div id="password">123</div>';
//4.发起请求
xhr.send(user);
onreadystatechange
当请求被发送到服务器时,我们需要执行一些基于响应的任务。 每当 readyState 改变时,就会触发 onreadystatechange 事件。 readyState 属性存有 XMLHttpRequest 的状态信息。
状态 | 说明 |
---|---|
0 | 请求正在初始化 |
1 | 服务器连接已建立 |
2 | 请求已被接收 |
3 | 请求处理中 |
4 | 请求已完成,且响应已就绪 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="startBTN">发起请求</button>
</body>
</html>
<script>
var startBTN = document.getElementById("startBTN");
startBTN.onclick = function(){
loadData();
}
function loadData(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
var readyState = xhr.readyState;
if(readyState === 0){
console.log("请求正在初始化");
}
if(readyState === 1){
console.log("服务器连接已建立");
}
if(readyState === 2){
console.log("请求已被接收");
}
if(readyState === 3){
console.log("请求处理中");
}
if(xhr.readyState === 4 && xhr.status === 200){
var result = xhr.responseText;
console.log("请求的结果",result);
}
}
xhr.open("POST","http://localhost:3000/login",true);
xhr.setRequestHeader("Content-type", "application/json");
var user = '{"username":"zmt","password":"123"}';
xhr.send(user);
}
</script>
JSON解析
服务器端返回的是字符串类型的数据,如果返回的是 JSON格式的字符串,我们就需要将这些字符串进行解析才可以使用.JS为我们提供了原生的解析方法
parse
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="startBTN">发起请求</button>
</body>
</html>
<script>
var startBTN = document.getElementById("startBTN");
startBTN.onclick = function(){
loadData();
}
function loadData(){
//1.创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
//2. 监听XMLHttpRequest的状态
xhr.onreadystatechange = function(){
//5.当xhr的状态等于4的时候,表示请求完成
//status等于200的时候,表示请求成功
if(xhr.readyState === 4 && xhr.status === 200){
//6.获取请求的结果
var result = xhr.responseText;
console.log("请求的字符串结果",result);
//对result进行JSON解析
var rs = JSON.parse(result);
console.log(rs);
}
}
//3.配置请求信息
xhr.open("GET","http://localhost:3000/json",true);
//4.发起请求
xhr.send();
}
</script>
从上图的结果可以看出,xhr.responseText获取到的是一个JSON格式的字符串,通过JSON.parse函数对该字符串进行解析之后,得到一个对象.以后就可以通过 对象.属性 的方式访问结果了.
stringify()
将对象进行序列化,常用于 post请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="startBTN">发起请求</button>
</body>
</html>
<script>
var startBTN = document.getElementById("startBTN");
startBTN.onclick = function(){
loadData();
}
function loadData(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
var readyState = xhr.readyState;
if(xhr.readyState === 4 && xhr.status === 200){
var result = xhr.responseText;
console.log("请求的结果",result);
}
}
xhr.open("get","http://localhost:3000/test401",true);
xhr.setRequestHeader("Content-type", "application/json");
//Authorization为123abc的时候,服务器验证通过,否则验证失败
xhr.setRequestHeader("Authorization","123abc")
var user = {username:"zmt",
password:"123"};
//将user对象转换成字符串
var userStr = JSON.stringify(user);
//{"username":"zmt","password":"123"}
console.log( userStr);
xhr.send(userStr);
}
</script>
status
服务器响应的状态码
状态码 | 响应类别 | 描述 |
---|---|---|
1xx | 信息性状态码 | 服务器正在处理中 |
2xx | 成功状态码 | 请求已经处理完毕 |
3xx | 重定向状态码 | 需要进行额外操作完成请求 |
4xx | 客户端错误状态码 | 客户端原因导致服务器无法处理请求 |
5xx | 服务器错误状态码 | 服务器原因导致处理请求出错 |
状态码
需要观察状态码的,需要先开启服务器,然后通过ajax请求进行访问. 服务器下载地址
状态码 | 描述 | 测试接口 |
---|---|---|
200 | 表示请求被服务器正常处理 | http://localhost:3000/json |
204 | 表示请求已成功处理,但是没有内容返回 | http://localhost:3000/test204 |
301 | 永久重定向,表示请求的资源已经永久的搬到了其他位置 | http://localhost:3000/test301 |
302 | 临时重定向,表示请求的资源临时搬到了其他位置 | http://localhost:3000/test302 |
304 | 表示客户端发送附带条件的请求(GET方法请求报文中的IF…)时,条件不满足 返回304时,不包含任何响应主体 | |
400 | 表示请求报文存在语法错误或参数错误,服务器不理解 | |
401 | 表示发送的请求需要有HTTP认证信息或者是认证失败了 | http://localhost:3000/test401 |
403 | 表示对请求资源的访问被服务器拒绝了 | http://localhost:3000/test403/1.png |
404 | 表示服务器找不到你请求的资源 | http://localhost:3000/test404 |
500 | 表示服务器执行请求的时候出错了 | |
503 | 表示服务器超负载或正停机维护,无法处理请求 |
Authorization
在测试401接口的时候,我们需要为请求设置 验证参数 Authorization,只有Authorization的值正确,验证才会通过,否则返回401
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="startBTN">发起请求</button>
</body>
</html>
<script>
var startBTN = document.getElementById("startBTN");
startBTN.onclick = function(){
loadData();
}
function loadData(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
var readyState = xhr.readyState;
if(xhr.readyState === 4 && xhr.status === 200){
var result = xhr.responseText;
console.log("请求的结果",result);
}
}
xhr.open("get","http://localhost:3000/test401",true);
xhr.setRequestHeader("Content-type", "application/json");
//Authorization为123abc的时候,服务器验证通过,否则验证失败
xhr.setRequestHeader("Authorization","123abc")
var user = '{"username":"zmt","password":"123"}';
xhr.send(user);
}
</script>
跨域
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,目的是防止某个文档或脚本从多个不同源装载。
由于同源策略(Same origin policy)机制的限制,Web 系统中无法通过 Ajax 访问他域的请求. 为了测试跨域,我们将服务器设置为不允许跨域, 打开服务器的根目录中app.js文件,注释掉第12行代码
const index = require('./routes/index')
const users = require('./routes/users')
const cors = require('koa-cors')();
//这是要被注释的代码
// app.use(cors);
// error handler
onerror(app)
重启服务器
我们在谷歌浏览器中访问 http://localhost:3000/json 的时候,就会出现报错,如下图
解决跨域的主流方式
解决跨域的方式有很多种,但这里只说常用的两种
- cors(Cross-Origin Resource Sharing ) 是一个允许跨域访问的规范。
- 需要服务器在 HTTP Head 信息中添加 ‘Access-Control-Allow-Origin’ 请求参数来指定允许访问来源,开启跨域访问权限
- 在支持 CORS 的服务器上进行配置
- 服务器编写代码实现 (不需要前端设置)
JSONP
- JSON 是一种数据交换格式,而 JSONP 是一种非官方跨域数据交互协议
- 由于跨域 js 文件可以无条件执行,所以可以通过动态添加 <script> 标签来调用服务器提供的 js 脚本
- JSONP 协议允许用户传递一个 callback 参数给服务端,然后服务端返回数据时会将这个 callback 参数作为函数名来包裹住 JSON 数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
//该方法由服务器返回的时候触发
function callBack(data){
console.log(data);
}
</script>
<!-- script 访问页面是不会产生跨域问题,通过这种方式
将要触发的函数名传递给服务器.
-->
<script src="http://localhost:3000/testJsonp?callBack=callBack"></script>
</head>
<body>
</body>
</html>
<script>
</script>
动态加载script
很多时候,我们并不需要页面一加载就马上访问script标签里的内容,这个时候,我们就需要将script以动态的方式插入到页面中,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
function callBack(data){
console.log(data);
}
</script>
</head>
<body>
<button id="startBTN">发起请求</button>
</body>
</html>
<script>
var startBTN = document.getElementById("startBTN");
startBTN.onclick = function(){
var src = "http://localhost:3000/testJsonp?callBack=callBack";
var script_dom = document.createElement('script');
script_dom.src = src;
script_dom.language = 'javascript';
script_dom.type = 'text/javascript';
var head = document.getElementsByTagName('head').item(0);
head.appendChild(script_dom);
}
</script>
AJAX封装
如果每次都重头到尾写一遍ajax请求,是件非常麻烦的事.因此我们可以自己对它进行封装, 封装之后的使用方式如下
$.ajax({
method: "get",
async: true,
url: "http://localhost:3000/testJsonp",
data: {},
dataType: "jsonp", // JSONP
jsonp: "callback", // 传递给服务器获得 jsonp 回调函数名的参数名(一般默认为:callback)
jsonpCallback: "myHandler", // 自定义回调函数名,取代jQuery自动生成的随机函数名
header: {
"Content-type": "application/json"
},
//请求成功的回调函数
success: function (json) {
console.info("success",json);
},
//请求失败的回调函数
error: function () {
console.info(json);
},
//请求完成的回调函数
complete: function(data){
console.log("请求结束",data);
}
});
封装代码
Object.prototype.$ = {
ajax: function(options){
var method = options.method?options.method.toLowerCase():"get";
var async = options.async?options.async:true;
var url = options.url?options.url:"";
var data = options.data?options.data:{};
var dataType = options.dataType?options.dataType:"json";
var jsonpCallback = options.jsonpCallback?options.jsonpCallback:"callback";
var jsonp = options.jsonp?options.jsonp:"callback";
var success = options.success?options.success:function(data){}
var error = options.error?options.error:function(){}
var complete = options.complete?options.complete:function(){}
var headers = options.header?options.header:{
"Content-type":"application/x-www-form-urlencoded"
}
//判断是否JSOP,
if(dataType==="jsonp"){
console.log("jsonp请求");
//动态生成script标签
this.dynamicScript(url+"?"+jsonp+"="+jsonpCallback);
//将回调函数注册到全局对象window中
window[jsonpCallback] = function(data){
console.log("请求的结果",data);
if(data!=null){
success(data);
}else {
error("请求的结果为空",data)
}
complete(data);
}
return;
}
//如果是get请求,判断data是字符串还是对象,如果是对象则序列化参数
if(method=="get"){
if((typeof data) =="string"){
url += "&"+data;
}
if(typeof data == "object"){
url += "&"+this.format(data)
}
}
var xhr = new XMLHttpRequest();
xhr.open(method,url,async);
//设置请求头
for(var key in headers){
xhr.setRequestHeader(key,headers[key])
}
//处理post请求的数据
var dataStr = JSON.stringify(data);
xhr.send(dataStr);
xhr.onreadystatechange = function(){
var status = {
"204":"服务器没有返回内容",
"401": "验证失败",
"403": "没有权限访问该资源",
"404": "请求的资源不存在",
"500": "服务器出错",
"503": "服务器超负荷,无法完成请求"
}
var result = null;
//判断ajax请求是否完成
if(xhr.readyState===4){
//判断服务器返回的请求状态是否为200
if(xhr.status===200){
//获取服务器返回的数据
var response = xhr.response;
//如果dataType为json,则需要将数据转换成json格式
if(dataType=="json"){
result = JSON.parse(response);
}else {
//其他格式原样输出
result = response;
}
success(result);
}else{
error(status[xhr.status])
}
}
}
},
format: function(obj){
var rs = ""
for(var key in obj){
rs += key+"="+obj[key]+"&"
}
return rs.slice(0,-1);
},
dynamicScript: function(src){
var script_dom = document.createElement('script');
script_dom.src = src;
script_dom.language = 'javascript';
script_dom.type = 'text/javascript';
var head = document.getElementsByTagName('head').item(0);
head.appendChild(script_dom);
}
}