# 第一天
# 思考:上网看到的内容在哪里?
无论是使用手机浏览器,还是PC浏览器,上网时看到的内容存在于哪里?
具体一点,我们上网看到的视频、浏览的网页、听到的音乐、看到的图片等等,他们在哪里?
- 它们在一台电脑里
- 我们称这台电脑为 服务器
# 服务器
服务器本质上也是一台电脑,也具有硬盘、CUP、内存等等必要的硬件。
只不过服务器的重要硬件比家用计算机要好。
服务器的作用
- 存储一个网站的文件(HTML、CSS、JS、图片、音乐.....)
- 提供网站的文件给用户
怎么获取服务器
- 购买(京东、淘宝......)
- 租赁(阿里云、腾讯云......)
学习阶段,没有必要购买服务器,老师已经把服务器准备(租)好了,我们可以共用一个服务器。
# 资源
通俗的讲,服务器上的 网页(html文件)、图片、音乐、视频、字体文件、CSS文件、JS文件等等都称之为资源。所以资源代指服务器上存储的内容。
# 数据
网页中使用的部分数据,放到服务器上了,它算不算是资源呢?
答:数据是资源。而且是一个网站的灵魂。
- HTML是骨架
- CSS是颜值
- JS是行为
- 数据是灵魂
☞ 了解
服务器多数情况都使用数据库、数据表的方式来存储数据,和我们平时见到的表格差不多,形式如下:
| id | bookname | author | publisher |
|---|---|---|---|
| 1 | 西游记 | 唐僧 | 大唐出版社 |
| 2 | 水浒传 | 宋江 | 大宋出版社 |
| 3 | 三国演义 | 罗贯中 | 三国出版社 |
| 4 | 斗破苍穹 | 土豆 | 仙侠出版社 |
# 浏览器-服务器交互模型

