(一)概述
1 .首先,Ajax不是一项独立的技术,它也属于javascript这门语言中的一个知识点,它是浏览器提供的一套方法,通过调用这些方法,可以实现页面无刷新更新数据,提高用户体验。
2 .Ajax技术的主要应用场景:
①Ajax技术可以应用在页面上拉加载数据中
②列表分页无刷新更新局部数据
③表单数据验证
③搜索框文字自动提示功能
(二)Ajax的运行场景
ajax技术虽然属于客户端javascript代码,但是它必须运行在网站环境中才能生效。也就是说需要放在网站服务器下运行,要能以域名的方式打开页面。
(三)Ajax的实现步骤
1 .使用JS内置构造函数:XMLHttpRequest 创建Ajax对象
var xhr = new XMLHttpRequest();
2 .通过调用open方法告诉Ajax请求方式和请求地址
xhr.open("/get", "http://localhost:80/playground");
3 .调用send方法发送请求
xhr.send();
4 .获取服务器端对客户端的响应数据xhr.responseText
xhr.addEventListener("load", function () { //xhr.responseText })
5 .在实际项目中,服务器端大多会以JSON对象的格式来响应数据,但是在http请求与响应的过程中,对象类型最终会转换成字符串进行传输,这是规定,所以在客户端就需要将用JSON.parse将字符串转换为JSON对象类型。转换完成后,再以DOM操作,最终显示到html页面中。
(四)请求方式的传递
传统网站中,不管是post还是get请求参数,都会以key=value&key2=value2的格式进行传输,get请求参数放在地址栏进行传输,post请求参数则放在请求体中。而在AJax中,就需要我们手动拼接参数,并放置在不同的位置进行传输。
1 .get请求放在请求地址中
xhr.open("get", "http://localhost/playground?name=zhangsan&age=18");
2 .post请求通过send方法放在请求体中
xhr.send("name=zhangsan&age=18");
在传递post请求参数前,需要通过xhr.setRequestHeader在请求报文中明确指定请求参数的类型
①像key=value&key2=value2这种字符串拼接的请求参数类型为
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
②JSON数据类型的请求参数为,当以JSON对象格式传递post请求参数时,除了要指定请求体类型,还要将JSON数据通过JSON.stringify转换为字符串格式进行传递
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify({name: "zhangsan", age: 18}));
当客户端以JSON数据格式传递post请求参数时,服务器端接收post请求参数的方法就要从bodyParser.urlencoded变为bodyParser.json。
(五)ajax函数封装
// 获取http状态码: xhr.status 可以通过判断http状态码来向客户响应错误信息
// 获取服务器端返回数据的响应头类型: xhr.getResponseHeader("Content-Type") 可以判断返回了json还是普通字符串
// Ajax函数封装
function ajax(options) {
// 创建默认值对象
var defaults = {
type: "get",
url: "",
data: {},
header: { "Content-Type": "application/x-www-form-urlencoded" },
success: function() {},
error: function() {}
};
// 覆盖默认值
Object.assign(defaults, options);
// 1 .创建ajax对象
var xhr = new XMLHttpRequest();
// 6. 循环调用者传入的对象格式的数据,并进行拼接
var params = "";
for (var attr in defaults.data) {
params += `${attr}=${defaults.data[attr]}&`;
};
// 7 .去掉多余的&符号
params = params.substr(0, params.length - 1);
console.log(params);
// 8 .如果请求类型为get,请求参数拼接到地址栏中
if (defaults.type == "get") {
// xhr.open(defaults.type, `${defaults.url}?${params}`);
defaults.url = `${defaults.url}?${params}`;
}
// 2. 配置
xhr.open(defaults.type, defaults.url);
// 9. 发送请求依然要判断请求类型
if (defaults.type == "post") {
/* 10 .判断用户传入参数类型*/
var contentType = defaults.header["Content-Type"];
// 3 .发送 post请求设置类型
xhr.setRequestHeader("Content-Type", contentType);
if (contentType == "application/json") {
// 如果是json类型,那么不拼接直接发送用户输入的json数据,并转换为字符串传送
xhr.send(JSON.stringify(defaults.data));
} else {
// 如果不是json数据类型,直接发送
xhr.send(params);
}
} else {
xhr.send();
}
// 4 .监听事件
xhr.addEventListener("load", function() {
// 11 .判断服务器端返回的数据类型,如果是json数据类型就转换为json对象格式
var contentType = xhr.getResponseHeader("Content-Type");
var responseText = xhr.responseText;
if (contentType.includes("application/json")) {
responseText = JSON.parse(responseText);
}
// 5 .当请求成功时,调用传入的success函数,并将响应结果传递出去,其实就是闭包
if (xhr.status == 200) {
defaults.success(responseText, xhr);
} else {
defaults.error(responseText, xhr);
}
})
}
// 函数调用方式
/*ajax({
type: "get",
url: "http://localhost/first",
data: {
name: "zhangsan",
age: 20
},
header: {
"Content-Type": "application/x-www-form-urlencoded"
},
success: function(data, xhr) {
console.log(data);
},
error: function(error) {
console.log(error);
}
})*/
(六)客户端art-template模板引擎
1 .下载地址
http://aui.github.io/art-template/docs/installation.html
2 .使用步骤
①准备模板,模板要包裹在script标签中,它不是一个单独的文件,只是html中的一个代码片段,由于html代码编辑器会提示语法错误高亮,所以在标签中指定类型为text/html,另外为了区分多个模板,在标签中定义了id属性.在语法上,和服务器端的art-template模板引擎仅有微小的区别
<script type="text/html" id="tempOne">
{{each result}}
<li>{{$value.username}}</li>
{{/each}}
</script>
②告诉模板引擎要将哪一个模板和哪一个数据进行拼接
当我们引入模板引擎库文件之后,在全局作用域下就会有一个template方法,方法的第一个参数是模板id,第二个参数是一个对象,对象中存储的数据就是要在文件中展示的数据,方法的返回值就是一个拼接好的字符串
var html = template("tempOne", { result//模板中的循环项: //服务器端响应的数据) })
③使用DOM方法将拼接好的字符串展示在页面中
document.querySelector("ul").innerHTML = html;
(七)FormData对象
1 .formdata对象的作用
①模拟HTML表单,相当于将html表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式,请求的发送只能放在xhr.send()方法中,所以formdata对象只能发送post请求。
②异步上传二进制文件,比如图片和视频文件
2 .formdata对象的使用
<body>
<!-- 创建html表单 -->
<form id="form">
<input type="text" name="username">
<input type="password" name="password">
<input type="button" id="btn">
</form>
<script>
// 获取DOM表单
var form = document.querySelector("#form");
var btn = document.querySelector("#btn");
// 绑定事件
btn.addEventListener("click", function() {
//创建formdata实例
var formData = new FormData(form);
// 创建ajax对象
var xhr = new XMLHttpRequest();
xhr.open("post", "http://localhost/formdata");
// 发送请求
xhr.send(formData);
// 接收服务器端结果
xhr.addEventListener("load", function() {
if(xhr.status == 200) {
console.log(xhr.responseText);
}
})
})
</script>
</body>
3 .服务端接收formdata请求
通常在服务器端使用body-parser模块下面的urlencoded方法处理post请求参数,但是它不能处理客户端传递的formdata请求参数,我们要使用第三方模块formidable来处理
const formidable = require("formidable");
const path = require("path");
server.post("post", (req, res) => {
// 创建 formidable实例
const form = new formidable.IncomingForm();
// 设置客户端上传文件的存储路径
form.uploadDir = path.join(__dirname, "uploads");
// 保留上传文件的后缀名 默认为false
form.keepExtensions = true;
form.parse(req, (err, fileds, files) => {
// 处理上传文件路径并返回,用以在客户端即时预览
/*files.客户端上传文件的属性名.path 就是文件地址 */
// 对服务器端响应的文件地址进行截取
res.send({
path: files.attrName.path.spilt("public")[1];
})
})
})
4 .formdata对象实例方法
一般来说,表单数据是不会直接提交到服务器端的,在用户点击提交按钮后,客户端通常会对用户提交的数据进行校验,这就要求我们能够从表单数据中获取输入的值,能够对其进行校验,能够对用户输入的值进行设置,要进行这些操作,都要依赖formdata提供的一系列方法
①获取表单对象中的值 formDate.get(“key”)
<body>
<!-- 创建html表单 -->
<form id="form">
<input type="text" name="username">
<input type="password" name="password">
<input type="button" id="btn">
</form>
<script>
// 获取DOM表单
var form = document.querySelector("#form");
var btn = document.querySelector("#btn");
// 绑定事件
btn.addEventListener("click", function() {
//创建formdata实例
var formData = new FormData(form);
// 获取用户输入的password的值
console.log(formDate.get("password"));
// 创建ajax对象
var xhr = new XMLHttpRequest();
xhr.open("post", "http://localhost/formdata");
// 发送请求
xhr.send(formData);
// 接收服务器端结果
xhr.addEventListener("load", function() {
if(xhr.status == 200) {
console.log(xhr.responseText);
}
})
})
</script>
</body>
②设置表单对象中的值 formData.set(“key”, “值”);
如果设置的属性存在,覆盖原有属性的值,如果不存在,添加此属性,并赋与设置的值
set方法可以应用在表单的二次处理,如当用户发布文章时,如果没有选择发布时间,我们可以利用set方法添加时间属性,并赋予当前时间作为时间属性的值;如用户输入数值时,我们利用set方法为数值保留两位小数点。
formData.set("username", "张三")
③删除表单对象中的属性 formData.delete(“key”);
比如在用户注册时,要求必须输入两次密码以验证密码输入一致,但是向服务器端发送时,我们只需要发送一个密码表单对象,此时,我们就可以用delete方法删除再次输入的密码属性
formData.delete("password");
④在表单对象中添加属性并赋值 formData.append(“key”, “value”);
append方法不会覆盖相同属性,如果添加的属性名相同,保留所有重名属性
formData.append("age", 18);
5 .formdata对象二进制文件上传以及即时预览
<body>
<div class="w">
<div class="file">
<input type="file" id="file">
</div>
<div class="pro">
<fieldset>
<legend>上传进度</legend>
<!-- <progress max="100" value="0"></progress> -->
<div class="progress">
<div class="total">0%</div>
</div>
</fieldset>
<fieldset id="loaded" style="display: none;">
<legend>上传预览</legend>
<div class="upLoaded">
</div>
</fieldset>
</div>
</div>
<script>
var file = document.querySelector("#file");
// 在表单状态变化时(选择了上传文件)触发change事件
file.addEventListener("change", function() {
// 创建formdata实例
var formData = new FormData();
// 上传的二进制文件信息存储在表单对象的files集合下面,获取当前表单对象文件集合中的文件用this.files[0]
formData.append("attrName", this.files[0]);
var xhr = new XMLHttpRequest();
xhr.open("post", "http://localhost/file");
/*在xhr对象下面有一个upload对象,和上传相关的事件
都存储在upload对象里面,upload对象里有一个监控文件上传进度
progress事件 在文件上传过程中触发 */
/*progress事件对象下有e.loaded:当前上传文件进度,e.total:文件总大小属性*/
xhr.upload.addEventListener("progress", function(e) {
// 上传进度
var upTotal = Math.abs((e.loaded / e.total * 100).toFixed(2));
// DOM元素赋值
document.querySelector(".total").style.width = `${upTotal}%`;
document.querySelector(".total").innerHTML = `${upTotal}%`
})
// formdata对象只能使用post请求方法
xhr.send(formData);
// 接收数据
xhr.addEventListener("load", function() {
if (xhr.status == 200) {
// console.log(JSON.parse(xhr.responseText));
// 获取服务器端响应的JSON字符串并解析成json对象
var path = JSON.parse(xhr.responseText);
// 动态创建img标签
var img = document.createElement("img");
// 设置img标签的src属性
img.src = path.path;
//在img绑定load事件, 等数据加载完成后再显示在页面上
img.addEventListener("load", function() {
document.querySelector("#loaded").style.display = "block";
document.querySelector(".upLoaded").appendChild(img);
})
}
});
})
</script>
</body>
(八)非同源请求解决方案 JSONP
所谓同源,就是协议、域名、端口三者都相同的多个地址,三者只要有一个不同,便是非同源,浏览器同源政策禁止非同源请求,以保证网络安全,但实际工作中,往往需要发送和接收非同源请求,JSONP就是其中一个解决方案,它需要前后端代码相互配合以达到非同源请求的需求。
1 .JSONP其实已经不属于Ajax请求的范围,但是它可以模拟Ajax请求,将不同源的服务器请求地址写在script标签的src属性中。填写的请求地址必须返回合法的javascript代码,script标签接收到响应时,会将响应当作JS代码来执行。
2 .服务器端响应的代码,必须是一个函数的调用,并以字符串的方式发送,将响应的数据当作函数的实参
const data = "fn({username: '张三', age: 18})";
res.send(data);
3 .在客户端全局作用域下定义fn函数,用来在接收到服务器端响应的函数后调用,并且,函数的定义一定要定义在响应数据接收代码的前面,否则当服务器端响应数据接收到后,函数还没有定义,程序就会报错。
4 .在函数内部对服务器端返回的数据进行处理。
5 .一个最基本的JSONP请求方案:
<body>
<input type="button" id="btn" value="JSONP点击发送">
<script>
var btn = document.querySelector("#btn");
// 注册事件
btn.addEventListener("click", function() {
// 动态生成script标签
var script = document.createElement("script");
/* 设置标签的src属性,将定义的函数名通过get请求参数数据一起传递
服务器端接收到后直接返回,这样客户端可以随意更改定义的函数名*/
script.src = "http://localhost:88/jsonp?callback=test";
// 将标签添加到body最后面
document.body.appendChild(script);
// 监听script标签的加载状态,加载完成后,删除标签
script.addEventListener("load", function() {
document.body.removeChild(script);
})
})
function test(data) {
console.log(`函数调用成功${data}`);
}
</script>
</body>
6 .JSONP函数封装
// jsonp函数封装
function jsonp(options) {
/*JSONP请求是一个get请求,如果需要传递额外的参数,在
封装函数内部就需要字符串拼接的方式将参数拼接在地址后面
在函数调用时,只需要传递对象格式的数据就可以了 */
var params = "";
for (var attr in options.data) {
params += `&${attr}=${options.data[attr]}`;
}
/* 为了保证jsonp函数的封装性,我们将外部函数的定义封装到jsonp函数里面
这么做就会出现两个问题,1 .函数就不是在全局作用域下了 2 .函数变成了一个
匿名函数 */
/* 解决第一个问题,将函数的定义挂在顶级对象window的属性里,解决第二个问题
为函数随机生成函数名*/
var fnName = `maJsonp${Math.random().toString().replace(".", "")}`;
window[fnName] = options.success;
// 动态创建script标签
var script = document.createElement("script");
script.src = `${options.url}?callback=${fnName}${params}`;
// 添加标签
document.body.appendChild(script);
script.addEventListener("load", function() {
document.body.removeChild(script);
})
}
7 .JSONP服务器端代码
在node.js的express框架中,提供了res.jsonp方法,用来响应jsonp请求
app.get("/jsonp", (req, res) => {
res.jsonp({ name: "zhangsan" });
})
在res.jsonp中其实相当于做了以下事情
app.get("/jsonp", (req, res) => {
const fnName = req.query.callback;
const data = {name: "zhangsan"};
res.send(`${fnName}(${data})`);
})
(九)第二种非同源请求数据的解决方案 CORS跨域资源共享
CORS解决方案,在客户端使用正常的Ajax请求,只需要在服务器端做适当的配置即可。
当客户端向服务器端发送Ajax请求时,浏览器会自动在请求头中添加一个origin属性,属性中包含了协议、域名和端口号,
无论请求是否被同一,服务器端都会回应一个正常的HTTP响应,如果服务器端同意这次请求,会在响应头中加入Access-Control-Allow-Origin字段,字段的值为同意跨域请求的白名单,浏览器会根据响应头中是否由此字段来判断服务器端对响应是同意还是拒绝。
server.use((req, res, next) => {
// 设置响应头白名单 * 表示全部
res.header("Access-Control-Allow-Origin", "*");
// 允许的请求方式
res.header("Access-Contorl-Allow-Methods", "get, post");
next();
})
还有一种非同源服务器端解决方案,利用了同源政策是浏览器给予Ajax技术的限制,但是服务器端是不受限制的。
实现的核心思路就是在服务器端对非同源服务器发送并接收请求,再响应给同源客户端。
1 .服务器端发送请求需要用到node提供的第三方模块request
const request = require("request");
server.get("/test", (req, res) => {
request("http://www.xxxx.com", (err, response, body) => {
// body就是非同源网站响应的数据
res.send(body);
})
})
(十)withCredentials属性
在使用Ajax技术发送跨域请求时,默认是不会携带cookie信息的,但实际工作中会有这种需求,比如跨域登录功能。
withCredentials属性用于在涉及跨域请求时,设定是否携带cookie信息,默认值为false。但是仅仅在客户端设置withCredentials时不够的,在服务器端,还要设置Access-Contorl-Allow-Credentials: true,允许客户端发送请求时携带cookie