目录
一.Ajax编程基础
1.Ajax 基本介绍
- 传统网站存在的问题:
- 网速慢,页面加载时间长,用户只能等待
- 表单提交后,如果一项内容不合格,需要重新填写所有表单内容
- 页面跳转,重新加载页面,造成资源浪费,增加用户等待时间
- Ajax:实现页面无刷新更新数据,提高用户浏览网站应用的体验
- Ajax 应用场景:
- 页面上拉加载更多数据
- 表单项离开焦点数据验证
- 搜索框提示文字下拉列表
- Ajax 运行环境:需要运行在网站环境中才能生效,比如:Node创建的 网站服务器
- Ajax 运行原理:相当于浏览器 发送请求 与 接收响应 的代理人:
2.Ajax 实现步骤
var xhr = new XMLHttpRequest(); // 1.创建ajax对象 xhr.open('get', 'http://localhost:3000/first'); // 2.告诉 Ajax对象: 1)请求方式 2)请求地址 xhr.send(); // 3.发送请求 xhr.onload = function (){ // 4.获取 服务器端响应到客户端的 数据 console.log(xhr.responseText)} // xhr.responseText 是JSON字符串格式
3.服务器端响应的数据格式
- 服务器端大多数情况会以 JSON对象 作为 响应数据 的格式
- 客户端拿到响应数据时,要将 JSON数据 和 HTML字符串 进行拼接,将拼接结果展示在页面中
- 在 HTTP 请求与响应的过程中,请求参数、响应内容 如果是对象类型,都会被转换为 对象字符串 传输
- JSON.parse():将 json字符串 转换为 json对象
- JSON.stringify():将 json对象 转换为 json字符串
// .html文件 xhr.onload = function (){ // 获取服务器端响应到客户端的数据 var responseText = JSON.parse(xhr.responseText); // 将 JSON字符串 转换为 JSON对象 var str = '<h2>'+ responseText.name +'</h2>'; // 将数据和html字符串进行拼接 document.body.innerHTML = str;} // 将拼接的结果追加到页面中 // app.js文件 app.get('/responseData', (req, res) => { res.send({"name": "zs"}); // 传入的是 json对象格式,HTTP会自动换成 json字符串格式 });
4.请求参数传递
- 传统网站表单提交:
4.1 GET请求参数传递
// .html文件 <p><input type="text" id="username"></p> <p><input type="text" id="age"></p> <p><input type="button" value="提交" id="btn"></p> <script type="text/javascript"> var btn = document.getElementById('btn'); // 获取按钮元素 var username = document.getElementById('username'); // 获取姓名文本框 var age = document.getElementById('age'); // 获取年龄文本框 btn.onclick = function () { // 为按钮添加点击事件 var nameValue = username.value; // 获取用户在文本框中输入的值 var ageValue = age.value; var params = 'username='+ nameValue +'&age=' + ageValue; // 拼接请求参数 var xhr = new XMLHttpRequest(); // 创建ajax对象 xhr.open('get', 'http://localhost:3000/get?'+params); // 配置ajax对象 xhr.send(); // 发送请求 xhr.onload = function () { // 获取服务器端响应的数据 console.log(xhr.responseText) }}</script> // app.js文件 app.get('/get', (req, res) => { res.send(req.query); });
- 注意:get 不能提交 json对象数据格式,传统网站的表单提交也不支持 json对象数据格式
- GET请求参数传递是在 配置ajax对象的时候 传递的:xhr.open('get', 'http://localhost:3000/get?'+params);
4.2 POST请求参数传递
// .html文件 var xhr = new XMLHttpRequest(); // 创建ajax对象 xhr.open('post', 'http://localhost:3000/post'); // 配置ajax对象 // 设置请求参数格式的类型(post请求必须要设置) xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(JSON.stringify({name: 'lisi', age:50})); // 发送请求,post在发送请求时 传递参数 xhr.onload = function () { // 获取服务器端响应的数据 console.log(xhr.responseText)} // app.js文件 app.post('/post', (req, res) => { res.send(req.body); });
- 请求头:设置请求参数格式的类型(post请求必须写):xhr.setRequestHeader('Content-Type', '......');
- 通过 请求头 告诉服务器端,客户端向服务器端传递的 请求参数的格式 是什么
- application/x-www-form-urlencoded: name=Lyrelion&age=20 向服务器传送 字符串格式 的请求参数
- application/json: {name:'zs',age:'20'} 向服务器传送 json对象格式 的请求参数 send(转为json字符串)
- POST请求参数传递是在 发送ajax请求的时候 传递的:xhr.send(params);
- 请求报文:HTTP 请求响应传递的 数据块,包括:请求方式、请求地址、要传送的数据、一些附加信息
- 客户端 → → 服务器端
5.获取服务器端的响应
- Ajax状态码:创建ajax对象,配置ajax对象,发送请求,接收完服务器端响应数据,每个步骤对应的数值
- 0:请求未初始化,创建了 ajax对象,没有调用open()
- 1:请求已经建立,还没有发送,调用了open(),没有调用send()
- 2:请求已经发送,xhr.onreadystatechange = function()......
- 3:请求正在处理中,通常响应中已经有 部分数据 可以用了
- 4:响应已经完成,可以获取并使用服务器的响应数据了
- 获取 HTTP状态码:xhr.status
- 获取 Ajax状态码:xhr.readyState
- onreadystatechange事件:Ajax状态码 发生变化时,将自动触发该事件
- 在事件处理函数中,Ajax状态码为 4 时,就可以通过 xhr.responseText 获取服务器端的响应数据了
// .html文件 var xhr = new XMLHttpRequest(); // 0:创建ajax对象 还没进行配置 xhr.open('get', 'http://localhost:3000/readystate'); // 1:配置ajax对象 还没发送请求 xhr.onreadystatechange = function() { // ajax状态码 发生变化 // 2:请求已经发送了 // 3:已经接收到服务器端的部分数据了 // 4:服务器端的响应数据已经接收完成 if (xhr.readyState == 4) { // 判断 Ajax状态码 为 4 时 console.log(xhr.responseText); }} // 获取服务器端的响应数据 xhr.send(); // app.js文件 app.get('/readystate', (req, res) => { res.send('hello'); });
- 推荐使用 onload事件,仅调用一次,且不需要判断状态码,提高了效率
- 当需要考虑兼容 低版本IE浏览器时,推荐使用 onreadystatechange事件
6.Ajax 错误处理
- 网络畅通,服务器端能接收到请求,但返回的结果不是预期结果:判断服务器端返回的状态码,分别进行处理
- 网络畅通,服务器端没有接收到请求,返回404状态码:检查 请求地址是否错误
- 网络畅通,服务器端能接收到请求,返回500状态码:服务器端错误,如服务器端有不存在的变量名,找后端程序员
- 网络中断,请求无法发送到服务器端,会触发 xhr.onerror事件,在 onerror事件处理函数中对错误进行处理
- Ajax状态码:表示 Ajax请求的过程状态,ajax对象返回的
- Http状态码:表示请求的处理结果,服务器端返回的
// .html文件 var btn = document.getElementById('btn'); btn.onclick = function () { var xhr = new XMLHttpRequest(); xhr.open('get', 'http://localhost:3000/error'); xhr.send(); xhr.onload = function (){ console.log(xhr.responseText); if (xhr.status == 400) { // xhr.status 获取http状态码 alert('请求出错') }} xhr.onerror = function () { // 当网络中断时会触发onerrr事件 alert('网络中断, 无法发送Ajax请求') }} // app.js文件 app.get('/error', (req, res) => { // console.log(abc); 出现了服务器端500错误 声明了不存在的变量 res.status(400).send('not ok'); });
7.低版本 IE浏览器的缓存问题
- 在低版本的 IE浏览器中,Ajax 请求有严重的 缓存问题,即在 请求地址不发生变化 的情况下,只有第一次请求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依然拿到的是缓存中的旧数据
- 解决方案:在请求地址的后面加请求参数,保证每一次请求地址不相同
- xhr.open('get', 'http://www.example.com?t=' + Math.random());
// tml文件 btn.onclick = function () { var xhr = new XMLHttpRequest(); xhr.open('get', 'http://localhost:3000/cache?t=' + Math.random()); xhr.send(); xhr.onreadystatechange = function (){ if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); }}} // app.js文件 app.get('/cache', (req, res) => { fs.readFile('./test.txt', (err, result) => { res.send(result); }); });
8.Ajax 封装
- 问题:发送一次请求代码过多,冗余重复
- 解决方案:将请求代码封装到函数中,发请求时调用函数
- 请求参数要考虑的问题:请求参数位置的问题,请求参数格式的问题
- 请求参数格式的问题:在 ajax函数内部,根据请求方式的不同,将请求参数放置在不同位置
- get 放在请求地址的后面
- post 放在send()方法中
- 请求参数格式的问题:
- application/x-www-form-urlencoded:参数名称=参数值&参数名称=参数值
- application/json(推荐):{name: '茶茶子', age: 21}
- 对象数据类型 对 函数调用者更友好,函数内部 对象数据类型 转换为 字符串数据类型更方便
function ajax (options) { // options用于接收实参,替换Ajax请求参数 var defaults = { // 存储默认值 type: 'get', url: '', data: {}, header: { 'Content-Type': 'application/x-www-form-urlencoded' }, success: function () {}, error: function () {} }; Object.assign(defaults, options); // 使用options对象中的属性覆盖defaults属性 var xhr = new XMLHttpRequest(); // 创建ajax对象 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; } xhr.open(defaults.type, defaults.url); // 配置ajax对象 if (defaults.type == 'post') { // 如果请求方式为post // 用户希望的向服务器端传递的请求参数的类型 var contentType = defaults.header['Content-Type'] xhr.setRequestHeader('Content-Type', contentType); // 设置请求参数格式的类型 // 如果类型为json if (contentType == 'application/json') { // 判断用户希望的请求参数格式的类型 xhr.send(JSON.stringify(defaults.data))// 向服务器传递json数据格式的参数 }else { xhr.send(params); // 向服务器端传递普通类型的请求参数 } }else { // 发送请求 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) }})
二.客户端模板引擎
- 模板引擎:可以将 数据 和 HTML 拼接起来
- 使用步骤:
- 下载 art-template 模板引擎库文件并在 html页面中引入,这指的是客户端模板引擎
- 准备 art-template模板,告诉模板引擎,数据 和 html字符串要如何拼接
- 告诉模板引擎 将哪个模板和哪个数据进行拼接
- 将拼接好的 html字符串添加到页面
- 以前都是在服务器端实现 用模板引擎将数据+html拼接,现在将这部分放到 客户端 实现
- <script type="text/html":令 script模板里的 html标签 得以正常实现效果,即模板引擎应写在 script里
<!-- 1. 将模板引擎的库文件引入到当前页面 --> <script src="/js/template-web.js"></script> </head> <body> <div id="container"></div> <!-- 2.准备art-template模板 --> <script type="text/html" id="tpl"> <h1>{{username}} {{age}}</h1> </script> <script type="text/javascript"> // 3.告诉模板引擎将那个数据和哪个模板进行拼接 // 1) 模板id 2)数据 var html = template('tpl', {username: '茶茶子', age:21 }); // 把拼接好的html字符串 写入页面 document.getElementById('container').innerHTML = html; </script> </body>
1.验证邮箱地址唯一性
- 无需关心服务器如何检测,后端人员会提供 接口文档,前端人员只要调用里面的 参数/地址即可
- 邮箱地址正则表达式:/^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/
- 获取文本框,添加离开焦点事件
- 失去焦点时,检测用户输入的邮箱地址是否符合规则
- 如果不符合规则,阻止程序向下执行并给出提示信息
- 向服务器端发送请求,检测邮箱地址是否被别人注册
- 根据服务器端返回值决定客户端显示何种提示信息
<script src="/js/ajax.js"></script> // 引入 ajax函数向服务器发送请求 <script> 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 // 第一个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>
2.搜索框内容自动提示
- 获取搜索框,添加用户输入事件,onInput()
- 获取用户输入的关键字key
- 向服务器端发送请求,携带关键字作为请求参数
- 将响应数据显示在搜索框底部
- 为了防止重复发送无意义请求,给 ajax()添加定时器
<script src="/js/ajax.js"></script> // 引入ajax函数,向服务器发送请求 <script src="/js/template-web.js"></script> // 引入客户端模板引擎 <script type="text/html" id="tpl"> // 引入客户端模板引擎,指定拼接模板 {{each result}} // 遍历服务器返回的result,将result里的 值$value添加到里面 <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; // 将拼接好的字符串显示在页面中 listBox.style.display = 'block'; // 显示ul容器 }})}, 800)} </script>
3.省市区三级联动
- 获取省份信息,显示在下拉框中
- 为下拉框元素添加表单值改变事件(onchange)
- 当用户选择省份时,根据省份id 获取城市信息
- 当用户选择城市时,根据城市id 获取县城信息
<script src="/js/ajax.js"></script> <script src="/js/template-web.js"></script> <!-- 省份模板 --> <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> // ...同上 <script type="text/html" id="areaTpl"> <option>请选择县城</option> // ...同上 <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) { var html = template('provinceTpl', {province: data}); // 返回的是编号+名字 province.innerHTML = html; }}); province.onchange = function () { // 为省份的下拉框添加值改变事件 var pid = this.value; // 获取省份id // 清空县城下拉框中的数据 var html = template('areaTpl', {area: []}); area.innerHTML = html; ajax({ // 根据省份id获取城市信息 type: 'get', url: '/cities', data: { id: pid // 要传给服务器的数据 }, success: function (data) { var html = template('cityTpl', {city: data}); city.innerHTML = html; }})}; city.onchange = function () { // 为城市的下拉框添加值改变事件 var cid = this.value; // 获取城市id ajax({ // 根据城市id获取县城信息 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 对象作用:
- 将 HTML表单映射成表单对象,自动将表单对象中的数据,拼接成请求参数的格式
- 异步上传二进制文件(视频、图片)
1.模拟 HTML表单
- FormData 对象使用:
- 准备 HTML 表单:不需要method、action属性,交给下面的 ajax进行判断
- 将 HTML表单 转化为 FormData对象
- 提交表单对象
- FormData 对象注意:
- 不能用于 get 请求,因为对象被传递到 send()方法中,而 get 请求参数在请求地址后面
- 服务器端 bodyParser模块 不能解析 formData对象表单数据,需要使用 formidable模块 进行解析
- FormData 对象实例方法:
- 获取表单对象中属性的值:formData.get('key');
- 设置表单对象中属性的值:formData.set('key', 'value');:比如用户没写年龄,客户端自动帮用户补充交给服务器端
- 删除表单对象中属性的值:formData.delete('key');:比如确认密码两遍,只会提交一次给客户端,需要删除一遍
- 追加表单对象中属性的值:formData.append('key', 'value');:和set功能类似
- set() 与 append()区别:属性名已存在的情况下,set()覆盖已有键名的值,append()会保留两个值
- 关于append(),服务器最终会接受 同一键名的两个值,但是只显示最后一个值
<!-- 创建普通的html表单 --> <form id="form"> <input type="text" name="username"> <input type="password" name="password"> <input type="button" id="btn" value="提交"> </form> <script type="text/javascript"> var btn = document.getElementById('btn'); // 获取提交按钮 var form = document.getElementById('form'); // 获取表单对象 btn.onclick = function () { // 将普通的html表单 转换为 表单对象 var formData = new FormData(form); var xhr = new XMLHttpRequest(); // 创建ajax对象 formData.set('age', 21); // set:覆盖原来的值/添加表单原来没有的属性 formData.append('username', '茶茶子'); // append:保留两个值 formData.delete('password'); xhr.open('post', 'http://localhost:3000/formData'); // 对ajax对象进行配置 xhr.send(formData); // 提交表单对象 xhr.onload = function () { // 监听xhr对象的onload事件 if (xhr.status == 200) { // 对象http状态码进行判断 console.log(xhr.responseText); }}} // 创建空的表单对象 var f = new FormData(); f.append('sex', '男'); console.log(f.get('sex')); } </script> // app.js 服务器端代码 const formidable = require('formidable'); app.post('/formData', (req, res) => { const form = new formidable.IncomingForm(); // 创建formidable表单解析对象 // 解析客户端传递过来的 FormData对象 form.parse(req, (err, fields, files) => { res.send(fields); });});
2.异步上传二进制文件
- FormData 二进制文件上传:
- 当用户选择文件时触发:file.onchange()
- FormData 文件上传进度条展示:
- 文件上传过程中持续触发 onprogress事件:xhr.uplode.onprogress = function(ev)
- FormData 文件上传图片即时预览:
- 将图片上传到服务器端后,服务器端通常将 图片地址 做为响应数据传递到客户端
- 客户端可以从 响应数据中 获取图片地址,然后显示图片
<div class="container"> <div class="form-group"> <input type="file" id="file"> // 文件选择区 <div class="padding" id="box"> // 图片预览区 <!--<img src="" class="img-rounded img-responsive">--></div> <div class="progress"> // 进度条 <div class="progress-bar" style="width: 0%;" id="bar">0%</div></div> </div> </div> <script type="text/javascript"> var file = document.getElementById('file'); // 获取文件选择控件 var bar = document.getElementById('bar'); // 获取进度条元素 var box = document.getElementById('box'); // 获取图片容器 // 为文件选择控件添加onchanges事件 在用户选择文件时触发 file.onchange = function () { var formData = new FormData(); // 创建 空的formData表单对象 // 将用户选择的文件追加到formData表单对象中,attrName自己取的名 formData.append('attrName', this.files[0]); // this.files[0]第一个文件 var xhr = new XMLHttpRequest(); // 创建ajax对象 xhr.open('post', 'http://localhost:3000/upload'); // 对ajax对象进行配置 xhr.upload.onprogress = function (ev) { // 在文件上传的过程中持续触发 // ev.loaded 文件已经上传了多少 // ev.total 上传文件的总大小 var result = (ev.loaded / ev.total) * 100 + '%'; bar.style.width = result; // 设置进度条的宽度 bar.innerHTML = result; // 将百分比显示在进度条中 } xhr.send(formData); // 发送ajax请求 xhr.onload = function () { // 监听服务器端响应给客户端的数据 // 如果服务器端返回的http状态码为200 说明请求是成功的 if (xhr.status == 200) { // 将服务器端返回的数据显示在控制台中 var result = JSON.parse(xhr.responseText); var img = document.createElement('img'); // 动态创建img标签 img.src = result.path; // 给图片标签设置src属性 img.onload = function () { // 当图片加载完成以后 box.appendChild(img); // 将图片显示在页面中 }}}} </script>