浏览器-服务器交互模型,是由 请求(request) --- 响应(response) 组合而成的。
# Ajax介绍
AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。简单点说,就是使用浏览器内置对象 XMLHttpRequest 与服务器通信。
Ajax是一种技术,通过浏览器内置对象和服务器进行数据交互的技术。
它可以使用JSON,XML,HTML和text文本等格式发送和接收数据。
AJAX最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。
你可以使用AJAX最主要的两个特性做下列事:
- 在不重新加载页面的情况下发送请求给服务器。
- 接受并使用从服务器发来的数据。
# Ajax的应用场景
- 搜索建议提示(比如百度、京东、淘宝等等)
- 地图(百度地图)
- 验证用户名是否存在
- 网页聊天室
- 无刷新的分页
- .....
- 总之,在不刷新页面的情况下,还要完成页面和服务器的数据交互,都可以使用Ajax
☞ 也许你会想到,使用浏览器输入网址,回车,已经可以请求到页面了,为什么还需要Ajax呢?
- 当在浏览器中输入网址,回车,确实是向服务器发送了请求,这是浏览器自主发送的请求。
- 如果希望页面中局部的数据,比如某个列表中,某个表格中的数据更新,那就用到了Ajax了,如下视频所示。(点击播放下面的视频)
# GET和POST请求方式
当使用浏览器和服务器进行数据交互时,多数都是由浏览器端发起请求,然后才能获取响应结果。
浏览器能够发起的请求,又分为多种请求方式,但常用的请求方式只有下面两种:
- GET ,获取;如果向服务器发送请求,获取服务器的资源,使用GET方式
- 比如获取页面中需要的数据
- 比如获取一个用户的信息(用户名、昵称、头像等)
- POST,邮寄(提交)。如果提交数据给服务器,那么使用POST方式。
- 比如,登录、注册(提交账号和密码给服务器)
- 比如,添加评论,发布评论(贴吧中有)
PS:
☞ 一个网站,99%或者100%只有这两种请求方式。其他请求方式参见这里 (opens new window)。
☞ 无论是浏览器自主发起的请求,还是Ajax请求,都分为各种请求方式。
# jQuery中封装的Ajax方法
前文提到,浏览器使用内置对象 XMLHttpRequest 完成与服务器的通信。目前,jQuery对 XMLHttpRequest 对象的使用做了封装,提供了非常简单的使用方法。
为了方便,我们先学习jQuery封装的方法。过两天,再学习原生的 XMLHttpRequest 对象。
jQuery提供的能够实现ajax请求的方法:
// 专门用于发送GET方式请求
$.get(url, [data], [callback], [dataType])
// 专门用于发送POST请求
$.post(url, [data], [callback], [dataType]);
// 一个综合的发送Ajax请求的方法,使用频率最高
$.ajax({
// 这里填ajax选项
url: Ajax请求的url地址, // 必填
type: 'GET', // 默认GET,可选POST
data: {}, // 请求参数,可选
success: function (res) {} // ajax请求成功时的回调,可选
....其他选项
});
$.ajax()方法的其他选项参见这里 (opens new window)。
# 获取服务器上的数据资源
# 语法
因为是获取服务器上的数据资源,所以要发送GET方式的请求,支持的方法有 $.get()和$.ajax()。
下面演示获取一个服务器上的图书数据
(服务器是前端老师租赁的,我们直接使用即可,通过向下面的url地址发送请求可以获取到服务器上的书籍数据)
var url = 'http://www.liulongbin.top:3006/api/getbooks';
/******************* $.get() ******************/
// 仅仅发送一个GET方式的请求
$.get(url);
// 发送GET请求,并使用回调函数接收服务器响应的结果
$.get(url, function (res) {
console.log(res); // res表示服务器响应的结果
});
// 发送GET请求,并传递一个请求参数(这里传递了id,表示只获取id为1的数据)
$.get(url, {id: 1}, function (res) {
console.log(res)
});
/******************* $.ajax() ******************/
$.ajax({
url: url, //这项必填
type: 'GET', // 这里可以省略该项,因为type默认的值就是GET
data: {id: 1}, // 传递id为1的参数表示只获取id为1的数据,可选
success: function (res) { // success表示ajax请求成功后的回调
console.log(res); // res表示服务器返回的结果
}
});
通过语法,我们发现,$.get()和$.ajax()方法的参数是相通的。只是两个方法的语法不同而已。
$.ajax()相比 $.get() 更加强大,因为 $.ajax() 方法还允许填写更多选项 (opens new window)。
# 案例
☞ 发送一个Ajax请求,获取服务器上的书籍数据,并将结果渲染到表格中。
// ---------------- 获取书籍列表信息,渲染到页面中 ---------------
function getData () {
// 发送ajax请求,获取所有的书籍信息
$.get('http://www.liulongbin.top:3006/api/getbooks', function (res) {
console.log(res);
// 渲染到页面中
if (res.status === 200) {
var listArr = [];
res.data.forEach(item => {
// 向数组中添加单元
listArr.push(`
<tr>
<td>${item.id}</td>
<td>${item.bookname}</td>
<td>${item.author}</td>
<td>${item.publisher}</td>
<td>
<a href="javascript:;" class="del">删除</a>
</td>
</tr>
`);
});
// 循环结束,把数组中的所有tr拼接成字符串,并且放到tbody中
$('tbody').html(listArr.join(''));
}
});
}
getData();
# 向服务器提交数据
# 语法
因为是获取服务器上的数据资源,所以要发送POST方式的请求,支持的方法有 $.post()和$.ajax()。
下面演示向服务器提交一本书籍的信息,让服务器帮我们保存起来。
(服务器能够接收到通过Ajax提交的数据,并会和其他书籍一样保存起来,这是前端老师已经设计好的,我们只需要提交数据即可,只需了解服务器做了什么即可)
// 注意下面的url和前面案例中的url不一样。
// 一个url只能完成一个功能,比如获取书籍数据是一个url,添加书籍是另一个url
var addUrl = 'http://www.liulongbin.top:3006/api/addbook';
/******************* $.post() ******************/
// 仅仅发送一个POST方式的请求,对于本例,仅仅发送一个POST请求没有意义
$.post(addUrl);
// 发送POST请求,并提交一本书的信息,并使用回调函数接收服务器响应的结果
var body = {
bookname: '遮天',
author: '辰东',
publiser: '仙侠出版社'
};
$.get(addUrl, body, function (res) {
console.log(res); // res表示服务器响应的结果
});
/******************* $.ajax() ******************/
$.ajax({
url: addUrl,
type: 'POST', // POST请求,所以这里必须是POST
data: body,
success: function (res) {
console.log(res); // res表示服务器返回的结果
}
});
# 案例
☞ 通过页面的输入框和按钮,完成添加书籍
HTML代码略。
- 给 “添加” 按钮,注册单击事件
- 获取输入框的值
- 判断值是否为空
- 发送ajax请求,提交数据给服务器,完成添加
- 添加成功之后,重新渲染页面,并且清空输入框的值
// ----------------------- 添加书籍 ---------------------
// 1. 给“添加”按钮注册单击事件
$('#btnAdd').click(function () {
// 2. 获取输入框的值(三个值)
var bookname = $('#iptBookname').val().trim();
var author = $('#iptAuthor').val().trim();
var publisher = $('#iptPublisher').val().trim();
// 最好,判断一下,如果上述三个值,有空的,则不允许添加
if (bookname == '' || author == '' || publisher == '') {
alert('必填项不能为空');
return;
}
// 3. 按照接口文档,调用接口,完成添加
var data = {
bookname: bookname,
author: author,
publisher: publisher
};
$.post('http://www.liulongbin.top:3006/api/addbook', data, function (res) {
// 4. 添加成功,调用getData函数,重新渲染页面;另外,清空输入框的值
alert(res.msg); // 无论添加成功还是失败,给出一个提示
if (res.status === 201) {
getData();
$('.form-control').val('');
}
});
});
# 接口及接口文档
# 概念及说明
前面我们使用的 url,叫做数据接口,或者简称为接口。
接口是服务器提供的一个url地址,通过这个url地址,我们可以操作服务器上的资源。
通过Ajax技术向一个接口发送请求,也叫做调用接口。
课程中所用到的接口文档:https://www.showdoc.com.cn/ajaxapi?page_id=3753323218792173 建议大家把它放到浏览器收藏夹。
- 接口是谁设计的呢
- 后端同学设计的(学java的、学php的同学、.....)
- 后端同学设计完接口之后,会提供一个接口文档给我们
- 一个好的接口文档,至少需要包含下面几项内容
- 接口说明(通过接口说明,大致了解到接口是干什么用的)
- 接口的url(发送ajax请求,必要的条件)
- 接口请求方式(发送ajax请求,必要的条件)
- 请求参数(参数名称、数据类型、是否必填、参数说明等)
- 响应格式
- 响应示例
# 案例
首先,根据接口文档,我们了解到,想要删除一本书,必要的条件是传递id参数,所以,方便起见,在循环渲染书籍的时候,给每个删除超链接加入自定义属性 data-id="${item.id}"
- 事件委托方案,给"删除"注册单击事件
- 询问是否要删除
- 获取当前这本书的id
- 渲染数据的时候,给a标签,添加自定义属性data-id,值就是当前书籍的id
- 单击事件中,通过
$(this).data('id')可以获取属性data-id的值
按照接口文档,发送ajax请求,删除书籍- 删除成功,重新渲染所有数据
- 删除成功之后,调用
getData()
- 删除成功之后,调用
// ----------------------- 删除书籍 ---------------------
// 1. 给 “删除” 超链接,注册单击事件
$('tbody').on('click', '.del', function () {
// alert(12);
// 2. 对于删除这种敏感操作,询问是否要删除?
// var a = confirm('你确定不要我了吗?你好狠?');
// console.log(a); // 用户点击了取消,a就是false;用户点击了确定,a就是true
if (!confirm('你确定不要我了吗?你好狠?')) return;
// 3. 按照接口要求发送ajax请求,实现删除
// 获取事件源(a超链接)的自定义属性
// var id = $(this).attr('data-id');
var id = $(this).data('id');
// console.log(id);
// return;
$.get('http://www.liulongbin.top:3006/api/delbook', { id: id }, function (res) {
// console.log(res);
// 提示一下
alert(res.msg); // 无论成功,还是失败,都提示一下
if (res.status === 200) {
// 删除成功,重新渲染页面
getData();
}
});
});
# network工具
network工具使用
- All -- 查看所有请求
- XHR -- 查看Ajax请求
- JS -- 查看请求了哪些JS文件
- CSS -- 查看请求了哪些CSS文件
- Img -- 查看请求了哪些图片
- Media -- 查看请求了哪些音频、视频等
- Font -- 查看请求了哪些字体文件
- Doc -- document,查看请求了哪些html文件
如果报错 xxxx (Too Many Requests),说明请求的太频繁了,过几秒再请求就好了
# 聊天机器人案例
# 效果展示
# 分析
聊天区是一个 ul>li 的列表形式,ul 的 id 为
talk_list我们自己说的内容,用一个
li标签展示,li的类名为right_word<li class="right_word"> <img src="img/person02.png"> <span>约吗?</span> </li>机器人的回复,也是用一个
li展示的,类名为left_word<li class="left_word"> <img src="img/person01.png"> <span>嗨,最近想我没有?</span> </li>滚动条自动出现,并定位到底部的效果,是通过
./js/scroll.js里面的resetui()方法实现的。直接拿来使用即可。语音播放,是因为页面内置了一个隐藏的
audio标签,并且该标签具有autoplay属性。我们只需要设置它的src属性为一个音频地址即可自动播放先注册“发送”按钮的单击事件,最后按回车的时候,触发按钮的单击事件,即可实现 “按回车发送消息” 效果。
写代码前,想好步骤,先做什么,做完之后再做什么。不要抄老师的代码。
# 参考代码
// 自己的JS
// 1. 点击 发送 按钮之后,把自己说的话渲染到页面中
$('#btnSend').on('click', function () {
// 获取自己说的内容(输入框的值)
var myWord = $('#ipt').val();
// 把自己说的话,渲染到页面中(弄一个li标签,放到ul中)
var my_li = `<li class="right_word">
<img src="img/person02.png" /> <span>${myWord}</span>
</li>`;
$('ul#talk_list').append(my_li);
// 清空输入框
$('#ipt').val('');
// 重置滚动条到底部
resetui();
// 把自己说的话渲染完毕,然后才能获取机器人的回复
getRobotWord(myWord);
});
// 2. 发送Ajax请求,获取机器人的回复,并且也渲染到页面中
function getRobotWord (a) {
$.get('http://www.liulongbin.top:3006/api/robot', { spoken: a }, function (res) {
// console.log(res)
// 获取到机器人的回复
var robotWord = res.data.info.text;
// 渲染到页面中
$(`<li class="left_word">
<img src="img/person01.png" /> <span>${robotWord}</span>
</li>`).appendTo('ul#talk_list');
// 重置滚动条到底部
resetui();
// 当获取到机器人的回复之后,才能把机器人的回复转成语音
toVoice(robotWord);
});
}
// 3. 发送Ajax请求,把文字转语音
function toVoice (b) {
$.ajax({
url: 'http://www.liulongbin.top:3006/api/synthesize',
data: { text: b },
success: function (res) {
// console.log(res);
// 设置音频标签的src属性
$('#voice').attr('src', res.voiceUrl);
}
});
}
// 4. 优化。。。。
$('#ipt').on('keyup', function (e) {
// 获取键盘的keyCode值
var keyCode = e.keyCode;
// console.log(keyCode);
if (keyCode === 13) {
// 表示按了回车键
$('#btnSend').trigger('click');
}
})
# 第二天
# 同步请求和异步请求
# 原理
JS代码分为同步代码和异步代码。目前,我们学习过的异步代码有:
- 事件
- 定时器
- Ajax请求
除此之外,其他所有代码都是同步代码。
为什么要把代码分为同步和异步两类呢?因为它们的执行顺序有很大差别。
假设有几段代码,既有同步代码,又有异步代码,他们的执行顺序如下:
- 优先执行同步代码
- 前一行同步代码没有执行完,后面的代码只能等待,这就是“阻塞”效果。
- 遇到异步代码,去排队等待
- 所有的同步代码执行完,才去检查是否有异步代码
- 如果有异步代码,按顺序执行,但不会有“阻塞”效果。
- 异步任务执行前,一般都会提前绑定一个回调函数
- 当前的异步任务执行完毕,就会调用提前帮的回调函数

