# 第一天

# 思考:上网看到的内容在哪里?

无论是使用手机浏览器,还是PC浏览器,上网时看到的内容存在于哪里?

具体一点,我们上网看到的视频、浏览的网页、听到的音乐、看到的图片等等,他们在哪里?

  • 它们在一台电脑里
  • 我们称这台电脑为 服务器

# 服务器

服务器本质上也是一台电脑,也具有硬盘、CUP、内存等等必要的硬件。

只不过服务器的重要硬件比家用计算机要好。

服务器的作用

  • 存储一个网站的文件(HTML、CSS、JS、图片、音乐.....)
  • 提供网站的文件给用户

怎么获取服务器

  • 购买(京东、淘宝......)
  • 租赁(阿里云、腾讯云......)

学习阶段,没有必要购买服务器,老师已经把服务器准备(租)好了,我们可以共用一个服务器。

# 资源

通俗的讲,服务器上的 网页(html文件)、图片、音乐、视频、字体文件、CSS文件、JS文件等等都称之为资源。所以资源代指服务器上存储的内容

# 数据

网页中使用的部分数据,放到服务器上了,它算不算是资源呢?

答:数据是资源。而且是一个网站的灵魂。

  • HTML是骨架
  • CSS是颜值
  • JS是行为
  • 数据是灵魂

☞ 了解

服务器多数情况都使用数据库、数据表的方式来存储数据,和我们平时见到的表格差不多,形式如下:

id bookname author publisher
1 西游记 唐僧 大唐出版社
2 水浒传 宋江 大宋出版社
3 三国演义 罗贯中 三国出版社
4 斗破苍穹 土豆 仙侠出版社

# 浏览器-服务器交互模型

image-20210107112010772

浏览器-服务器交互模型,是由 请求(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请求

除此之外,其他所有代码都是同步代码

为什么要把代码分为同步和异步两类呢?因为它们的执行顺序有很大差别。

假设有几段代码,既有同步代码,又有异步代码,他们的执行顺序如下:

  1. 优先执行同步代码
  2. 前一行同步代码没有执行完,后面的代码只能等待,这就是“阻塞”效果。
  3. 遇到异步代码,去排队等待
  4. 所有的同步代码执行完,才去检查是否有异步代码
  5. 如果有异步代码,按顺序执行,但不会有“阻塞”效果。
    1. 异步任务执行前,一般都会提前绑定一个回调函数
    2. 当前的异步任务执行完毕,就会调用提前帮的回调函数

image-2021007221412656

# 练习

☞ 思考,下面的代码执行顺序是怎样的?

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。

image-2020070414003708

使用模板引擎的好处:

  • ①减少了字符串的拼接操作
  • ②使代码结构更清晰
  • ③使代码更易于阅读与维护
  • .....

# 尝试编写一个实现模板引擎功能的函数

<!-- 
        定义模板 
            使用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>&nbsp;&nbsp;
          <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 简称 XHR 对象,是一个浏览器内置对象,目前,所有的浏览器均支持这个对象。
  • 它的作用是可以实现Ajax请求,Ajax技术的核心就是XMLHttpRequest 对象。

# 实现Ajax的GET请求

# 步骤

  1. 创建xhr对象。
  2. 注册 xhr.onload 事件,当Ajax请求成功后,会触发onload函数。在onload函数中,接收响应结果。
  3. 调用open方法,初始化一个请求,此方法用于配置请求方式和url。
  4. 调用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请求多了一行代码)

  1. 创建xhr对象。
  2. 注册 xhr.onload 事件,当Ajax请求成功后,会触发onload函数。在onload函数中,接收响应结果。
  3. 调用open方法,初始化一个请求,此方法用于配置请求方式和url。
  4. 调用setRequestHeader方法,设置请求头。
  5. 调用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),表示正在接收服务器返回的数据(可能已接收完毕,也可能正在接收中,取决于数据量的大小)
  • 如果状态值为4xhr.readyState === 4),表示Ajax请求~响应过程完成

image-20210111003705390

值为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: falsecontentType: false
  • FormData可以收集文件域的值,而serialize不能。也就是说,如果有文件上传,必须使用FormData
  • 得到的结果的数据类型不一样(知道即可)
    • FormData收集到的数据是JS对象格式;
    • serialize得到的是查询字符串格式;
    • serializeArray得到的是数组格式;

# 使用FormData的案例

# 上传文件并显示进度条(原生代码)

用到的接口

  • 请求方式:post
  • 接口地址:http://www.liulongbin.top:3006/api/upload/avatar
  • 请求参数(FormData对象):
    • avatar -- 文件对象

实现过程

  1. 先完成提交文件,即使用FormData获取文件域的内容,然后Ajax提交FormData对象给接口。
  2. 再能够上传文件的基础之上,注册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

案例是后面课程大事件项目的一部分。

# 准备工作

  1. 到线上演示地址(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),让我们使用起来得心应手。

# 使用

  • 如果只是做页面布局,一般只需要加载 /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字符串。否则不允许访问接口。(相当于必须带着自己的证件,才能正常请求这类接口)

image-20200817111233252

所以,当登录成功后,需要把 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。后面通过 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请求头了

实际开发中,无非就是对数据增删改查。

这是大事件项目中的一部分。后面的大事件课,就是许多套增删改查操作。