# Ajax
Ajax基础
传统网站中存在的问题:
-
网速慢的情况下,页面加载时间长,用户只能等待
-
表单提交后,如果一项内容不合格,需要重新填写所有表单内容
-
页面跳转,重新加载页面,造成资源浪费,增加用户等待时间
Ajax概述:
它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验
Ajax的运行环境
Ajax 技术需要运行在网站环境中才能生效,当前课程会使用Node创建的服务器作为网站服务器。
Ajax运行原理及实现
Ajax 相当于浏览器发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验。
实现步骤
服务器端响应的数据格式
在真实的项目中,服务器端大多数情况下会以 JSON 对象作为响应数据的格式。当客户端拿到响应数据时,要将 JSON 数据和 html 字符串进行拼接,然后将拼接的结果展示在页面中。
在 http 请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输。
请求参数传递
传统网站表单提交:
请求报文
在 HTTP 请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,这些数据和信息要遵守规定好的格式。
请求参数格式
获取服务器端的响应
Ajax状态码:
在创建ajax对象,配置ajax对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是ajax状态码。
- 0:请求未初始化(还没有调用open())
- 1:请求已经建立,但是还没有发送(还没有调用send())
- 2:请求已经发送
- 3:请求正在处理中,通常响应中已经有部分数据可以用了
- 4:响应已经完成,可以获取并使用服务器的响应了
获取服务器端的响应
onreadystatechange 事件
当 Ajax 状态码发生变化时将自动触发该事件。
在事件处理函数中可以获取 Ajax 状态码并对其进行判断,当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。
两种获取服务器端响应方式的区别:
Ajax错误处理
低版本IE浏览器的缓存问题
问题:在低版本的 IE 浏览器中,Ajax 请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依然拿到的是缓存中的旧数据。
解决方案:在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同
Ajax封装
问题:发送一次请求代码过多,发送多次请求代码冗余且重复。
解决方案:将请求代码封装到函数中,发请求时调用函数即可。
请求参数要考虑的问题:
1.请求参数位置的问题:
将请求参数传递到ajax函数内部, 在函数内部根据请求方式的不同将请求参数放置在不同的位置
get 放在请求地址的后面
post 放在send方法中
2.请求参数格式的问题:
application/x-www-form-urlencoded => 参数名称=参数值&参数名称=参数值 => name=zhangsan&age=20
application/json => {name: \'zhangsan\', age: 20}
1.传递对象数据类型对于函数的调用者更加友好 2.在函数内部对象数据类型转换为字符串数据类型更加方便
代码如下:
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, - 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\']
// 设置请求参数格式的类型
xhr.setRequestHeader(\'Content-Type\', contentType);
// 判断用户希望的请求参数格式的类型
// 如果类型为json
if (contentType == \'application/json\') {
// 向服务器端传递json数据格式的参数
xhr.send(JSON.stringify(defaults.data))
}else {
// 向服务器端传递普通类型的请求参数
xhr.send(params);
}
}else {
// 否则get发送请求
xhr.send();
}
// 监听xhr对象下面的onload事件
// 当xhr对象接收完响应数据后触发
xhr.onload = function () {
// xhr.getResponseHeader()
// 获取响应头中的数据
var contentType = xhr.getResponseHeader(\'Content-Type\');
// 服务器端返回的数据
var responseText = xhr.responseText;
// 如果响应类型中包含applicaition/json
if (contentType.includes(\'application/json\')) {
// 将json字符串转换为json对象
responseText = JSON.parse(responseText)
}
// 当http状态码等于200的时候
if (xhr.status == 200) {
// 请求成功 调用处理成功情况的函数
defaults.success(responseText, xhr);
}else {
// 请求失败 调用处理失败情况的函数
defaults.error(responseText, xhr);
}
}
}
ajax({
type: \'post\',
// 请求地址
url: \'http://localhost:3000/responseData\',
success: function (data) {
console.log(\'这里是success函数\');
console.log(data)
}
})
模板引擎
art-template使用步骤
art-template语法格式
标准语法
{{value}}
{{data.key}}
{{data[\'key\']}}
{{a ? b : c}}
{{a || b}}
{{a + b}}
原始语法
<%= value %>
<%= data.key %>
<%= data[\'key\'] %>
<%= a ? b : c %>
<%= a || b %>
<%= a + b %>
循环语法:
{{each target}}
{{$index}} {{$value}}
{{/each}}
<% for(var i = 0; i < target.length; i++){ %> <%= i %> <%= target[i] %><% } %>
案例
验证邮箱地址唯一性
代码:
// 获取页面中的元素
var emailInp = document.getElementById(\'email\');
var info = document.getElementById(\'info\');
// 当文本框离开焦点以后
emailInp.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({
type: \'get\',
url: \'http://localhost:3000/verifyEmailAdress\',
data: {
email: email
},
success: function (result) {
console.log(result);
info.innerHTML = result.message;
info.className = \'bg-success\';
},
error: function (result) {
console.log(result)
info.innerHTML = result.message;
info.className = \'bg-danger\';
}
});
}
搜索框内容自动提示
代码:
<script type="text/html" id="tpl">
{{each result}}
<li class="list-group-item">{{$value}}</li>
{{/each}}
</script>
<script>
var searchInp = document.getElementById(\'search\');
var listBox = document.getElementById(\'list-box\');
var timer = null;
// 当用户在文本框中输入的时候触发
searchInp.oninput = function () {
// 清除上一次开启的定时器
clearTimeout(timer);
// 获取用户输入的内容
var key = this.value;
if (key.trim().length == 0) {
listBox.style.display = \'none\';
return;
}
// 开启定时器 让请求延迟发送
timer = setTimeout(function () {
ajax({
type: \'get\',
url: \'http://localhost:3000/searchAutoPrompt\',
data: {
key: key
},
success: function (result) {
// 使用模板引擎拼接字符串
var html = template(\'tpl\', {result: result});
// 将拼接好的字符串显示在页面中
listBox.innerHTML = html;
// 显示ul容器
listBox.style.display = \'block\';
}
})
}, 800)
}
</script>
判断输入是否为空:
var textValue = text.replace(/(^\\s*)|(\\s*$)/g, ""); //替换输入内容当中所有的空字符,包括全角空格,半
if(textValue==null || textValue=="") {
alert("输入的内容为空,无法查询!");
return false;
}
省市区联动
模板:
<!-- 省份模板 -->
<script type="text/html" id="provinceTpl">
<option>请选择省份</option>
{{each province}}
<option value="{{$value.id}}">{{$value.name}}</option>
{{/each}}
</script>
<!-- 城市模板 -->
<script type="text/html" id="cityTpl">
<option>请选择城市</option>
{{each city}}
<option value="{{$value.id}}">{{$value.name}}</option>
{{/each}}
</script>
<!-- 县城模板 -->
<script type="text/html" id="areaTpl">
<option>请选择县城</option>
{{each area}}
<option value="{{$value.id}}">{{$value.name}}</option>
{{/each}}
</script>
js代码:
<script>
// 获取省市区下拉框元素
var province = document.getElementById(\'province\');
var city = document.getElementById(\'city\');
var area = document.getElementById(\'area\');
// 获取省份信息
ajax({
type: \'get\',
url: \'http://localhost:3000/province\',
success: function (data) {
// 将服务器端返回的数据和html进行拼接
var html = template(\'provinceTpl\', {province: data});
// 将拼接好的html字符串显示在页面中
province.innerHTML = html;
}
});
// 为省份的下拉框添加值改变事件
province.onchange = function () {
// 获取省份id
var pid = this.value;
// 清空县城下拉框中的数据
var html = template(\'areaTpl\', {area: []});
area.innerHTML = html;
// 根据省份id获取城市信息
ajax({
type: \'get\',
url: \'/cities\',
data: {
id: pid
},
success: function (data) {
var html = template(\'cityTpl\', {city: data});
city.innerHTML = html;
}
})
};
// 当用户选择城市的时候
city.onchange = function () {
// 获取城市id
var cid = this.value;
// 根据城市id获取县城信息
ajax({
type: \'get\',
url: \'http://localhost:3000/areas\',
data: {
id: cid
},
success: function(data) {
var html = template(\'areaTpl\', {area: data});
area.innerHTML = html;
}
})
}
</script>
FormData
FormData对象的作用
FormData对象的使用
var btn = document.getElementById(\'btn\');
// 获取表单
var form = document.getElementById(\'form\');
// 为按钮添加点击事件
btn.onclick = function () {
// 将普通的html表单转换为表单对象
var formData = new FormData(form);
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 对ajax对象进行配置
xhr.open(\'post\', \'http://localhost:3000/formData\');
// 发送ajax请求
xhr.send(formData);
// 监听xhr对象下面的onload事件
xhr.onload = function () {
// 对象http状态码进行判断
if (xhr.status == 200) {
console.log(xhr.responseText);
}
}
}
服务器端使用formidable解析formdata对象数据:
app.post(\'/formData\', (req, res) => {
// 创建formidable表单解析对象
const form = new formidable.IncomingForm();
// 解析客户端传递过来的FormData对象
form.parse(req, (err, fields, files) => {
res.send(fields);
});
FormData对象的实例方法
FormData二进制文件上传
FormData文件上传进度展示
FormData文件上传图片即时预览
服务器端:
app.post(\'/upload\', (req, res) => {
// 创建formidable表单解析对象
const form = new formidable.IncomingForm();
// 设置客户端上传文件的存储路径
form.uploadDir = path.join(__dirname, \'public\', \'uploads\');
// 保留上传文件的后缀名字
form.keepExtensions = true;
// 解析客户端传递过来的FormData对象
form.parse(req, (err, fields, files) => {
// 将客户端传递过来的文件地址响应到客户端
res.send({
path: files.attrName.path.split(\'public\')[1]
});
});
});
客户端完整:
// 获取文件选择控件
var file = document.getElementById(\'file\');
// 获取进度条元素
var bar = document.getElementById(\'bar\');
// 获取图片容器
var box = document.getElementById(\'box\');
// 为文件选择控件添加onchanges事件
// 在用户选择文件时触发
file.onchange = function () {
// 创建空的formData表单对象
var formData = new FormData();
// 将用户选择的文件追加到formData表单对象中
formData.append(\'attrName\', this.files[0]);
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 对ajax对象进行配置
xhr.open(\'post\', \'http://localhost:3000/upload\');
// 在文件上传的过程中持续触发
xhr.upload.onprogress = function (e) {
// ev.loaded 文件已经上传了多少
// ev.total 上传文件的总大小
var result = (e.loaded / e.total) * 100 + \'%\';
// 设置进度条的宽度
bar.style.width = result;
// 将百分比显示在进度条中
bar.innerHTML = result;
}
// 发送ajax请求
xhr.send(formData);
// 监听服务器端响应给客户端的数据
xhr.onload = function () {
// 如果服务器端返回的http状态码为200
// 说明请求是成功的
if (xhr.status == 200) {
// 将服务器端返回的数据显示在控制台中
var result = JSON.parse(xhr.responseText);
// 动态创建img标签
var img = document.createElement(\'img\');
// 给图片标签设置src属性
img.src = result.path;
// 当图片加载完成以后
img.onload = function () {
// 将图片显示在页面中
box.appendChild(img);
}
}
}
}
同源策略
jsonp
同源策略是浏览器的一种安全策略,所谓同源是指域名,协议,端口完全相同,只有同源的地址才可以相互通过AJAX 的方式请求。
同源或者不同源说的是两个地址之间的关系,不同源地址之间请求我们称之为跨域请求
什么是同源?例如:http://www.example.com/detail.html 与一下地址对比
解决方式一:jsonp
客户端发送jsonp
// 获取按钮
var btn1 = document.getElementById(\'btn1\');
// 为按钮添加点击事件
btn1.onclick = function () {
jsonp({
// 请求地址
url: \'http://localhost:3001/better\',
data: {
name: \'lisi\',
age: 30
},
success: function (data) {
console.log(123)
console.log(data)
}
})
}
function jsonp (options) {
// 动态创建script标签
var script = document.createElement(\'script\');
// 拼接字符串的变量
var params = \'\';
for (var attr in options.data) {
params += \'&\' + attr + \'=\' + options.data[attr];
}
// myJsonp0124741
var fnName = \'myJsonp\' + Math.random().toString().replace(\'.\', \'\');
// 它已经不是一个全局函数了
// 我们要想办法将它变成全局函数
window[fnName] = options.success;
// 为script标签添加src属性
script.src = options.url + \'?callback=\' + fnName + params;
// 将script标签追加到页面中
document.body.appendChild(script);
// 为script标签添加onload事件
script.onload = function () {
document.body.removeChild(script);
}
}
服务器端:
app.get(\'/better\', (req, res) => {
接收客户端传递过来的函数的名称
const fnName = req.query.callback;
将函数名称对应的函数调用代码返回给客户端
const data = JSON.stringify({name: "张三"});
const result = fnName + \'(\'+ data +\')\';
setTimeout(() => {
res.send(result);
}, 1000)
//或者直接用:
res.jsonp({name: \'lisi\', age: 20});
});
CORS跨域资源共享
CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制。
Node 服务器端设置响应头示例代码:
app.get(\'/cross\', (req, res) => {
res.header(\'Access-Control-Allow-Origin\', \'http://localhost:3000\')
// 2.允许客户端使用哪些请求方法访问我
res.header(\'Access-Control-Allow-Methods\', \'get,post\')
res.send(\'ok\')
});
或在全局下拦截所有请求:
app.use((req, res, next) => {
// 1.允许哪些客户端访问我
// * 代表允许所有的客户端访问我
// 注意:如果跨域请求中涉及到cookie信息传递,值不可以为*号 比如是具体的域名信息
res.header(\'Access-Control-Allow-Origin\', \'http://localhost:3000\')
// 2.允许客户端使用哪些请求方法访问我
res.header(\'Access-Control-Allow-Methods\', \'get,post\')
// 允许客户端发送跨域请求时携带cookie信息
res.header(\'Access-Control-Allow-Credentials\', true);
next();
});
访问非同源数据服务器端解决方案
同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制。
向我自己A服务器/server发送ajax请求,再通过A服务器对B服务器/cross发送请求获取数据
A客户端代码:
// 获取按钮
var btn = document.getElementById(\'btn\');
// 为按钮添加点击事件
btn.onclick = function () {
ajax({
type: \'get\',
url: \'http://localhost:3000/server\',
success: function (data) {
console.log(data);
}
})
};
A服务器端代码:
// 向其他服务器端请求数据的模块
const request = require(\'request\');
app.get(\'/server\', (req, res) => {
request(\'http://localhost:3001/cross\', (err, response, body) => {
res.send(body);
})
});
B服务器端代码:
app.get(\'/cross\', (req, res) => {
res.send(\'ok\')
});
cookie实现跨域登录功能
在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。
withCredentials
属性
withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false
A服务器下的客户端对B服务器端发送登录请求,并且携带cookie
A客户端代码:
loginBtn.onclick = function () {
// 将html表单转换为formData表单对象
var formData = new FormData(loginForm);
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 对ajax对象进行配置
xhr.open(\'post\', \'http://localhost:3001/login\');
// 当发送跨域请求时,携带cookie信息
xhr.withCredentials = true;
// 发送请求并传递请求参数
xhr.send(formData);
// 监听服务器端给予的响应内容
xhr.onload = function () {
console.log(xhr.responseText);
}
}
B服务器端:
app.post(\'/login\', (req, res) => {
// 创建表单解析对象
var form = formidable.IncomingForm();
// 解析表单
form.parse(req, (err, fields, file) => {
// 接收客户端传递过来的用户名和密码
const { username, password } = fields;
// 用户名密码比对
if (username == \'itheima\' && password == \'123456\') {
// 设置session
req.session.isLogin = true;
res.send({message: \'登录成功\'});
} else {
res.send({message: \'登录失败, 用户名或密码错误\'});
}
})
});
jQuery中的Ajax
$.ajax()
概述
发送Ajax请求:
注意:
- success函数response参数为服务器端返回的数据,方法内部会自动将json字符串转换为json对象
- data也可以为参数字符串的格式
- beforSend方法的return false可阻止发送Ajax
serialize
方法
作用:将表单中的数据自动拼接成字符串类型的参数
$(\'#form\').on(\'submit\', function () {
// 将表单内容拼接成字符串类型的参数
// username = \'用户输入的内容\' & password = 123456
var params = $(\'#form\').serialize();
//封装函数将用户在表单里输入的内容转化成对象的形式
//{username:\'用户输入的内容\',password:\'123456\'}
//需要用到 serializeArray()方法
serializeObject($(this));
return false;
});
// 将表单中用户输入的内容转换为对象类型
function serializeObject (obj) {
var result = {};
// [{name: \'username\', value: \'用户输入的内容\'}, {name: \'password\', value: \'123456\'}]
var params = obj.serializeArray();
$.each(params, function (index, value) {
result[value.name] = value.value;
})
// 将处理的结果返回到函数外部方便进行处理 {username:\'用户输入的内容\',password:\'123456\'}
return result;
}
发送jsonp
请求
客户端:
function fn (response) {
console.log(response)
}
$(\'#btn\').on(\'click\', function () {
$.ajax({
url: \'/jsonp\',
// 向服务器端传递函数名字的参数名称
jsonp: \'cb\',
jsonpCallback: \'fn\',
// 代表现在要发送的是jsonp请求
dataType: \'jsonp\'/*,
success: function (response)
console.log(response)
}*/
})
});
服务器端:
app.get(\'/jsonp\', (req, res) => {
const cb = req.query.cb
const data = cb+"({name: \'zhaoliu\'})"
res.send(data);
// res.jsonp({
// name: \'lisi\',
// age:50
// })
});
- jsonp修改参数callback名称
- jsonpCallback修改函数名称
$.get、$.post
作用:$.get方法用于发送get请求,$.post方法用于发送post请求。
$.get(\'/base\', \'name=zhangsan&age=30\', function (response) {
console.log(response)
})
Todo案例
客户端:
<!-- 任务列表模板 -->
<script type="text/html" id="taskTpl">
{{each tasks}}
<li class="{{$value.completed ? \'completed\' : \'\'}}">
<div class="view">
<input class="toggle" type="checkbox" {{$value.completed ? \'checked\' : \'\'}}>
<label>{{$value.title}}</label>
<button class="destroy" data-id="{{$value._id}}"></button>
</div>
<input class="edit">
</li>
{{/each}}
</script>
<script type="text/javascript">
// 用于存放任务列表的数组
var taskAry = [];
// 选择任务列表容器
var taskBox = $(\'#todo-list\');
// 添加任务的文本框
var taskInp = $(\'#task\');
// 用于存储未完成任务数量的strong标签
var strong = $(\'#count\');
// 当页面中有ajax请求发送时触发
$(document).on(\'ajaxStart\', function () {
NProgress.start()
})
// 当页面中有ajax请求完成时触发
$(document).on(\'ajaxComplete\', function () {
NProgress.done()
})
// 向服务器端发送请求 获取已经存在的任务
$.ajax({
url: \'/todo/task\',
type: \'get\',
success: function (response) {
// 将已存在的任务存储在taskAry变量中
taskAry = response;
// 拼接字符串 将拼接好的字符串显示在页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
// 获取文本框并且添加键盘抬起事件
taskInp.on(\'keyup\', function (event) {
// 如果用户敲击的是回车键
if (event.keyCode == 13) {
// 判断用户是否在文本框中输入了任务名称
var taskName = $(this).val();
// 如果用户没有在文本框中输入内容
if (taskName.trim().length == 0) {
alert(\'请输入任务名称\')
// 阻止代码向下执行
return;
}
// 向服务器端发送请求 添加任务
$.ajax({
type: \'post\',
url: \'/todo/addTask\',
contentType: \'application/json\',
data: JSON.stringify({title: taskName}),
success: function (response) {
// 将任务添加到任务列表中
taskAry.push(response);
// 拼接字符串 将拼接好的字符串显示在页面中
render();
// 清空文本框中的内容
taskInp.val(\'\');
// 计算未完成任务数量
calcCount ()
}
})
}
});
// 拼接字符串 将拼接好的字符串显示在页面中
function render() {
// 字符串拼接
var html = template(\'taskTpl\', {
tasks: taskAry
});
// 将拼接好的字符串显示在ul标签中
taskBox.html(html);
}
// 当用户点击删除按钮时触发ul标签身上的点击事件
taskBox.on(\'click\', \'.destroy\', function () {
// 要删除的任务的id
var id = $(this).attr(\'data-id\');
// 向服务器端发送请求删除 任务
$.ajax({
url: \'/todo/deleteTask\',
type: \'get\',
data: {
_id: id
},
success: function (response) {
// 从任务数组中找到已经删除掉的任务的索引
var index = taskAry.findIndex(item => item._id == id);
// 将任务从数组中删除
taskAry.splice(index, 1);
// 重新将任务数组中的元素显示在页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
});
// 当用户改变任务名称前面的复选框状态时触发
taskBox.on(\'change\', \'.toggle\', function () {
// 代表复选框是否选中 true 选中 false 未选中的
const status = $(this).is(\':checked\');
// 当前点击任务的id
const id = $(this).siblings(\'button\').attr(\'data-id\');
// 向服务器端发送请求 更改任务状态
$.ajax({
type: \'post\',
url: \'/todo/modifyTask\',
data: JSON.stringify({_id: id, completed: status}),
contentType: \'application/json\',
success: function (response) {
// 将任务状态同步到任务数组中
var task = taskAry.find(item => item._id == id);
// 更改任务状态
task.completed = response.completed;
// 将数组中任务的最新状态更新到页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
});
// 当双击事件名称的时候触发
taskBox.on(\'dblclick\', \'label\', function () {
// 让任务处于编辑状态
$(this).parent().parent().addClass(\'editing\');
// 将任务名称显示在文本框中
$(this).parent().siblings(\'input\').val($(this).text())
// 让文本框获取焦点
$(this).parent().siblings(\'input\').focus();
})
// 当文本框离开焦点的时候
taskBox.on(\'blur\', \'.edit\', function () {
// 最新的任务名称
var newTaskName = $(this).val();
// 编辑任务的id
var id = $(this).siblings().find(\'button\').attr(\'data-id\');
// 向服务器端发送请求 修改任务名称
$.ajax({
url: \'/todo/modifyTask\',
type: \'post\',
data: JSON.stringify({_id: id, title: newTaskName}),
contentType: \'application/json\',
success: function (response) {
// 将当期任务的最新状态同步到任务数组中
var task = taskAry.find(item => item._id == id);
// 修改任务名称
task.title = response.title;
// 将任务数组中的任务同步到页面中
render();
// 计算未完成任务数量
calcCount ()
}
})
});
// 用于计算未完成任务的数量
function calcCount () {
// 存储结果的变量
var count = 0;
// 将未完成的任务过滤到一个新的数组中
var newAry = taskAry.filter(item => item.completed == false);
// 将新数组的长度赋值给count
count = newAry.length;
// 将未完成的任务数量显示在页面中
strong.text(count)
}
</script>
服务器端:
// 引入express框架
const express = require(\'express\');
// 工具库
const _ = require(\'lodash\');
// 对象校验
const Joi = require(\'joi\');
// 创建todo案例路由
const todoRouter = express.Router();
// 导入todo集合构造函数
const Task = require(\'../model/task\');
// 获取任务列表
todoRouter.get(\'/task\', async (req, res) => {
const task = await Task.find();
// 响应
res.send(task);
});
// 添加任务
todoRouter.post(\'/addTask\', async (req, res) => {
// 接收客户端传递过来的任务名称
const { title } = req.body;
// 验证规则
const schema = {
title: Joi.string().required().min(2).max(30)
};
// 验证客户端传递过来的请求参数
const { error } = Joi.validate(req.body, schema);
// 验证失败
if (error) {
// 将错误信息响应给客户端
return res.status(400).send({message: error.details[0].message})
}
// 创建任务实例
const task = new Task({title: title, completed: false});
// 执行插入操作
await task.save();
// 响应
setTimeout(() => {
res.send(task);
}, 2000)
});
// 删除任务
todoRouter.get(\'/deleteTask\', async (req, res) => {
// 要删除的任务id
const { _id } = req.query;
// 验证规则
const schema = {
_id: Joi.string().required().regex(/^[0-9a-fA-F]{24}$/)
}
// 验证客户端传递过来的请求参数
const { error } = Joi.validate(req.query, schema);
// 验证失败
if (error) {
// 将错误信息响应给客户端
return res.status(400).send({message: error.details[0].message})
}
// 删除任务
const task = await Task.findOneAndDelete({_id: _id});
// 响应
res.send(task);
});
// 清除已完成任务
todoRouter.get(\'/clearTask\', async (req, res) => {
// 执行清空操作
const result = await Task.deleteMany({completed: true});
// 返回清空数据
res.send(result);
});
// 修改任务
todoRouter.post(\'/modifyTask\', async (req, res) => {
// 执行修改操作
const task = await Task.findOneAndUpdate({_id: req.body._id}, _.pick(req.body, [\'title\', \'completed\']),{new: true})
// 响应
res.send(task);
});
// 查询未完成任务数量
todoRouter.get(\'/unCompletedTaskCount\', async (req, res) => {
// 执行查询操作
const result = await Task.countDocuments({completed: false});
// 响应
res.send({num: result})
});
// 更改任务全部状态
todoRouter.get(\'/changeAllTasksComplete\', async (req, res) => {
// 状态
const { status } = req.query;
// 执行更改状态操作
const result = await Task.updateMany({}, {completed: status});
// 响应
res.send(result);
});
// 将todo案例路由作为模块成员进行导出
module.exports = todoRouter;
全局事件
// 当页面中有ajax请求发送时触发
$(document).on(\'ajaxStart\', function () {
NProgress.start()
})
// 当页面中有ajax请求完成时触发
$(document).on(\'ajaxComplete\', function () {
NProgress.done()
})
运用nprogress
进度条插件
RESTful风格
传统请求地址:
RESTful API 概述
一套关于设计请求的规范。
- GET: 获取数据
- POST: 添加数据
- PUT: 更新数据
- DELETE: 删除数据
RESTful API 的实现:
客户端:
// 获取用户列表信息
$.ajax({
type: \'get\',
url: \'/users\',
success: function (response) {
console.log(response)
}
})
// 获取id为1的用户信息
$.ajax({
type: \'get\',
url: \'/users/1\',
success: function (response) {
console.log(response)
}
})
// 获取id为1的用户信息
$.ajax({
type: \'delete\',
url: \'/users/10\',
success: function (response) {
console.log(response)
}
})
// 获取id为1的用户信息
$.ajax({
type: \'put\',
url: \'/users/10\',
success: function (response) {
console.log(response)
}
})
服务器端:
// 获取用户列表信息
app.get(\'/users\', (req, res) => {
res.send(\'当前是获取用户列表信息的路由\');
});
// 获取某一个用户具体信息的路由
app.get(\'/users/:id\', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在获取id为${id}用户信息`);
});
// 删除某一个用户
app.delete(\'/users/:id\', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在删除id为${id}用户信息`);
});
// 修改某一个用户的信息
app.put(\'/users/:id\', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在修改id为${id}用户信息`);
});
XML
XML 的全称是 extensible markup language,代表可扩展标记语言,它的作用是传输和存储数据。
客户端:
var btn = document.getElementById(\'btn\');
var container = document.getElementById(\'container\');
btn.onclick = function () {
var xhr = new XMLHttpRequest();
xhr.open(\'get\', \'/xml\');
xhr.send();
xhr.onload = function () {
// xhr.responseXML 获取服务器端返回的xml数据
var xmlDocument = xhr.responseXML;
var title = xmlDocument.getElementsByTagName(\'title\')[0].innerHTML;
container.innerHTML = title;
}
}
服务器端:
app.get(\'/xml\', (req, res) => {
res.header(\'content-type\', \'text/xml\');
res.send(\'<message><title>消息标题</title><content>消息内容</content></message>\')
});