# 练习
☞ 思考,下面的代码执行顺序是怎样的?
console.log(111);
setTimeout(function () {
console.log(222)
}, 0)
console.log(333)
☞ 思考,下面的代码执行顺序是怎样的?
console.log(111);
$.ajax({
url: 'http://www.liulongbin.top:3006/api/getbooks',
success: function (res) {
console.log(333)
}
})
$.ajax({
url: 'http://www.liulongbin.top:3006/api/news',
success: function (res) {
console.log(444)
}
})
console.log(222);
# 同步的Ajax请求(了解)
目前,我们发送的Ajax请求,都是异步请求。但也可以通过修改选项设置Ajax同步请求。下面是一个例子。
console.log(111);
$.ajax({
url: 'http://www.liulongbin.top:3006/api/getbooks',
success: function (res) {
console.log(222)
},
async: false // 默认true,如果改为false,表示发送 同步 的Ajax请求
});
console.log(333);
同步的Ajax请求了解即可,因为它会阻塞代码的运行,开发中基本不用。
# 常用请求方式的区别
- GET
- GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据。
- 通俗的讲,获取数据应该使用GET方式的请求。
- POST
- POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。
- 添加、修改都可以使用POST请求,因为此类操作都会改变服务器上的资源
- PUT
- PUT方法用请求有效载荷替换目标资源的所有当前表示。
- 修改操作可以选择使用PUT方式
- DELETE
- DELETE方法删除指定的资源。
- 删除操作可以使用DELETE方式
具体使用什么请求方式,必须参照接口文档。
# 深入理解jQuery方法的请求参数
无论使用 $.get() 还是 $.post() 还是 $.ajax() 方法,都可以设置请求参数,即 data。示例如下
- $.get('url',
data, function (res) { ... }) - $.post('url',
data, function (res) { ... }) - $.ajax({
data: { id: 1 } })
实际上,在使用jQuery的上述三个方法的前提下,我们不但可以使用对象形式的参数,也能使用数组或者查询字符串,示例如下:
// 这里使用 $.ajax() 举例,另外两个方法同理
$.ajax({
url: 'http://www.liulongbin.top:3006/api/getbooks',
// 对象形式的写法
data: { id: 1, bookname: '西游记' }
// 数组形式的写法,注意,只能是这种写法
data: [{name:'id',value:1}, {name:'bookname',value:'西游记'}]
// 字符串写法,注意,这种类型的字符串,叫做查询字符串
data: 'id=1&bookname=西游记'
})
无论我们填写的何种形式的参数,都会被jQuery转换成查询字符串形式传递到服务器,因为底层支持查询字符串形式的参数,而不支持字面量对象和数组形式。
这一特点,可以通过network工具查看。
扩展:GET请求传递的叫做请求参数;POST请求提交的一般都叫做请求体。
# serialize和serializeArray方法
☞ 思考一个问题,添加书籍的时候,是如何获取输入框的值的?
答:一个一个获取的。
☞ 思考,如果输入框特别多,而且还有下拉框,单选按钮组、复选按钮组,又该如何获取这些值呢?
答:不知道(心里想肯定有办法,猜测和标题有关系。。。要不然不能讲)
确实,可以使用jQuery提供的 serialize() 或者 serializeArray() 方法获取表单各项的值。
语法:
$('form').serialize();
$('form').serializeArray();
也就是说,使用 serialize() 或者 serializeArray() 方法是通过 表单(form) 调用的,所以必须在HTML页面中加入 <form>...</form> 标签。如下所示:
<form>
<!-- 把所有的框框、按钮都放这里面 -->
<input type="text" name="bookname" /><br />
<input type="password" name="pwd" /><br />
<input type="radio" name="sex" value="nan" checked />男
<input type="radio" name="sex" value="nv" />女<br />
<select name="address">
<option value="bj">北京</option>
<option value="sh">上海</option>
</select><br />
<button>提交</button>
</form>
接下来,就可以使用 serialize() 或者 serializeArray() 方法 获取全部的值了,代码如下:
// 监听表单的 submit 事件 (表单提交时触发)
$('form').on('submit', function (e) {
// 一定阻止表单提交,否则页面会跳转;默认跳转到当前页面,感觉和刷新一样
e.preventDefault();
// 保证页面不会跳转,接下来使用 serialize 获取表单数据
var str = $(this).serialize();
var arr = $(this).serializeArray();
});
小结:
- 在必须具有
<form>...</form>标签的前提下,才能使用serialize()或者serializeArray()方法 - 各项表单元素(input、select、textarea)必须具备
name属性。 - 通过
serialize()得到的是查询字符串类型;通过serializeArray()得到的是数组类型。结果都可以直接当做Ajax请求的参数。 - 两个方法均不能获取 **禁用状态(disabled)**的输入框的值。
- 两个方法均不能获取文件域(
<input type="file" />)的值。 - 两个方法都能获取隐藏域(
<input type="hidden" />)的值。
# 关于HTML表单的补充
一个表单可以分为三个部分组成:
- 表单标签(form)
- 表单域(input、textarea、select)
- 按钮
# form标签
form标签的action属性表示提交地址,默认为空,表示提交到当前页面。
还有 method、enctype、target 三个属性,了解即可。
# 表单域
常用表单域列举
- 单行文本域
<input type="text" /> - 密码框
<input type="password" /> - 单选按钮
<input type="radio" /> - 复选按钮
<input type="checkbox" /> - 隐藏域
<input type="hidden" /> - 文件域
<input type="file" /> - 下拉选择框
<select><option>xxx</option></select> - 多行文本框
<textarea></textarea>
# 按钮
- 普通按钮(点击之后,默认不会发生任何事)
<button type="button">普通按钮1</button><input type="button" value="普通按钮2" />
- 重置按钮(点击之后,默认会重置表单)
<button type="reset">重置按钮1</button><input type="reset" value="重置按钮2" />
- 提交按钮(点击之后,默认会提交表单)
<button>提交按钮1</button><button type="submit">提交按钮2</button><input type="submit" value="提交按钮3" />
☞ 问题1:如果JS代码中,注册了表单的提交事件($('form').on('submit', fun....)),应该使用什么类型的按钮?
答:使用 提交按钮。
☞ 问题2:使用serialize方法收集表单值的时候,能不能找到提交按钮,然后注册单击事件呢?
答:也可以。可以找到form,注册submit事件;也可以找到提交按钮,注册click事件。建议找到form,使用submit事件。
# 模板引擎 art-template
# 案例中渲染数据的问题
- 性能问题,不断的拼接字符串,性能低下
- 代码结构比较混乱,JS代码和HTML代码混合到一起了
解决办法:模板引擎
# 模板引擎简介
模板引擎,可以把数据和准备好的HTML模板组合到一起,得到最终HTML。

使用模板引擎的好处:
- ①减少了字符串的拼接操作
- ②使代码结构更清晰
- ③使代码更易于阅读与维护
- .....
# 尝试编写一个实现模板引擎功能的函数
<!--
定义模板
使用script标签的目的是,不让模板显示到页面
加 type="text/html" 是为了不报错
-->
<script type="text/html" id="test">
<h2>{{title}}</h2>
<p>{{content}}</p>
</script>
<script>
/**
* 实现模板引擎的函数
* @param {string} id 模板id
* @param {object} data 数据
* @returns {string} 模板和数据组合好的结果
*/
function template(id, data) {
var html = document.getElementById(id).innerHTML;
for (var key in data) {
html = html.replace('{{' + key + '}}', data[key]);
}
return html;
}
// 调用 template 函数,监测效果
var result = template('test', {
title: '悯农',
content: '锄禾日当午'
});
console.log(result);
</script>
# art-template模板引擎
市面上,目前有很多模板引擎(JS插件)。
我们上课选择的是art-template这个插件,因为这款模板引擎执行速度是非常快的。
官网:http://aui.github.io/art-template/zh-cn/docs/index.html
# art-template基本使用方法
模板引擎可以把HTML模板和数据组合到一起。
使用方法和使用自己编写的模板引擎函数一样简单。
但art-template比我们自己封装函数功能更加强大,性能更好。
步骤:
- 加载 template-web.js
- 准备模板
- 建议使用script标签,因为script标签里面的内容,不会显示到页面中
- 给script标签加 type="text/html",告诉浏览器,这段代码不是JS,这样不会报错
- 给script标签加 id属性,后面调用 template 函数的时候会用
- 准备数据
- 建议用JS对象
- 调用template函数,完成模板和数据的组合
- 参数1:模板id
- 参数2:数据
- 返回值:组合好的结果
- 最后,按需使用这个结果即可。
参考代码如下:
<script src="./lib/template-web.js"></script>
<!--
模板引擎可以把 HTML模板结构 和 数据 进行组合
测试的话,需要
- 模板结构
- 数据
-->
<!-- 定义一个HTML模板,模板不应该直接显示 -->
<script type="text/html" id="tpl-shi">
<h2>{{title}}</h2>
<small>{{author}}</small>
<p>{{content}}</p>
</script>
<script>
// 正常情况下,数据应该是ajax请求,服务器返回的数据
// 这里为了测试,就不发送ajax请求了,自己模拟一点数据即可
var res = {
title: '锦瑟',
author: '李商隐',
content: '锦瑟无端五十弦,一弦一柱思华年。'
};
// art-template插件给我们提供了一个函数 template,调用这个函数,就可以把HTML结构和数据组合到一起
var html = template('tpl-shi', res);
console.log(html);
</script>
# 模板语法
//模板语法,即只能在模板中使用的形如 `{{xxx}}` 这样的语法。
//art-template提供了非常强大的模板语法,能够适应绝大多数开发场景。
准备的数据如下:
var res = {
title: '静夜思',
author: '李白',
content: '窗前明月光,疑是地上霜',
age: 15,
hobby: ['吃饭', '睡觉', '打豆豆'],
str: 'hello',
str2: 'apple,pear,orange,banana',
str3: '<b>nice to meet you</b>'
};
模板中可以使用的语法如下:
直接输出值
//`{{title}}`原样输出
//`{{@str3}}` 加入@,会显示加粗的效果;不加@,页面中会直接显示标签条件判断
{{if age >= 18}} 成年人 {{else}} 未成年 {{/if}}循环(遍历)
<!-- item 表示数组的值 --> <!-- index 表示数组的下标,如果用不上,index可以不写 --> {{each hobby item index}} <li>{{index}} -- {{item}}</li> {{/each}}模板中可以使用JS内置方法
<!-- 调用toUpperCase() 把要输出的hello变为大写形式 --> {{str.toUpperCase()}} <!-- str2是一个字符串,调用split方法,把字符串变为数组,然后使用each遍历数组 --> {{each str2.split(',') val}} <li>{{val}}</li> {{/each}}模板中使用过滤器
{{str|过滤器名}} 比如: {{str|ucfirst}}// JS代码中,需要在调用 template 函数之前,准备ucfirst这个过滤器函数 template.defaults.imports.ucfirst = function (str) { // 函数的形参str是原来准备要输出的值 // return,返回值是最终要输出到页面中的值 return str.substr(0,1).toUpperCase() + str.substr(1); }
# 新闻列表案例
- 使用Ajax技术获取全部的新闻列表数据。这样数据就有了。
- 设置模板,取一篇新闻(所在的div)当做模板。
- 调用
template(模板id, 数据)函数,并将结果放到页面($('#news-list'))中。 - 使用 模板语法 处理模板。
JS代码参考:
// 自定义函数处理时间日期
template.defaults.imports.abc = function (t) {
// console.log(t) // 形参,就是原来要输出的时间
var date = new Date(t);
var y = date.getFullYear();
var m = date.getMonth() + 1;
var d = date.getDate();
return y + '-' + m + '-' + d;
}
// 发送Ajax请求,获取全部的新闻数据
$.ajax({
url: 'http://www.liulongbin.top:3006/api/news',
success: function (res) {
// console.log(res)
var html = template('tpl-news', res);
// 把模板和数据组合好的结果,放到 div#news-list 中
$('#news-list').html(html);
}
});
HTML模板参考:(图片在服务器上,所以要在图片地址前加根路径)
<script id="tpl-news" type="text/html">
{{each data item index}}
<div class="news-item">
<img class="thumb" src="http://www.liulongbin.top:3006{{item.img}}">
<div class="right-box">
<h1 class="title">{{item.title}}</h1>
<div class="tags">
{{each item.tags.split(',') val}}
<span>{{val}}</span>
{{/each}}
</div>
<div class="footer">
<div>
<span>{{item.source}}</span>
<span>{{item.time|abc}}</span>
</div>
<span>评论数:{{item.cmtcount}}</span>
</div>
</div>
</div>
{{/each}}
</script>
# 第三天
# XMLHttpRequest对象简介
# 纲要
XMLHttpRequest 是一个内建的浏览器对象,Ajax技术的核心就是XMLHttpRequest 对象。
jQuery中的 $.ajax() 、$.get()、$.post() 的底层实现,就是 XMLHttpRequest 。
# 历史
XMLHttpRequest对象最初是 WHATWG(超文本应用程序技术工作组) (opens new window) 的一部分。
2006年移至W3C。
2008年2月,对XMLHttpRequest进行了扩展(如进度事件和跨域请求),也就是所谓的XMLHttpRequest Level 2 (opens new window),直至2011年底。
2012年底,它移回了 WHATWG (opens new window)。
# 小结
- XMLHttpRequest 简称 XHR 对象,是一个浏览器内置对象,目前,所有的浏览器均支持这个对象。
- 它的作用是可以实现Ajax请求,Ajax技术的核心就是
XMLHttpRequest对象。
# 实现Ajax的GET请求
# 步骤
- 创建xhr对象。
- 注册 xhr.onload 事件,当Ajax请求成功后,会触发onload函数。在onload函数中,接收响应结果。
- 调用open方法,初始化一个请求,此方法用于配置请求方式和url。
- 调用send方法,发送请求。
# 基础代码
// 1. 创建xhr对象
var xhr = new XMLHttpRequest();
// 2. 注册 xhr.onload 事件,当Ajax请求**成功**后,会触发onload函数。在onload函数中,接收响应结果。
xhr.onload = function () {
// 使用 xhr.response 接收响应结果
var res = xhr.response;
}
// 3. 调用open方法,设置请求方式及请求的url地址
// xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks');
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=西游记');
// 4. 最后,调用send方法,发送这次ajax请求
xhr.send();
# 请求参数
重要:如果传递请求参数,请求参数要以查询字符串形式拼接到url后面。
形如:url?id=1&bookname=西游记
# 其他说明
| API | 兼容性 |
|---|---|
| XMLHttpRequest | IE7+ 支持 |
| open | 所有浏览器均支持 |
| send | 所有浏览器均支持 |
| onload | Level 2 新增,IE9+ 支持 |
| response | Level 2 新增,IE10+ 支持 |
了解 XMLHttpRequest 对象所有API的兼容性,点击这里 (opens new window)。
稍后,我们再讨论兼容所有浏览器的写法。
# 实现Ajax的POST请求
# 步骤
(比GET请求多了一行代码)
- 创建xhr对象。
- 注册 xhr.onload 事件,当Ajax请求成功后,会触发onload函数。在onload函数中,接收响应结果。
- 调用open方法,初始化一个请求,此方法用于配置请求方式和url。
- 调用setRequestHeader方法,设置请求头。
- 调用send方法,发送请求。
# 基础代码
// 1. 创建xhr对象
var xhr = new XMLHttpRequest();
// 2. 注册 xhr.onload 事件,当Ajax请求**成功**后,会触发onload函数。在onload函数中,接收响应结果。
xhr.onload = function () {
// 使用 xhr.response 接收响应结果
var res = xhr.response;
}
// 3. 调用open方法,设置请求方式及请求的url地址
xhr.open('POST', 'http://www.liulongbin.top:3006/api/addbook');
// 4. 调用setRequestHeader,设置请求头,目的是告知服务器以何种方式解析请求体
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// 5. 最后,调用send方法,发送这次ajax请求
xhr.send('bookname=西游记&author=唐僧&publisher=大唐出版社');
# 请求体
重申一下,POST请求的参数,称之为请求体。
和GET请求一样,请求体也要写成查询字符串格式,不同之处是,请求体做为send方法的参数发送。
# 其他说明
| API | 兼容性 |
|---|---|
| setRequestHeader | 所有浏览器均支持 |
setRequestHeader 方法用于设置请求头,格式如下:
xhr.setRequestHeader('名称', '值');
- 设置的请求头可以在 network 面板 > Headers > Request Headers 中查看。
- 大部分请求头由浏览器管理,不允许我们修改。所以我们无需关心。
- 一旦设置了请求头,就无法撤销了。
# Content-Type
- 这里设置的
Content-Type,作用是告知服务器,浏览器提交的数据是何种类型的。- 值为:application/x-www-form-urlencoded(需要自己指定) ,表示客户端提交的是查询字符串。
- 值为:application/json(需要自己指定) ,表示客户端提交的是 JSON 字符串。
- 值为:multipart/form-data(xhr对象会自动设置),表示客户端提交的是 FormData 对象。
# 对请求参数进行编码
# 什么是URL编码?
- 把中文和部分特殊符号转成 URL的标准格式 ,这就是url编码
- 如,把“中文” 进行url编码后得到 “%E4%B8%AD%E6%96%87”
# 为什么要对请求参数进行编码
- RFC文档规定,只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*’(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。
- 对于Unicode字符,RFC文档建议使用utf-8对其进行编码得到相应的字节,然后对每个字节执行百分号编码,也就是所谓的 URL编码。
# 编码能够解决的问题
- 乱码问题(浏览器使用utf-8、服务器使用其他编码,传输数据的时候,就会乱码)
- 提交的内容有特殊符号问题,比如添加一本书,书名是 “红&黑”
# 如何进行URL编码
JS提供了内置编码解码函数
- 编码:encodeURIComponent('西游记')
- 解码:decodeURIComponent("%E8%A5%BF%E6%B8%B8%E8%AE%B0");
具体查看手册:https://www.w3school.com.cn/jsref/jsref_obj_global.asp
# 小结
无论是GET请求还是POST请求,向服务器传递参数的时候,必须要对参数进行编码。这里的参数包括参数名和值。
比如,参数为 shu ming=红&黑
- 参数名中有空格,需要编码;
- 值有中文,且有歧义,需要编码;
# readyState属性
Ajax从创建xhr对象开始,一直到完全接收服务器返回的结果为止;我们可以把整个请求响应过程划分为5个阶段。并且可以使用 xhr.readyState 属性检测当前请求执行到哪个阶段了。
readyState属性值为一个数字,不同的数字表示Ajax的不同状态。
- 如果状态值为0(xhr.readyState === 0),初始状态,表示xhr对象一定创建了。
- 如果状态值为1(xhr.readyState === 1),表示open一定调用了
- 如果状态值为2(xhr.readyState === 2),表示send一定调用了,并且已经接收到响应头。
- 如果状态值为3(xhr.readyState === 3),表示正在接收服务器返回的数据(可能已接收完毕,也可能正在接收中,取决于数据量的大小)
- 如果状态值为
4(xhr.readyState === 4),表示Ajax请求~响应过程完成

值为0、1、2、3的时候,基本上不用关心。主要关心值为4的时候,因为这个时候表示Ajax请求~响应过程完成了,如果成功的结束了,则可以完全接收服务器响应的结果。
下面在创建xhr对象后和onload事件内部分别输出 xhr.readyState 可以分别得到 0 和 4。
// 1. 创建xhr对象
var xhr = new XMLHttpRequest();
console.log(xhr.readyState); // ===> 0
// 2. 注册 xhr.onload 事件,当Ajax请求**成功**后,会触发onload函数。在onload函数中,接收响应结果。
xhr.onload = function () {
console.log(xhr.readyState); // ===> 4
// 使用 xhr.response 接收响应结果
var res = xhr.response;
}
// 3. 调用open方法,设置请求方式及请求的url地址
// xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks');
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=西游记');
// 4. 最后,调用send方法,发送这次ajax请求
xhr.send();
# onreadystatechange事件
# 介绍
onreadystatechange 翻译过来是 当Ajax的请求状态改变的时候。
所以,它是配合上述的 readyState 使用的事件。
事件具体的触发时机如下:
- readyState属性值改变的时候
- 0 --> 1
- 1 --> 2
- 2 --> 3
- 3 --> 4
- 接收到的数据量改变的时候,此时 readyState 的值保持为3,但也会触发 onreadystatechange 事件(发生在分块接收大量数据的时候)
所以,绑定的onreadystatechange事件,可能会触发多次。
# 代替onload事件解决兼容问题
前文提到,onload有浏览器兼容问题,如果你的项目需要支持低版本的浏览器,那么可以使用 onreadystatechange事件代替onload事件。
由于onreadystatechange事件可能会触发多次,所以需要在事件中加入判断,已保证准确的接收到响应结果。
// 1. 创建xhr对象
var xhr = new XMLHttpRequest();
// IE6 创建对象 var xhr = new ActiveXObject('Microsoft.XMLHTTP');
// 2. 使用onreadystatechange代替onload
xhr.onreadystatechange = function () {
// 判断Ajax请求是否完成
if(xhr.readyState === 4) {
// 还要根据响应状态码判断,请求是否成功
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log('请求失败')
}
}
}
// 3. 调用open方法,设置请求方式及请求的url地址
// xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks');
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=西游记');
// 4. 最后,调用send方法,发送这次ajax请求
xhr.send();
既然要解决兼容性问题,所以把 response 换成没有兼容问题的 responseText。区别是response可以接收任何类型的响应结果,而responseText只能接收文本类型的结果。
# 封装Ajax
思路:
想好了,最终封装成什么样的 --- 封装成和 $.ajax 一样的(简略版)
我们希望和调用jQuery的$.ajax方法一样,来调用自己封装的函数,形式如下:
// 假设我们封装了一个 ajax 函数,调用方式如下 ajax({ type: 'GET', url: 'xxxx', data: {}, success: function (res) { // res 表示服务器响应的结果 } });封装函数 ajax,参数只有一个,是对象形式,参照上面的代码
函数体
- 基本的Ajax代码(基本的步骤写出来)
- 判断GET和POST请求,分别写 open 和 send 方法
- 处理请求参数,把对象形式的参数处理成查询字符串格式
- 当ajax请求成功之后,调用success函数,把服务器响应的结果当做实参传给success。
- 细节处理(默认GET方式、不区分大小写等等、响应结果是否转换成JS对象...)
封装,不可能封装的和jQuery那样强大;通过自己封装,我们能够体会到jQuery中的$.ajax()方法是怎么封装的。
参考代码如下:
/**
* 把字面量对象转换成查询字符串
* @param {object} obj 一个字面量对象
* @returns {string} 查询字符串
*/
function ObjectToQueryString(obj) {
var arr = [];
for (var key in obj) {
arr.push(`${key}=${obj[key]}`);
}
return arr.join('&');
}
/**
* 实现Ajax请求
* @param {object} option 对象形式的参数
* @param {string} option.type 请求方式
* @param {string} option.url 接口地址
* @param {object} option.data 请求参数
* @param {callback} option.success 成功后的回调
*/
function ajax(option) {
// 1. 把请求方式统一转大写,为后面的判断做准备
var type = option.type.toUpperCase();
// 2. 调用上面的函数,把请求参数处理成查询字符串
var params = ObjectToQueryString(option.data);
// 3. 写Ajax的基本步骤
var xhr = new XMLHttpRequest();
xhr.onload = function () {
// 当响应成功后,把JSON格式的结果转换为JS对象
var res = JSON.parse(this.responseText);
// 把结果,传递给 success 回调函数
option.success(res);
}
// 判断是GET还是POST请求方式
if (type === 'GET') {
xhr.open('GET', option.url + '?' + params);
xhr.send();
} else if (type === 'POST') {
xhr.open('POST', option.url);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(params);
}
}
# JSON
# JSON在Ajax中的作用
JSON在Ajax请求的过程中,作用就是作为数据的载体。
比如中国人和英国人交流,双方的语言不通,所以必须找一个翻译。
服务端使用的编程语言可能是java、php等,前端使用的编程语言是JavaScript,双方的数据格式可能不一样,所以在交互数据的时候,得转换成双方都能识别的格式,比如JSON格式。
古老的XML也具有和JSON相同的作用,现在基本不用了。
# 编写JSON
JSON长得和JS数据差不多,但JSON是字符串类型。比如:
// JS 对象
var obj = { id: 1, name: 'zs' };
// JSON 字符串
var json = '{ "id": 1, "name": "zs" }';
很少直接在JS代码中写JSON。一般都是在JSON文件(
xxx.json)中写JSON。Ajax课程中,接口响应的结果都是JSON字符串格式。
编写JSON的具体要求:
- 不允许出现 undefined
- 不允许写注释
- 不能有函数
- 无论是属性名还是字符串类型的值,都必须加双引号。(单引号都不行)
- JSON中可以包括的数据类型
- 数字
- 字符串(必须加双引号)
- 布尔
- null
- 数组
- 对象
- 一个完整的JSON字符串,前后的括号必须对应,且不能有其他内容。
# JSON和JS数据转换
JSON ----> JS
- var JS数据 = JSON.parse(JSON字符串);
- 这个过程叫做 反序列化
JS ----> JSON
- var JSON字符串 = JSON.stringify(JS数据);
- 这个过程叫做 序列化
请思考下面的转换:
// ------------------------- 转成JSON格式 --------------------
var obj = {
// 这里有一个注释
id: 1,
name: 'zs',
age: undefined,
sayHi: function () {
console.log('Hi~');
},
};
console.log( JSON.stringify(obj) );
// 思考,答案是怎样的呢?
// ---------------------- JSON转成JS数据 ---------------------
var json = 'true123["apple", "orange"]';
var aa = JSON.parse(json); // 报错,因为把三部分JSON格式的字符串放到一起了
# 设置HTTP请求时限
- timeout -- Level 2 新增,IE8+支持,用于设置请求的超时时间,单位是毫秒
- ontimeout -- Level 2 新增,IE10+支持,如果请求超时了,会触发 ontimeout 事件
var xhr = new XMLHttpRequest()
xhr.onload = function () {
console.log(JSON.parse(this.response));
}
xhr.timeout = 30; // 单位是毫秒
// 当请求超时之后,会触发下面的函数
xhr.ontimeout = function () {
alert('请求超时,请刷新重试');
}
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks');
xhr.send();
# FormData对象
# 介绍
Form是表单,Data是数据。猜测,它和表单数据有关系。
FormData (opens new window)是h5出现之后,新增的一个对象。用于管理表单数据。IE10+支持。
可以这样理解,FormData的作用和 jQuery中的 serialize() 作用一样,用于快速收集表单数据,并且可以将结果直接提交给接口。
创建的FormData对象,可直接通过 xhr.send(FormData对象) 提交给服务器的接口。
# 基本语法
var fd = new FormData([form]); // 参数是表单的DOM对象,可选
FormData的API:(除了append方法IE10支持外,其他方法IE均不支持)
append('key', 'value');-- 向对象中追加数据set('key', 'value');-- 修改对象中的数据- delete('key'); -- 从对象中删除数据
- get('key') -- 获取对象中的数据
- getAll('key') -- 获取指定key的全部数据
- forEach() -- 遍历对象中的数据
- ....
<form action="">
<input type="text" name="username"><br />
<input type="password" name="pwd"><br />
<input type="radio" name="sex" value="nan">
<input type="radio" name="sex" value="nv"><br />
<button>提交</button>
</form>
<script>
document.querySelector('form').onsubmit = function (e) {
e.preventDefault();
// 使用FormData收集表单数据
var fd = new FormData(this); // 传入表单的DOM对象
// append向fd对象中追加数据
fd.append('age', 20); // 追加一个age
fd.append('username', 'lisi'); // 追加一个username
// set修改fd中的数据
fd.set('sex', 'yao'); // 修改sex
// get用于获取一个值
console.log( fd.get('username') ); // 获取到一个username的值
console.log( fd.getAll('username') ); // 获取到全部username的值
// console.log(fd); // 输出fd没用,看不到数据
// 只能通过forEach来检查对象中有哪些数据
fd.forEach(function (val, key) {
// console.log(key, val);
});
}
</script>
# 使用FormData的注意事项(必看)
- 使用FormData,要求表单各项必须有name属性,因为FormData也是根据表单各项的name属性获取值的
- 实例化 FormData对象,传入表单的DOM对象,可以快速收集到表单各项值。
- 可以收集文件信息,这是和 serialize 不一样的。可以完成文件上传。
- 如果要检查FormData中有哪些值,需要使用forEach遍历。
- 如果需要动态添加或修改FormData中的值,可以调用 FormData的append或set方法。
# FormData配合XHR对象
FormData本就是配合XHR对象一起使用的
前面我们收集到了很多值,现在,我们可以通过Ajax,将这些值提交给接口。
- 只能通过POST方式提交FormData对象
- 不能设置Content-Type请求头,因为当提交FormData的时候,XHR对象会自动设置这个请求头。
<form action="">
姓名:<input type="text" name="username"><br>
年龄:<input type="text" name="age"><br>
身高:<input type="text" name="height"><br>
<button>提交</button>
</form>
<script>
document.querySelector('form').onsubmit = function (e) {
e.preventDefault();
var fd = new FormData(this);
// ajax提交数据到接口
var xhr = new XMLHttpRequest();
xhr.onload = function () {
console.log(this.response);
}
xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata');
// xhr.setRequestHeader(); // 使用FormData,不要设置请求头;写了请求头,反而会报错
xhr.send(fd);
}
</script>
如果提交FormData数据给接口,需要接口的支持。比如添加图书、添加评论接口都不支持提交FormData数据。
# FormData配合jQuery使用
<form action="">
姓名:<input type="text" name="username"><br>
年龄:<input type="text" name="age"><br>
身高:<input type="text" name="height"><br>
<button>提交</button>
</form>
<script src="./jquery.js"></script>
<script>
$('form').on('submit', function (e) {
e.preventDefault();
// 使用FormData收集数据
var fd = new FormData(this); // 传入DOM对象哟~~~
// 使用$.ajax()提交
$.ajax({
url: 'http://www.liulongbin.top:3006/api/formdata',
type: 'POST',
data: fd, // 这里直接使用FormData
processData: false, // 必填项
contentType: false, // 必填项
success: function (res) {
console.log(res);
}
})
})
</script>
processData:
前文讲,jQuery默认会把data转换成查询字符串格式,这里 processData: false ,表示不要把FormData对象转换成查询字符串。因为原生(底层)实现是 xhr.send(fd),也是直接提交FormData对象的。
contentType:
前文讲,提交FormData,不能自己设置Content-Type这个请求头,这里 contentType: false,表示不要设置这个请求头。
# FormData和serialize的区别
- FormData属于原生的代码;serialiaze和serializeArray是jQuery封装的方法
- 共同点:都需要设置表单各项的name属性。
- jQuery中提交FormData,必须指定 processData: false 和 contentType: false。
- FormData可以收集文件域的值,而serialize不能。也就是说,如果有文件上传,必须使用FormData。
- 得到的结果的数据类型不一样(知道即可)
- FormData收集到的数据是JS对象格式;
- serialize得到的是查询字符串格式;
- serializeArray得到的是数组格式;
# 使用FormData的案例
# 上传文件并显示进度条(原生代码)
用到的接口:
- 请求方式:post
- 接口地址:http://www.liulongbin.top:3006/api/upload/avatar
- 请求参数(FormData对象):
- avatar -- 文件对象
实现过程:
- 先完成提交文件,即使用FormData获取文件域的内容,然后Ajax提交FormData对象给接口。
- 再能够上传文件的基础之上,注册xhr.upload.onprogress事件,监听上传进度变化,制作进度条
参考代码:
<form>
<input type="file" name="avatar">
<button>上传</button>
</form>
<!-- 设置一个进度条,这里使用h5新标签,开始隐藏 -->
<progress max="0" value="0" style="display: none"></progress>
<script>
document.querySelector('form').onsubmit = function (e) {
e.preventDefault();
// 使用FormData收集输入框的值(这里是选择的图片)
var fd = new FormData(this);
// ajax提交fd对象
// 接口文档,查看ppt即可
var xhr = new XMLHttpRequest();
/*************************************************/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
// 找到 进度条标签
var progress = document.querySelector('progress');
// 注册上传监听事件
xhr.upload.onprogress = function (e) {
progress.style.display = 'block';
progress.max = e.total; // 文件总大小,单位字节
progress.value = e.loaded; // 已经上传了多少字节
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/************************************************/
xhr.onload = function () {
console.log(this.response);
}
xhr.open('POST', 'http://www.liulongbin.top:3006/api/upload/avatar');
xhr.send(fd);
}
</script>
注意,上传进度使用xhr.upload.onprogress事件;下载进度使用xhr.onprogress事件;
# 上传文件并显示进度条(jQuery实现)
写基础的代码,实现文件上传
<form> <input type="file" name="avatar"> <button>上传</button> </form> <!-- 设置一个进度条,这里使用h5新标签,开始隐藏 --> <progress max="0" value="0" style="display: none"></progress> <script> $('form').on('submit', function (e) { e.preventDefault(); // 使用FormData收集输入框的值(这里是选择的图片) var fd = new FormData(this); // ajax提交fd对象 $.ajax({ type: 'POST', url: 'http://www.liulongbin.top:3006/api/upload/avatar', data: fd, processData: false, contentType: false, success: function (res) { console.log(res); } }); }); </script>加入xhr选项,写原生的代码,实现进度条
- jQuery没有提供实现进度条的选项,但是提供了一个xhr选项,允许我们写原生的代码。最终实现进度条,还是通过原生的代码实现的。
- 注意:使用xhr选项,必须
return xhr;
$.ajax({
type: 'POST',
url: 'http://www.liulongbin.top:3006/api/upload/avatar',
data: fd,
processData: false,
contentType: false,
success: function (res) {
console.log(res);
},
/*************************************************/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
xhr: function () {
var xhr = new XMLHttpRequest();
var progress = $('progress');
// 注册上传监听事件
xhr.upload.onprogress = function (e) {
progress.attr('max', e.total).attr('value', e.loaded).show()
};
return xhr; // 必须返回xhr
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/************************************************/
});
# 第四天
前面3天,已经把Ajax相关知识点讲解完毕。
第4天,全天围绕一个综合案例,我们从页面布局,一直到完成页面的功能,都有自己写。
案例使用单独的笔记,具体参考 “Ajax案例.md”
# 案例说明
- 案例需要完成对数据的增删改查4项操作。其中查询、添加、删除我们都已经做过。
- 案例采用身份认证方式,即必须登录,才能完成数据的增删改查。
- 登录后,只能操作自己的数据。即只能看到自己添加的数据,只能删除自己的数据,只能修改自己的数据。
案例接口文档:https://docs.apipost.cn/view/8675137ccc2b3ac0#3361314
案例是后面课程大事件项目的一部分。
# 准备工作
- 到线上演示地址(http://www.itcbc.com:8888/login.html),注册一个账号。
# 创建案例目录
- 项目中用到的lib文件夹,去资料里面复制。
- lib文件夹里面是项目所需的第三方文件,比如jquery.js、template-web.js、layui等等。
创建好的项目文件结构如下:
|- code
|- category.html (分类的查询、添加、修改、删除)
|- login.html (完成登录功能)
|- js (存放自己的js文件)
|- css (存放自己的css文件)
|- lib
|- jquery.js
|- template-web.js
|- layui
# layui介绍
# 介绍
类似于bootstrap,layui (opens new window)是一个经典模块化前端框架。
- 提供了大量基础类(比如背景颜色、布局、文字等),写页面的时候,可以直接使用这些类。
- 页面元素,可以轻松做出 图标 (opens new window)、按钮 (opens new window)、表单 (opens new window)、表格 (opens new window)、面板 (opens new window)等页面元素。
- 内置模块,可以轻松实现网页常用功能,比如表单验证 (opens new window)、分页 (opens new window)、流加载 (opens new window)、弹出层 (opens new window)、轮播图 (opens new window)等效果。
详细的文档 (opens new window),让我们使用起来得心应手。
# 使用
- 如果只是做页面布局,一般只需要加载 /lib/layui/css/layui.css 即可。
- 如果涉及到功能(比如弹出层等),还需要加载 /lib/layui/js/layui.all.js 文件。
# 登录功能
# 准备工作
- 创建 js/login.js
- 创建 css/login.css
- login.html 中,引入所需的文件
- head区 引入 layui.css
- head区 引入 自己的 login.css
- body区 引入 jquery.js
- body区 引入 自己的 login.js
# 页面布局
表单区
- 去 layui官网 --> 文档 --> 页面元素(侧边栏) --> 表单
- 复制(form标签、输入框、按钮)
- 自己稍微修改一下
图标
- 去 layui官网 --> 文档 --> 页面元素(侧边栏) --> 图标
完整的HTML:
<div class="login">
<div class="title">后台管理系统</div>
<!-- 表单区域 start -->
<form class="layui-form login-form" action="">
<!-- 第一行 用户名 -->
<div class="layui-form-item">
<i class="layui-icon layui-icon-username"></i>
<input type="text" name="title" required lay-verify="required" placeholder="请输入标题"
autocomplete="off" class="layui-input">
</div>
<!-- 第二行 密码 -->
<div class="layui-form-item">
<i class="layui-icon layui-icon-password"></i>
<input type="password" name="title" required lay-verify="required" placeholder="请输入密码"
autocomplete="off" class="layui-input">
</div>
<!-- 第三行 按钮 -->
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid layui-bg-blue" lay-submit lay-filter="formDemo">立即提交</button>
</div>
<!-- 第四行 去注册 -->
<div class="layui-form-item right-a">
<a href="javascript:;">去注册账号</a>
</div>
</form>
<!-- 表单区域 end -->
</div>
完整的css样式:
html, body {
height: 100%;
background-color: skyblue;
}
/* body {
background: url();
} */
/* 登录盒子 */
.login {
width: 400px;
height: 310px;
background-color: #fff;
position: absolute;
/* left和top,50%指的是body的50% */
left: 50%;
top: 50%;
/* 下面的50%,指的是盒子自身的50% */
transform: translate(-50%, -50%);
}
.title {
font-size: 28px;
color: skyblue;
text-align: center;
height: 60px;
line-height: 60px;
}
.login-form {
margin: 0 30px;
}
.right-a {
display: flex;
justify-content: flex-end;
}
/* 图标相关设置 */
.layui-form-item {
position: relative;
}
.layui-icon {
position: absolute;
left: 5px;
top: 10px;
}
.layui-input {
padding-left: 24px;
}
# 完成登录功能
- 异步提交账号密码
- 成功后,跳转到category.html
// --------------------- 登录功能 ------------------------
// 1. 监听表单提交事件
$('.login-form').on('submit', function (e) {
// 2. 阻止表单提交
e.preventDefault();
// 3. 收集表单中的数据(FormData or serialize 必须用serialize)
// 必须要检查表单各项的name属性值;让它和接口要求的参数名一致
var data = $(this).serialize();
// console.log(data);
// 4. ajax提交给登录接口
$.ajax({
type: 'POST',
url: 'http://www.itcbc.com:8080/api/login',
data: data,
success: function (res) {
console.log(res);
if (res.status === 0) {
// 登录成功,跳转到 category.html
location.href = 'category.html';
} else {
alert('登录失败');
}
}
});
})
# token原理
登录成功之后,登录接口会返回一个叫做 token 的字符串。
token字符串,是客户端的身份凭证(车票、证件)
客户端需要存储token字符串,一般存放到本地存储中,备用
当请求以
/my开头的接口的时候,必须在请求头中携带Authorization,值是token字符串。否则不允许访问接口。(相当于必须带着自己的证件,才能正常请求这类接口)

所以,当登录成功后,需要把 token 字符串保存到本地存储中。
success: function (res) {
// console.log(res);
if (res.status === 0) {
// 先保存token,然后再跳转
localStorage.setItem('token', res.token);
// 登录成功,跳转到 category.html
location.href = 'category.html';
} else {
alert('登录失败');
}
}
# 类别页面布局
# 准备工作
- 创建所需的js文件、css文件
- category.html 中,加载所需的js文件和css文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="lib/layui/css/layui.css">
<link rel="stylesheet" href="css/category.css">
</head>
<body>
<script src="lib/jquery.js"></script>
<script src="lib/template-web.js"></script>
<!-- 必须加载layui.all.js,因为页面中用到了很多layui提供的功能,比如弹层 -->
<script src="lib/layui/layui.all.js"></script>
<!-- 加载自己的js文件 -->
<script src="js/category.js"></script>
</body>
</html>
# 页面布局
- layui文档 --> 页面元素(侧边栏) --> 面板 --> 卡片面板 --> 复制代码,实现卡片布局
- 自己写css,设置body的背景色,卡片区的外边距
- 标题区
- 有一行文字
- 一个按钮(layui文档-->页面元素-->按钮-->复制代码)
- 自己写css,调整标题区里的元素两端对齐
- 内容区
- 复制一个表格(layui文档-->页面元素-->表格-->复制代码)
- 调整列的宽度
- 最后一列中,放编辑和删除两个按钮
完整的HTML结构:
<div class="layui-card">
<div class="layui-card-header">
<!-- 标题区 -->
文章类别管理
<button type="button" class="layui-btn layui-btn-normal layui-btn-sm">添加类别</button>
</div>
<div class="layui-card-body">
<!-- 内容区 -->
<table class="layui-table">
<colgroup>
<col width="40%">
<col width="40%">
<col> <!-- 剩余的20% 就是第三列的宽度 -->
</colgroup>
<thead>
<tr>
<th>类别名称</th>
<th>类别别名</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>贤心</td>
<td>2016-11-29</td>
<td>
<button type="button" class="layui-btn layui-btn-xs">编辑</button>
<button type="button" class="layui-btn layui-btn-xs layui-btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
完整的css代码:
body {
background-color: #f2f3f5;
}
/* 调整卡片面板的边距 */
.layui-card {
margin: 15px;
}
/* 设置标题区 */
.layui-card-header {
display: flex;
align-items: center; /*上下居中对齐*/
justify-content: space-between; /*左右两端对齐*/
}
# 获取类别列表
# ajax请求,获取分类列表数据
- 在category.js中,封装renderCategory函数,里面发送ajax请求获取类别列表数据
- 必须加请求头
- 请求头的键是
Authorization - 请求头的值,获取本地存储中的 token。(登录后已经保存)
- 请求头的键是
// ----------------------------- 获取分类,渲染到页面中 ---------------------------
// 直接封装成函数,目的是,添加完成、删除完成、修改完之后,还要调用这个函数来重新渲染页面
// render -- 渲染
// category -- 类别
// ------------------------ 获取类别列表,渲染到页面中 -----------------------
function renderCategory () {
$.ajax({
url: 'http://www.itcbc.com:8080/my/category/list',
success: function (res) {
// console.log(res);
if (res.status === 0) {
// 把数据 通过模板引擎 渲染到页面中
var html = template('tpl-list', res);
// 把html放到tbody中
$('tbody').html(html);
}
},
// 设置请求头
headers: {
// Authorization: 'token字符串'
Authorization: localStorage.getItem('token')
}
});
}
// 别忘记调用函数哟~~~
renderCategory();
# 通过模板引擎渲染数据
HTML模板:
<!-- 类别列表模板 start -->
<script type="text/html" id="tpl-list">
{{each data val}}
<tr>
<td>{{val.name}}</td>
<td>{{val.alias}}</td>
<td>
<button type="button" class="layui-btn layui-btn-xs">编辑</button>
<button type="button" class="layui-btn layui-btn-xs layui-btn-danger">删除</button>
</td>
</tr>
{{/each}}
</script>
<!-- 类别列表模板 end -->
# 删除类别
# 注册事件并获取分类id
//- 遍历数据的时候,给删除按钮加自定义属性 `data-id` ,值是 `{{val.id}}`。 //- 必须使用事件委托的方式,给删除注册单击事件。
代码略。
# 使用弹出层
- 文档位置:
- layui文档 --> 内置模块(侧边栏) --> 弹出层 --> 目录(右侧)--> 内置方法 --> 询问框
- 可以使用在线调试测试弹层的代码
进入到弹出层页面之后,有一个独立版本的链接,通过这个链接,可以看到所有的弹层效果。
# 发送Ajax请求,完成删除
// ------------------------ 删除分类 -----------------------
$('tbody').on('click', 'button:contains("删除")', function () {
// 获取id
var id = $(this).data('id');
// 询问是否要删除
layer.confirm('你是否要删除吗?', function (index) {
// do something
// 点击确定,这个函数触发了
// console.log(id);
// 发送ajax请求进行删除操作
$.ajax({
url: 'http://www.itcbc.com:8080/my/category/delete',
data: { id: id },
success: function (res) {
// console.log(res);
// 无论删除成功,还是失败,都给出提示
layer.msg(res.message);
// 删除成功,重新渲染页面
if (res.status === 0) {
renderCategory();
}
},
// 设置请求头
headers: {
// Authorization: 'token字符串'
Authorization: localStorage.getItem('token')
}
});
// 关闭弹层
layer.close(index);
});
});
# 添加类别
# 点击按钮,实现弹层
注册按钮的单击事件
显示弹层
// 声明一个变量,让它表示弹层;后面关闭弹层时会用到它。 var addIndex; // 一、点击添加分类,实现弹层 $('button:contains("添加类别")').click(function () { addIndex = layer.open({ type: 1, title: '添加类别', content: $('#tpl-add').html(), // 内容在HTML中 area: ['500px', '250px'] }); });HTML中,制作添加的模板,下面是一个演示效果。
<!-- 添加的模板 start --> <script type="text/html" id="tpl-add"> <form action=""> 类别名称:<input type="text"><br /> 类别名称:<input type="text"><br /> <button>确认添加</button> <button>重置</button> </form> </script> <!-- 添加的模板 end -->
# 设置弹层的内容
- 具体查看layui官网,去复制表单的内容。
- 给 表单加一个类或者id,方便找到它。这里我加的是
id="add-form" - 设置input的name属性值,值必须和接口要求的请求参数一致。
完整的模板:
<!-- 添加的模板 start -->
<script type="text/html" id="tpl-add">
<!-- 表单去layui官网复制 -->
<form class="layui-form" id="add-form" style="margin: 15px 30px 0 0">
<!-- 第一项:类别名称 -->
<div class="layui-form-item">
<label class="layui-form-label">类别名称</label>
<div class="layui-input-block">
<input type="text" name="name" required lay-verify="required" placeholder="请输入类别名称" autocomplete="off"
class="layui-input">
</div>
</div>
<!-- 第二项:类别别名 -->
<div class="layui-form-item">
<label class="layui-form-label">类别别名</label>
<div class="layui-input-block">
<input type="text" name="alias" required lay-verify="required" placeholder="请输入类别别名" autocomplete="off"
class="layui-input">
</div>
</div>
<!-- 第三项:按钮(这个按钮去表单那复制) -->
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">确认添加</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</script>
<!-- 添加的模板 end -->
# 提交表单数据,完成添加
- 事件委托的方式,给添加的form注册submit事件
- 使用 serialize 获取输入框的值(
注意:需要修改input的name属性) - ajax提交数据,完成添加
- 添加成功,除了要渲染数据之外,还需要调用
layer.close(addIndex)关闭弹层
// 二、提交表单数据,完成添加
// 注册表单的submit事件
$('body').on('submit', '#add-form', function (e) {
e.preventDefault();
// 规律:如果没有图片上传,一般都不使用FormData。
// 具体:还得看接口要求
var data = $(this).serialize(); // 一定要检查input是否有正确的name属性
// ajax提交数据,完成添加
$.ajax({
type: 'POST',
url: 'http://www.itcbc.com:8080/my/category/add',
data: data,
success: function (res) {
// 无论成功失败,都提示
layer.msg(res.message);
// 添加成功
if (res.status === 0) {
renderCategory();
// 关闭弹层
layer.close(addIndex);
}
},
// 设置请求头
headers: {
// Authorization: 'token字符串'
Authorization: localStorage.getItem('token')
}
});
});
# 编辑类别
# 点击编辑按钮,实现弹出层
复制添加的代码(html模板和js代码)即可。当然要稍作修改
- 复制JS弹出层代码
- 弹出层的索引
editIndex - 修改弹层的标题
- 修改弹层的内容
content: $('#tpl-edit').html()
- 弹出层的索引
- 复制添加类别的模板
- 修改模板的id为
tpl-edit - 修改表单form的id 把
add-form改为edit-form - 修改标题的文字和按钮文字
- 添加隐藏域id
<input type="hidden" name="id" />,老师添加到按钮前面了
- 修改模板的id为
修改分类的接口,要求提交分类id,所以在表单中隐藏一个id。后面通过 serialize() 能够获取隐藏域的值。
# 设置input的默认值
☞ 重要:所有的编辑操作,都会有类似的,为input设置value的操作。
☞ 重要:这一个步骤,也叫做数据回填,或者叫做为表单赋值。
☞ 重要:做法也不一定一样,只要能够把输入框的默认值设置好即可。
我们采用一个简单的办法,思路来源于删除操作,具体如下:
- 给编辑按钮添加三个自定义属性,分别存放当前分类的id、name、alias值
- 点击编辑按钮的时候,获取事件源的三个自定义属性值
- 当弹出层出现之后,找到input,设置他们的value值。
HTML的改变(设置编辑按钮的 data-xxx 属性值):
<button type="button" class="layui-btn layui-btn-xs edit" data-id="{{val.id}}" data-name="{{val.name}}" data-alias="{{val.alias}}">编辑</button>
JS代码:
// ----------------------- 编辑 (点击 编辑,显示弹层) -----
$('body').on('click', 'button:contains("编辑")', function () {
// 获取事件源上的 三个 data-xxx 属性值
var zhi = $(this).data();
// console.log(zhi); // { name: 'xx', alias: 'xx', id: 2 }
// editIndex 表示当前的弹层;关闭弹层的时候,需要用到它
editIndex = layer.open({
type: 1,
title: '编辑文章分类',
content: $('#tpl-edit').html(),
area: ['500px', '250px'],
// 弹层弹出后的回调,不要和ajax中的success弄混了
success: function () {
// 数据回填(不要忘记id)
$('#edit-form input[name="name"]').val(zhi.name);
$('#edit-form input[name="alias"]').val(zhi.alias);
$('#edit-form input[name="id"]').val(zhi.id);
}
});
});
# 提交表单,完成编辑
- 注册表单的submit事件
- 获取输入框的值
- ajax提交给接口
// ----------------------- 编辑 (点击 确认修改,完成编辑) -----
$('body').on('submit', '#edit-form', function (e) {
e.preventDefault();
// 收集表单各项值
var data = $(this).serializeArray();
// ajax提交,完成修改
$.ajax({
type: 'POST',
data: data,
url: 'http://www.itcbc.com:8080/my/category/update',
success: function (res) {
// 无论成功,还是失败,都给出提示
layer.msg(res.message);
if (res.status === 0) {
renderCategory();
layer.close(editIndex);
}
},
// 设置请求头
headers: {
// Authorization: 'token字符串'
Authorization: localStorage.getItem('token')
}
});
})
# 优化项目
统一配置url和headers和complete
案例中,所有接口的根路径都一样,分类相关操作的接口都带了请求头,还有如果token过期了怎么处理。所有的这一切,我们可以写一个统一的处理文件common.js中。页面中只需要引入这个js文件即可。
common.js代码如下:
// 所有ajax请求,统一配置
$.ajaxPrefilter(function (option) {
// option 就是ajax选项;我们可以修改它,也可以增加一些选项
// 1. 统一配置url
option.url = 'http://www.itcbc.com:8080/' + option.url;
// 2. 统一配置请求头
option.headers = {
Authorization: localStorage.getItem('token')
};
// 3. 请求完成后,如果接口返回“身份认证失败”,则需要跳转到登录页面
option.complete = function (xhr) {
var res = xhr.responseJSON;
if (res && res.status === 200 && res.message === '身份认证失败') {
location.href = './login.html';
}
}
});
category.html中引入 common.js ,注意位置,必须在 category.js 引入之前。
发送ajax请求,使用简短的url,不用写根路径了。
不用每次发请求都写headers请求头了
实际开发中,无非就是对数据增删改查。
这是大事件项目中的一部分。后面的大事件课,就是许多套增删改查操作。