Ajax 与前后端交互

Ajax 概述

Ajax 的全称是 Asynchronous Javascript And XML(异步 JavaScript 和 XML)。

简单理解:在网页中利用 XMLHttpRequest 对象和服务器进行数据交互的方式,就是 Ajax。

XMLHttpRequest(简称 xhr)是浏览器提供的 JavaScript 成员,如果要在网页中请求服务器上的数据资源,则需要用到 XMLHttpRequest 对象。

客户端请求服务器时,请求的方式有很多种,最常见的两种请求方式分别为 get 和 post 请求。

  • get 请求通常用于获取服务端资源(向服务器要资源),例如:根据 URL 地址,从服务器获取 HTML 文件、CSS 文件、JS 文件、图片文件、数据资源等。
    image-20210917221919876
  • post 请求通常用于向服务器提交数据(往服务器发送资源),例如:登录注册时向服务器提交的登录信息、向服务器上传文件等各种数据提交操作。
    image-20210917221943692

Ajax 能让我们轻松实现网页与服务器之间的数据交互,Ajax 的典型应用场景:

  • 用户名检测:注册用户时,动态检测用户名是否被占用;
  • 搜索提示:当输入搜索关键字时,动态加载搜索提示列表;
  • 数据分页显示:当点击页码值的时候,不用刷新页面动态刷新表格的数据;
  • 数据的增删改查:数据的添加、删除、修改、查询操作。

jQuery 中的 Ajax

浏览器中提供的 XMLHttpRequest 用法比较复杂,所以 jQuery 对 XMLHttpRequest 进行了封装,提供了一系列 Ajax 相关的函数,极大地降低了 Ajax 的使用难度。

jQuery 中发起 Ajax 请求最常用的三个方法如下:

  • $.get(url, [data], [callback]):专门用来发起 get 请求,从而将服务器上的资源请求到客户端来进行使用。
    image-20210917215749095

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // $.get()发起不带参数的请求
    $.get('http://www.liulongbin.top:3006/api/getbooks'function(res{
    console.log(res) // 这里的 res 是服务器返回的数据
    })

    // $.get()发起带参数的请求
    $.get('http://www.liulongbin.top:3006/api/getbooks', { id: 1 }, function(res{
    console.log(res)
    })
  • $.post(url, [data], [callback]):专门用来发起 post 请求,从而向服务器提交数据。
    image-20210917220139328

    1
    2
    3
    4
    5
    6
    7
    $.post(
    'http://www.liulongbin.top:3006/api/addbook', // 请求的URL地址
    { bookname: '水浒传', author: '施耐庵', publisher: '上海图书出版社' }, // 提交的数据
    function(res) { // 回调函数
    console.log(res)
    }
    )
  • $.ajax(settings)$.get(), $.post() 的底层实现,它允许我们对 Ajax 请求进行更详细的配置。上传文件只能使用 $.ajax() 不能使用 $.post(),并且必须指定配置对象中的 processDatacontentType 属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    $.ajax({
    type: '', // 请求的方式,例如 GET 或 POST,请求方式可以小写可以大写,推荐大写
    url: '', // 请求的 URL 地址
    data: { },// 这次请求要携带的数据
    success: function(res) { } // 请求成功之后的回调函数
    })

    // 使用 $.ajax() 发起 GET 请求
    $.ajax({
    type: 'GET', // 请求的方式
    url: 'http://www.liulongbin.top:3006/api/getbooks', // 请求的 URL 地址
    data: { id: 1 },// 这次请求要携带的数据
    success: function(res) { // 请求成功之后的回调函数
    console.log(res)
    }
    })

    // 使用 $.ajax() 发起 POST 请求
    $.ajax({
    type: 'POST', // 请求的方式
    url: 'http://www.liulongbin.top:3006/api/addbook', // 请求的 URL 地址
    data: { // 要提交给服务器的数据
    bookname: '水浒传',
    author: '施耐庵',
    publisher: '上海图书出版社'
    },
    success: function(res) { // 请求成功之后的回调函数
    console.log(res)
    }
    })
  • $(document).ajaxStart()$(document).ajaxStop() 函数会监听当前文档内所有的 Ajax 请求,当发出新的 Ajax 请求或 Ajax 请求结束(可能是请求成功或者请求超时失败)会触发相应的回调函数。

    1
    2
    3
    4
    5
    6
    7
    8
    // 自 jQuery 版本 1.8 起,ajaxStart() 和 ajaxStop() 方法只能被附加到文档
    $(document).ajaxStart(function({
    console.log("发出了一个 Ajax 请求");
    })

    $(document).ajaxStop(function({
    console.log("Ajax 请求结束");
    })

接口文档规范

使用 Ajax 请求数据时,被请求的 URL 地址,就叫做数据接口(简称接口)。

接口文档,顾名思义就是接口的说明文档,它是我们调用接口的依据。好的接口文档包含了对接口 URL,参数以及输出内容的说明,参照接口文档就能方便的知道接口的作用,以及接口如何进行调用。

接口文档可以包含很多信息,也可以按需进行精简,不过,一个合格的接口文档,应该包含以下 6 项内容,从而为接口的调用提供依据:

  1. 接口名称:用来标识各个接口的简单说明,如登录接口,获取图书列表接口等。
  2. 接口 URL:接口的调用地址。
  3. 调用(请求)方式:接口的调用(请求)方式,如 GET 或 POST。
  4. 参数格式:接口需要传递的参数,每个参数必须包含参数名称、参数类型否必选、参数说明这4项内容。
  5. 响应格式:接口的返回值的详细描述,一般包含数据名称、数据类型、说明3项内容。
  6. 返回示例(可选):通过对象的形式,例举服务器返回数据的结构。

接口文档实例:

image-20210917222659894

image-20210917222725164

image-20210917222732127

form 表单

表单在网页中主要负责数据采集功能。HTML 中的 <form> 标签,就是用于采集用户输入的信息。通过 <form> 标签的提交操作,把采集到的信息提交到服务器端进行处理。

<form> 标签中的属性则是用来规定如何把采集到的数据发送到服务器。

image-20210918154941942

  • action:用来规定当提交表单时,向何处发送表单数据。一般来说 action 属性的值应该是后端提供的一个 URL 地址,这个 URL 地址专门负责接收表单提交过来的数据。当 <form> 表单在未指定 action 属性值的情况下,action 的默认值为当前页面的 URL 地址。
  • target:用来规定在何处打开 action 属性指定的 URL 。target 的默认值是 _self,表示在相同的框架中打开,当提交表单后,当前标签会立即跳转到 action 属性指定的 URL 地址。
    image-20210918155829957
  • method:用来规定以何种方式把表单数据提交到服务器。可选值有两个,分别是 getpostmethod 的默认值为 get,表示以 URL 查询参数形式把表单数据提交到 action 属性指定的 URL 地址。get 方式适合用来提交少量的、简单的数据。post 方式适合用来提交大量的、复杂的、或包含文件上传的数据。实际开发中,<form> 表单的 post 提交方式用的最多,很少用 get。例如登录、注册、添加数据等表单操作,都需要使用 post 方式来提交表单。
  • enctype:用来规定在发送表单数据之前如何对数据进行编码。可选值有三个,enctype 的默认值为 application/x-www-form-urlencoded,表示在发送前编码所有的字符。在涉及到文件上传的操作时,必须将 enctype 的值设置为 multipart/form-data。如果表单的提交不涉及到文件上传操作,则不设置直接使用默认值即可。image-20210918155938355

通过点击表单域中提交按钮,触发表单提交的操作,从而使页面跳转到 action URL 的行为,叫做表单的同步提交。

同步提交后,整个页面会发生跳转,跳转到 action 属性中所指向的 URL 地址,页面之前的状态和数据会丢失,用户体验很差。

实际开发中往往表单只负责采集数据,再使用 Ajax 请求将表单中数据提交到服务器,这也叫做表单的异步提交。

使用 Ajax 提交表单数据有以下几个步骤:

  1. 监听表单提交事件,在 jQuery 中,可以使用如下两种方式,监听到表单的提交事件:

    1
    2
    3
    4
    5
    6
    7
    $('#form1').submit(function(e) {
    alert('监听到了表单的提交事件')
    })

    $('#form1').on('submit', function(e) {
    alert('监听到了表单的提交事件')
    })
  2. 阻止表单默认提交行为,当监听到表单的提交事件以后,可以调用事件对象的 event.preventDefault() 函数,来阻止表单的提交和页面的跳转:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $('#form1').submit(function(e) {
    // 阻止表单的提交和页面的跳转
    e.preventDefault()
    })

    $('#form1').on('submit', function(e) {
    // 阻止表单的提交和页面的跳转
    e.preventDefault()
    })
  3. 获取表单中的数据,通过 DOM 操作获取表单域 <input> 元素的值。在 jQuery 中使用 serialize() 函数,可以一次性获取到表单中的所有的数据,简化表单中数据的获取操作。
    注意:使用 serialize() 函数快速获取表单数据时,必须为每个表单元素添加 name 属性!

    1
    2
    3
    4
    5
    6
    7
    8
    <form id="form1">
    <input type="text" name="username" value="zhangsan" />
    <input type="password" name="password" value="123456" />
    <button type="submit">提交</button>
    </form>
    <script>
    console.log($('#form1').serialize()); // username=zhangsan&password=123456
    </script>

art-template 模板引擎

概述

以往我们要通过字符串拼接或者 ES6 中的模板字符串将数据嵌套到 HTML 结构字符串中,然后根据得到的新字符串创建新的元素节点插入到 DOM 中,来动态添加新元素。

如果元素结构比较复杂,则拼接字符串的时候需要格外注意引号之间的嵌套。且一旦需求发生变化,修改起来也非常麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div>爱好:
<ul id="hobby">
<li>爱好1</li>
<li>爱好2</li>
</ul>
</div>
<script>
var data = {
hobby: ['吃饭', '睡觉', '打豆豆']
}
var rows = [];
// 循环拼接字符串
$.each(data.hobby, function (i, item) {
rows.push('<li>' + item + '</li>');
})
// 将字符串数组转换为一个长的字符串让后渲染到页面
$('#hobby').html(rows.join('')); // $('#hobby').empty().append(rows.join(''));
</script>

而模板引擎可以根据程序员指定的模板结构和数据,自动生成一个完整的 HTML 页面(片段)。使用模板引擎减少了字符串的拼接操作,使代码结构更清晰,更易于阅读与维护。

image-20210919155101947

art-template 是一个简约、超快的模板引擎。中文官网首页为 http://aui.github.io/art-template/zh-cn/index.html

使用步骤

  1. 导入 art-template:从官网template-web.js 下载到本地或者直接引入 CDN 上的脚本文件。导入脚本后就可以在全局中使用 template() 函数。

    1
    <script src="https://unpkg.com/art-template@4.13.2/lib/template-web.js"></script>
  2. 定义模板:在 <script> 标签中,定义模板的 HTML 结构,HTML 结构里面可以根据 art-template 的语法嵌入各种占位符。 <script> 标签 type 属性的默认值为 text/javascript,所以一定要显示指定 type 属性的值为 text/html,否则其中的 HTML 片段会被当成 JavaScript 代码执行。

    1
    2
    3
    4
    5
    6
    7
    <script type="text/html" id="tpl-hobby">
    <ul id="hobby">
    <li>{{hobby[0]}}</li>
    <li>{{hobby[1]}}</li>
    <li>{{hobby[2]}}</li>
    </ul>
    </script>
  3. 准备要渲染的数据。

  4. 调用 template(templateID, data) 函数生成 HTML 结构字符串,templateID 是模板的 ID 属性值,data 是需要渲染的数据对象,返回经过模板引擎处理后的 HTML 结构字符串。

  5. 通过 DOM 操作将 HTML 结构字符串渲染到页面上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1.导入 art-template -->
<script src="./template-web.js"></script>
<script src="./jquery-3.6.0.js"></script>
</head>
<body>
<div>爱好:
</div>
<!-- 2.定义模板 -->
<script type="text/html" id="tpl-hobby">
<ul id="hobby">
<li>{{hobby[0]}}</li>
<li>{{hobby[1]}}</li>
<li>{{hobby[2]}}</li>
</ul>
</script>
<script>
// 3.准备数据
var data = {
hobby: ['吃饭', '睡觉', '打豆豆']
};
// 4.调用 `template(templateID, data)` 函数生成 HTML 结构字符串
var htmlStr = template("tpl-hobby", data);
// 5.通过 DOM 操作将 HTML 结构字符串渲染到页面上。
$('div').append(htmlStr);
</script>
</body>
</html>

标准语法

在模板的两个大括号内可以进行变量输出,或循环数组等基基础操作,这种 {{ }} 语法在 art-template 中被称为标准语法。

  • 基本输出:变量的输出、对象属性的输出、三元表达式输出、逻辑或输出、加减乘除等表达式输出。

    1
    2
    3
    4
    5
    6
    {{value}}
    {{obj.key}}
    {{obj['key']}}
    {{a ? b : c}}
    {{a || b}}
    {{a + b}}
  • 原文输出:如果要输出的变量值中,包含了 HTML 标签结构,则需要使用原文输出语法,才能保证 HTML 标签被正常渲染。

    1
    {{@ value }}
  • 条件输出:使用 if … else if … /if 对变量的值进行判断,按需输出。

    1
    2
    3
    {{if conditionExpression}} 按需输出的内容 {{/if}}

    {{if exp1}} 按需输出的内容 {{else if exp2}} 按需输出的内容 {{/if}}
  • 循环输出:通过 each 语法循环数组,当前循环的索引使用 $index 进行访问,当前的循环项使用 $value 进行访问。

    1
    2
    3
    4
    {{each arr}}
    {{$index}}
    {{$value}}
    {{/each}}

    在前面使用步骤的举例的模板就可以使用 each 循环语法:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/html" id="tpl-hobby">
    <ul id="hobby">
    {{each hobby}}
    <li>{{$value}}</li>
    {{/each}}
    </ul>
    </script>
  • 过滤器:过滤器语法使用管道操作符将变量和多个过滤器连接起来,它的上一个输出作为下一个输入,最后一个函数的返回值才是最终的结果。

    1
    {{value | filterName1 | filterName2}}

    过滤器本质就是一个处理函数,将原始输入经过一系列处理后再输出。只有使用如下语法定义的函数对象才能在模板中被使用。

    1
    2
    3
    4
    5
    template.defaults.imports.filterName1 = function(value){
    /* 将原始输入经过一系列处理 */

    /*return处理的结果*/
    }

    例如定义一个格式化时间的过滤器 dateFormat 如下:

    1
    2
    3
    4
    5
    6
    7
    template.defaults.imports.dateFormat = function(date) {
    var y = date.getFullYear()
    var m = date.getMonth() + 1
    var d = date.getDate()

    return y + '-' + m + '-' + d // 注意,过滤器最后一定要 return 一个值
    }

    在模板中就可以使用这个格式化时间的过滤器:

    1
    2
    3
    <script type="text/html" id="tpl-demo">
    <div>注册时间:{{regTime | dateFormat}}</div>
    </script>

模板引擎实现原理

模板引擎的主要功能就是用正则表达式和字符串替换操作来实现的。

正则表达式对象的 exec() 函数用于检索传入的字符串中和该正则表达式的匹配情况。如果匹配成功,则返回一个数组(但是包含额外的属性 indexinput),里面有匹配结果的相关信息,否则返回 null

1
2
3
4
var str = 'hello'
var pattern = /o/
// ["o", index: 4, input: "hello", groups: undefined]
console.log(pattern.exec(str))

正则表达式中小括号包起来的内容表示一个分组,分组会单独提取成为匹配结果伪数组中一个元素。

伪数组中第一个元素为该正则表达式所匹配到的完整字符串,后面的元素为小括号分组匹配到的子字符串。

1
2
3
4
5
6
var str = '<div>姓名:{{name}}</div>'
var pattern = /{{([a-zA-Z]+)}}/
var patternResult = pattern.exec(str)
console.log(patternResult) // ['{{name}}', 'name', index: 7, input: input: '<div>姓名:{{namea}}</div>',groups: undefined]
str = str.replace(patternResult[0], patternResult[1]); // 使用 `replace()` 进行字符串替换
console.log(str); // <div>姓名:name</div>

使用循环匹配替换所有的占位符:

1
2
3
4
5
6
7
8
var str = '<div>{{name}}今年{{ age }}岁了</div>';
var pattern = /{{\s*([a-zA-Z]+)\s*}}/; // 使用 \s 修剪两端的空格
var patternResult = null;
// 所有都匹配结束,patternResult 为 null 结束循环
while(patternResult = pattern.exec(str)) {
str = str.replace(patternResult[0], patternResult[1]);
}
console.log(str) // <div>name今年age岁了</div>

replace() 函数的第二个参数换成真正想要替换的对象中的属性值:

1
2
3
4
5
6
7
8
var data = { name: '张三', age: 20 };
var str = '<div>{{name}}今年{{ age }}岁了</div>';
var pattern = /{{\s*([a-zA-Z]+)\s*}}/;
var patternResult = null;
while ((patternResult = pattern.exec(str))) {
str = str.replace(patternResult[0], data[patternResult[1]]);
}
console.log(str);

实现简易的模板引擎:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>

</head>

<body>
<div id="user-box"></div>
<!-- 定义模板结构 -->
<script type="text/html" id="tpl-user">
<div>姓名:{{name}}</div>
<div>年龄:{{ age }}</div>
<div>性别:{{  gender}}</div>
<div>住址:{{address  }}</div>
</script>
<script>
// 封装的 template 函数
function template(id, data) {
var str = document.getElementById(id).innerHTML
var pattern = /{{\s*([a-zA-Z]+)\s*}}/
var pattResult = null
while ((pattResult = pattern.exec(str))) {
str = str.replace(pattResult[0], data[pattResult[1]])
}
return str
}
// 定义数据
var data = { name: 'zs', age: 28, gender: '男', address: '北京顺义马坡' }
// 调用模板函数
var htmlStr = template('tpl-user', data)
// 渲染 HTM L结构
document.getElementById('user-box').innerHTML = htmlStr
</script>
</body>

</html>

Ajax 加强

XMLHttpRequest 对象

XMLHttpRequest(简称 xhr)是浏览器预置的 JavaScript 成员,通过它可以请求服务器上的数据资源。jQuery 中的 Ajax 相关的函数,就是基于 xhr 对象封装出来的。

使用 xhr 发起 GET 请求的步骤:

  1. 创建 XMLHttpRequest 对象;
  2. 调用 xhr 对象的open() 函数;
  3. 调用 xhr 对象的 send() 函数;
  4. 监听 xhr 对象的onreadystatechange 事件,请求完成时对响应对处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 创建 XHR 对象
var xhr = new XMLHttpRequest()
// 2. 调用 open 函数,指定 请求方式 与 URL地址
xhr.open('GET''http://www.liulongbin.top:3006/api/getbooks')
// 3. 调用 send 函数,发起 Ajax 请求
xhr.send()
// 4. 监听 onreadystatechange 事件
xhr.onreadystatechange = function({
// 4.1 监听 xhr 对象的请求状态 readyState ;与服务器响应的状态 status
if (xhr.readyState === 4 && xhr.status === 200) {
// 4.2 打印服务器响应回来的数据
console.log(xhr.responseText)
}
}

使用 xhr 发起 POST请求的步骤:

  1. 创建 XMLHttpRequest 对象;
  2. 调用 xhr 对象的open() 函数;
  3. 设置 xhr 对象的 Content-Type 属性,application/x-www-form-urlencoded
  4. 调用 xhr 对象的 send() 函数,同时指定要发送的数据;
  5. 监听 xhr 对象的onreadystatechange 事件,请求完成时对响应对处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 创建 xhr 对象
var xhr = new XMLHttpRequest()
// 2. 调用 open()
xhr.open('POST''http://www.liulongbin.top:3006/api/addbook')
// 3. 设置 Content-Type 属性(固定写法)
xhr.setRequestHeader('Content-Type''application/x-www-form-urlencoded')
// 4. 调用 send(),同时将数据以查询字符串的形式,提交给服务器
xhr.send('bookname=水浒传&author=施耐庵&publisher=天津图书出版社')
// 5. 监听 onreadystatechange 事件
xhr.onreadystatechange = function({
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}

使用 xhr 发起 POST 请求的步骤和发起 GET 请求的步骤大致一样,区别是在调用 open() 函数时请求方式改为 ‘POST’,在调用 send() 函数之前需要设置 Content-Type 属性。

XMLHttpRequest 对象的 readyState 属性,用来表示当前 Ajax 请求所处的状态。每个 Ajax 请求必然处于以下状态中的一个:

image-20210919225529979

查询字符串

查询字符串(Query String)是指在 URL 地址末尾加上的用于向服务器发送参数信息的字符串,也叫 URL 参数。
查询字符串的格式:将英文的 ? 放在 URL 中请求路径的末尾,传递的参数信息用 = 连接成键值对,如果有多个参数的话,使用 & 符号进行分隔。以这个形式,可以将想要发送给服务器的数据添加到 URL 中。

下面 URL 最后面的 ?wd=JavaScript&ie=UTF-8 就是查询字符串。注意:查询字符串不包括 # 后面的锚点信息。

1
https://www.baidu.com/s?wd=JavaScript&ie=UTF-8#11

使用 xhr 对象发起带参数的 GET 请求时,只需在调用 open() 函数时在 URL 地址后面按照格式拼接上参数即可。

jQuery 中使用 $.ajax()$.get() 发起带参数的 GET 请求,可以使用对象来传递参数信息。但本质上,都是直接将参数以查询字符串的形式,追加到 URL 地址的后面,发送到服务器的。

1
2
3
4
5
6
7
$.get('url', {name: 'zs', age: 20}, function() {})
// 等价于
$.get('url?name=zs&age=20', function() {})

$.ajax({ method: 'GET', url: 'url', data: {name: 'zs', age: 20}, success: function() {} })
// 等价于
$.ajax({ method: 'GET', url: 'url?name=zs&age=20', success: function() {} })

JSON 数据交换格式

数据交换格式,就是服务器端(后端)与客户端(前端)之间进行数据传输与交换的格式。

比较流行的两种数据交换格式分别是 XML 和 JSON。其中 XML 因为格式臃肿,传输效率低和解析不方便,已经很少在数据传输上被使用了,更多用在服务端的配置文件上。而 JSON 是在 2001 年开始被推广和使用的数据格式,到现今为止,JSON 已经成为了主流的数据交换格式。因此重点要学习的数据交换格式就是 JSON。

JSON 是一种轻量级的文本数据交换格式,在作用上类似于 XML,专门用于存储和传输数据,但是 JSON 比 XML 更小、更快、更易解析。

概念:JSON 的英文全称是 JavaScript Object Notation,即 JavaScript 对象表示法。准确来说是 Javascript 对象和数组的字符串表示法,它使用文本(即一个长的字符串)表示一个 JavaScript 对象或数组的信息,JSON 的本是一个字符串。

JSON 中只包含对象和数组两种结构,JSON 的最外层必须是对象或数组格式。通过这两种结构的相互嵌套,可以表示各种复杂的数据结构。

  • 对象结构:对象结构在 JSON 中表示为花括号括起来的形如 { key: value, key: value, … } 键值对结构。其中,key 必须是使用英文的双引号包裹的字符串,value 的数据类型只能是数字、字符串、布尔值、null、数组、对象这 6 种类型。

    1
    2
    3
    4
    5
    6
    7
    {
    "name": "zs",
    "age": 20,
    "gender": "男",
    "address": null,
    "hobby": ["吃饭", "睡觉", "打豆豆"]
    }
  • 数组结构:数组结构在 JSON 中表示为方括号括起来的形如 [ “javascript”, 30, true … ] 有序列表结构 。数组中数据的类型只能是数字、字符串、布尔值、null、数组、对象这 6 种类型。

    1
    2
    3
    4
    5
    [ "java", "python", "php" ]
    [ 100, 200, 300.5 ]
    [ true, false, null ]
    [ { "name": "zs", "age": 20}, { "name": "ls", "age": 30} ]
    [ [ "苹果", "榴莲", "椰子" ], [ 4, 50, 5 ] ]

JSON 语法和 JavaScript 中的对象声明和数组声明很像,但不是严格意义上的 JavaScript 子集。典型区别如下:

  1. 字符串类型的值必须使用双引号包裹,不允许使用单引号表示字符串。
  2. JSON 对象结构中 key 必须是字符串,即被双引号包裹,而 JavaScript 中对象属性名可以使用单引号,甚至省略引号。
  3. JSON 对象结构中 value 不能为 undefined,表示值为空应该使用 null。
  4. JSON 对象结构中 value 不能为函数对象。
  5. JSON 规范中使用逗号分割相邻的元素,但是最后一个元素的末尾不能有多余的逗号。
  6. JSON 规范不允许加注释,防止过多的注释,影响了文件本身的数据载体的目的,因为 JSON 本意就是让程序来处理的,而不是让人类去读的。JSON5(JSON for Humans) 在 JSON 规范的基础上做了一些拓展,允许使用单行和多行注释。
1
2
3
4
5
6
7
8
{
"gender": '男', // 违反上面第 1 条
name: "zs", // 违反上面第 2 条
'age': 20, // 违反上面第 2 条
"address": undefined, // 违反上面第 3 条
say: function() {}, // 违反上面第 4 条
"hobby": ["吃饭", "睡觉", '打豆豆'], // 违反上面第 5 条
}

JSON 字符串可以和 JavaScript 对象(包括数组)相互转换。

把字符串转换为数据对象的过程,叫做反序列化,使用 JSON.parse() 方法从 JSON 字符串转换为 JavaScript 对象:

1
2
var obj = JSON.parse('{"a": "Hello", "b": "World"}')
console.log(obj); // { a: 'Hello', b: 'World' }

把数据对象转换为字符串的过程,叫做序列化,使用 JSON.stringify() 方法从 JavaScript 对象转换为 JSON 字符串:

1
2
var json = JSON.stringify({ a: 'Hello', b: 'World' })
console.log(json); // {"a":"Hello","b":"World"}

XMLHttpRequest Level2 的新特性

在很早之前 XMLHttpRequest 对象只支持文本数据的传输,无法用来读取和上传文件,并且传送和接收数据时,没有进度信息,只能监听请求有没有完成。
XMLHttpRequest Level2 赋予了 XMLHttpRequest 对象新的特性:

  1. 可以设置 Ajax 请求的时限:Ajax 操作时间受网络环境影响,而且无法预知要花多少时间。如果网速很慢,用户可能要等很久。新版本的 XMLHttpRequest 对象,增加了 timeout 属性,可以设置 HTTP 请求的时限,过了这个时限,就自动停止 Ajax 请求。与之配套的还有一个 timeout 事件,用来指定超时发生时的回调函数:

    1
    2
    3
    4
    xhr.timeout = 3000;
    xhr.ontimeout = function(event){
    alert('请求超时!')
    }
  2. 新增了一个 FormData 对象,方便表单处理。可以根据表单元素创建一个 FormData 对象,会自动将表单中的数据填充到 FormData 对象中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 获取表单元素
    var form = document.querySelector('#form1')
    // 监听表单元素的 submit 事件
    form.addEventListener('submit'function(e{
    e.preventDefault()
    // 根据 form 表单创建 FormData 对象,会自动将表单数据填充到 FormData 对象中
    var fd = new FormData(form)
    var xhr = new XMLHttpRequest()
    xhr.open('POST''http://www.liulongbin.top:3006/api/formdata')
    xhr.send(fd)
    xhr.onreadystatechange = function({}
    })

    也可以使用该对象来模拟表单的提交操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 1. 新建 FormData 对象
    var fd = new FormData()
    // 2. 为 FormData 添加表单项
    fd.append('uname''zs')
    fd.append('upwd''123456')
    // 3. 创建 XHR 对象
    var xhr = new XMLHttpRequest()
    // 4. 指定请求类型与URL地址
    xhr.open('POST''http://www.liulongbin.top:3006/api/formdata')
    // 5. 直接提交 FormData 对象,这与提交网页表单的效果,完全一样
    xhr.send(fd)

    将文件对象追加到 FormData 对象中,就可以上传指定文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var files = document.querySelector('#file1').files;
    if (files.length <= 0) {
    return alert('请选择要上传的文件!')
    }
    // 1. 创建 FormData 对象
    var fd = new FormData()
    // 2. 向 FormData 中追加文件
    fd.append('avatar', files[0])
    // 3 创建 xhr 对象
    var xhr = new XMLHttpRequest()
    // 4. 调用 open 函数,指定请求类型与URL地址。其中,请求类型必须为 POST
    xhr.open('POST''http://www.liulongbin.top:3006/api/upload/avatar')
    // 5. 发起请求
    xhr.send(fd)

    使用 jQuery 配合 FormData 对象发起上传文件的请求时,不能对数据进行编码,必须指定 contentTypeprocessData 的属性值为 false

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 1. 创建 FormData 对象
    var fd = new FormData()
    // 2. 向 FormData 中追加文件
    fd.append('avatar', files[0])

    $.ajax({
    method: 'POST',
    url: 'http://www.liulongbin.top:3006/api/upload/avatar',
    data: fd,
    // 不修改 Content-Type 属性,使用 FormData 默认的 Content-Type 值
    contentType: false,
    // 不对 FormData 中的数据进行 url 编码,而是将 FormData 数据原样发送到服务器
    processData: false,
    success: function(res{
    console.log(res)
    }
    })
  3. 可以通过监听 xhr.upload.onprogress 事件,可以获取文件上传的进度信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 创建 XHR 对象
    var xhr = new XMLHttpRequest()
    // 监听 xhr.upload 的 onprogress 事件
    xhr.upload.onprogress = function(e{
    // e.lengthComputable 是一个布尔值,表示当前上传的资源是否具有可计算的长度
    if (e.lengthComputable) {
    // e.loaded 已传输的字节,e.total 需传输的总字节,相除即得传输进度百分比
    var percentComplete = Math.ceil((e.loaded / e.total) * 100)
    }
    }

Axios

Axios 是专注于网络数据请求的库。相比于原生的 XMLHttpRequest 对象,Axios 简单易用。相比于 jQuery,Axios 更加轻量化,只专注于网络数据请求。

导入 Axios:将 axios.min.js 下载到本地引入或者直接引入 CDN 上的脚本文件。导入脚本后就可以在全局中使用 axios 对象。

1
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

Axios 来调用接口的响应结果会在接口返回的原始数据基础上额外包装了一层,响应结果对象有 datastatusstatusTextheadersconfigrequest 这 6 个属性,其中 data 属性才是接口返回的原始响应数据。

使用 axios 对象发起 get 请求的语法:

1
2
// 查询参数可以直接拼接在 URL 后面,或者通过参数对象传递 
axios.get('url', { params: { /*查询参数*/ } }).then(callback);

示例:

1
2
3
4
5
6
7
8
9
10
// 请求的 URL 地址
var url = 'http://www.liulongbin.top:3006/api/get'
// 请求的参数对象
var paramsObj = { name'zs'age20 }
// 调用 axios.get() 发起 GET 请求
axios.get(url, { params: paramsObj }).then(function(res{
// res.data 是服务器返回的数据
var result = res.data
console.log(res)
})

使用 axios 对象发起 post 请求的语法:

1
axios.post('url', { /*参数*/ }).then(callback)

示例:

1
2
3
4
5
6
7
8
9
10
// 请求的 URL 地址
var url = 'http://www.liulongbin.top:3006/api/post'
// 要提交到服务器的数据
var dataObj = { location'北京'address'顺义' }
// 调用 axios.post() 发起 POST 请求
axios.post(url, dataObj).then(function(res{
// res.data 是服务器返回的数据
var result = res.data
console.log(result)
})

直接使用 axios() 方法发起请求,类似于 jQuery 中 $.ajax() 的传入一个对象设置请求的相关信息:

1
2
3
4
5
6
axios({
method: '请求类型',
url: '请求的URL地址',
data: { /* POST数据 */ },
params: { /* GET参数 */ }
}) .then(callback)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 使用 axios() 方法发起 get 请求   
axios({
method: 'GET',
url: 'http://www.liulongbin.top:3006/api/get',
params: { // GET 参数要通过 params 属性提供
name: 'zs',
age: 20
}
}).then(function(res{
console.log(res.data)
})

// 使用 axios() 方法发起 post 请求
axios({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/post',
data: { // POST 数据要通过 data 属性提供
bookname: '程序员的自我修养',
price: 666
}
}).then(function(res{
console.log(res.data)
})

Axios 的请求执行结果是一个 Promise 对象,通过 .then 的方法还可以使用 ES6 的 async/await 关键字直接得到响应结果对象,进一步的,配合解构赋值和冒号重命名得到接口的原始响应数据。

1
2
3
4
5
6
7
8
const {data: res} = async function() {
await axios({
method: '请求类型',
url: '请求的URL地址',
data: { /* POST数据 */ },
params: { /* GET参数 */ }
})
}

关于 Axios 库的更多用法参考官方文档

同源策略和跨域

同源:如果两个页面的协议,域名和端口都相同,则两个页面具有相同的源。例如,下表给出了相对于 http://www.test.com/index.html 页面的同源检测:

image-20210920235252171

同源策略(Same origin policy)是浏览器提供的一个安全功能。同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

通俗的理解:浏览器中 A 网站的 JavaScript 脚本,不允许和非同源的网站 B 之间,进行资源的交互,例如:

  • 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
  • 无法接触非同源网页的 DOM
  • 无法向非同源地址发送 Ajax 请求

跨域:非同源的 URL 之间进行资源的交互访问。

浏览器允许发起跨域网络请求,但是,跨域请求回来的数据,会被浏览器的同源策略给拦截,无法被页面获取到。

image-20210921001055021

JSONP

实现跨域数据请求,最主要的两种解决方案,分别是 JSONP 和 CORS。

  • JSONP:出现的早,兼容性好(兼容低版本IE)。是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。缺点是只支持 GET 请求,不支持 POST 请求。
  • CORS:出现的较晚,它是 W3C 标准,属于跨域 Ajax 请求的根本解决方案。支持 GET 和 POST 请求。缺点是不兼容某些低版本的浏览器。

JSONP(JSON with Padding) 是 JSON 的一种使用模式,可用于解决主流浏览器的跨域数据访问的问题。

由于浏览器同源策略的限制,网页中无法通过 Ajax 向非同源的接口请求数据。但是 <script> 标签不会受浏览器同源策略的影响,即可以请求接收非同源站点的 JavaScript 脚本。

JSONP 请求的原理:通过 <script> 标签的 src 属性,请求跨域的数据接口,接口返回一个函数调用的语句,该函数处理接口响应回来的数据(通常是 JSON)。

使用 JSONP 发起跨域请求的步骤。

  1. 定义一个成功回调函数,JSONP 请求成功后会自动动调用该函数对响应数据做处理:

    1
    2
    3
    4
    5
    6
    <script>
    function success(data{
    console.log('获取到了data数据:')
    console.log(data)
    }
    </script>
  2. 通过 <script> 标签,向接口请求数据,src 属性值即为 GET请求的 URL 地址,查询字符串中除了 GET 请求的相关参数信息,还要指定成功回调函数的名称。

    1
    <script src="https://suggest.taobao.com/sug?q=apple&callback=success"></script>
  3. 服务端返回脚本并执行,脚本中就是一行 JavaScript 代码——将服务器响应结果——JSON 对象直接作为实参传入成功回调函数中并调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    success({
    "result": [
    [
    "applewatch表带",
    "9306.792483204854"
    ],
    [
    "applewatch",
    "9106.716268073735"
    ],
    [
    "applepencil",
    "4213.880638420522"
    ]
    ],
    "shop": "apple",
    "tmall": "apple"
    })

由于 JSONP 是通过 <script> 标签的 src 属性,来实现跨域数据获取的,而 <script> 标签只能发出 GET 请求,所以,JSONP 只支持 GET 数据请求,不支持 POST 请求。

JSONP 请求的 type 类型为 script 类型,本质就是请求了一个脚本文件。

json是理想的数据交换格式,但没办法跨域直接获取,于是就将json包裹(padding)在一个合法的js语句中作为js文件传过去。这就是json和jsonp的区别,json是想要的东西,jsonp是达到这个目的而普遍采用的一种方法,当然最终获得和处理的还是json。所以说json是目的,jsonp只是手段。json总会用到,而jsonp只有在跨域获取数据才会用到。
————————————————
版权声明:本文为CSDN博主「一只大海」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44392418/article/details/88782809

JSONP 没有用到 XMLHttpRequest 这个对象,JSONP 和 Ajax 之间没有任何关系,不能把 JSONP 请求数据的方式叫做 Ajax 请求。

jQuery 提供的 $.ajax() 函数,除了可以发起真正的 Ajax 数据请求之外,还能够发起 JSONP 数据请求,例如:

1
2
3
4
5
6
7
8
$.ajax({
url: 'https://suggest.taobao.com/sug?q=apple',
// 如果要使用 $.ajax() 发起 JSONP 请求,必须指定 datatype 为 jsonp
dataType: 'jsonp',
success: function(res{
console.log(res)
}
})

使用 $.ajax() 发起 JSONP 请求时,配置对象的 datatype 属性值必须为 'jsonp',这样请求就会默认携带一个 callback=jQueryxxx 的参数,会自动拼接在 GET 请求 URL 的查询字符串上,其中 jQueryxxx 是随机生成的成功回调函数名称,和配置对象中的 success 属性指向同一个函数对象。

有时服务端接口约定的成功回调函数参数名不一定是 callback,也可能是 cbcbk 等。可以通过修改配置对象 jsonpjsonpCallback 两个属性的来自定义 JSONP 请求中回调函数的参数名和参数值:

1
2
3
4
5
6
7
8
9
10
11
12
13
jQueryxxx$.ajax({
url: 'https://suggest.taobao.com/sug?q=apple',
dataType: 'jsonp',
// 发送到服务端的参数名称,默认值为 callback
jsonp: 'cb',
// 自定义的回调函数名称,默认值为 jQueryxxx 格式
jsonpCallback: 'abc',
success: function(res{
console.log(res)
}
})
// 最终 JSONP 发出的 GET 请求的 URL 地址查询字符串为:?q=apple&cb=abc
// 默认为 ?q=apple&callback=success

jQuery 中的 JSONP 请求也是通过 <script> 标签实现的,但是jQuery 采用的是动态创建和移除 <script> 标签的方式:在发起 JSONP 请求的时候,动态向 <header> 内部尾部追加一个 <script> 标签,JSONP 请求成功以后,动态从 <header> 中移除刚才追加进去的 <script> 标签。

跨域的接口请求是否要使用 JSONP 要根据服务端的设定而定,如果在服务器端已经处理好了该接口的跨域问题,那么直接通过普通的 Ajax 请求就可以跨域访问了;如果服务端想让前端来使用 JSONP 的方式来跨域访问,在服务端要提前配置好处理 JSONP 请求的函数,该函数根据 URL 中是否有 callback 参数判断是 JSONP 请求,如果是则将响应数据包裹在调用语句中作为脚本文件返回。当然服务端也有可能对 JSONP 请求不做特殊处理直接返回响应,这样返回的数据会被浏览器给拦截,而无法实现跨域访问。

节流和防抖

节流(throttle)策略当指事件被触发后,立即执行回调,但在一段时间内该时间不可以被再次触发。可以类比 moba 游戏中英雄释放技能有 CD,一段时间只能释放一次。

image-20210922160228397

防抖(debounce)策略是当事件被触发后,延迟一段时间后再执行回调,如果在这段时间内事件又被触发,则重新开始计时。可以类比 moba 游戏中英雄释放回城技能有(前摇)等待时间,必须等待计时结束技能才生效,如果前摇被打断只能重新开始计时。

image-20210922160214740

节流的应用场景

  1. 鼠标连续点击轮播图切换按钮,只在单位时间内只触发一次或者轮播图动画完成才能切换下一张。
  2. 鼠标跟随事件不必时刻监听鼠标位置,可以间隔很短的时间监听。
  3. 懒加载时要监听计算滚动条的位置,但不必每次页面滚动都触发事件,滚动一定距离再触发事件。

防抖的应用场景:

  1. 在搜索框输入关键词,只有在停止输入一段时间后才会向服务器发送查询搜索建议的 Ajax 请求,这样可以减少请求次数,节约请求资源。
  2. 当调整浏览器窗口大小时,resize 事件过于频繁,造成计算过多,此时可以使用防抖,调整完毕后再触发 resize 操作。
  3. 在文本编辑器实时保存,当无任何更改操作一秒后再进行保存。

节流和防抖的对比:当事件短时间内被频繁触发时,节流和防抖都能减少事件处理中某些步骤(例如Ajax 请求、DOM 操作)的触发频率,从而节约 CPU 、网络资源,提升性能。

节流是触发事件就立刻执行操作,但在该周期内后面的触发都会被忽略。防抖是某个周期内只有最后有一次触发事件会真正执行操作,这个周期内的前面的触发都会被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<script>
var btn1 = $('#button1');
var btn2 = $('#button2');

// 定义节流阀开关
var timer1 = null;
btn1.click(function () {
// 每次执行操作前先判断节流阀开关状态,如果节流阀为空,表示可以执行操作
if (!timer1) {
console.log("点击了节流按钮");
$(this).attr("disabled", true);
timer1 = setTimeout(() => {
// 将节流阀重置为空,下次可以执行操作
timer1 = null;
$(this).attr("disabled", false);

}, 1000);
}
})

// 定义防抖开关
var timer2 = null;
btn2.click(function () {
// 触发事件时先清空之前的计时器重新计时,一定要用 clearTimeout() 清空计时器而不是赋值 null
clearTimeout(timer2);
$(this).attr("disabled", true);
// 如果一段时间内没有再次触发事件即可执行操作
timer2 = setTimeout(() => {
console.log("点击了防抖按钮");
$(this).attr("disabled", false);
}, 1000);
})
</script>

HTTP 协议

HTTP 协议即超文本传送协议 (HyperText Transfer Protocol) ,它规定了客户端与服务器之间进行网页内容传输时,所必须遵守的传输格式。

HTTP 协议采用了 请求/响应 的交互模型。客户端要以 HTTP 协议要求的格式把数据提交到服务器。服务器要以 HTTP 协议要求的格式把内容响应给客户端。

由于 HTTP 协议属于客户端浏览器和服务器之间的通信协议。因此,客户端发起的请求叫做 HTTP 请求,客户端发送到服务器的消息,叫做 HTTP 请求消息;服务器返回的响应叫做 HTTP 响应,服务器响应给客户端的消息,叫做HTTP 响应消息。

HTTP 请求消息

HTTP 请求消息又叫做 HTTP 请求报文。HTTP 请求消息由请求行(request line)、请求头部( header)、空行和请求体 4 个部分组成。

image-20210922201015017

  • 请求行:由请求方式、URL 和 HTTP 协议版本 3 个部分组成,他们之间使用空格隔开。
    image-20210922201325974
    image-20210922201331151

  • 请求头部:用来描述客户端的基本信息,从而把客户端相关的信息告知服务器。请求头部由多行键值对组成,每行的键和值之间用英文的冒号分隔。
    image-20210922201405689
    image-20210922201509234
    常见的请求头字段说明:
    image-20210922201439011

  • 空行:用来分隔请求头部与请求体。最后一个请求头字段的后面是一个空行,通知服务器请求头部至此结束。
    image-20210922201619545

  • 请求体:存放着要通过 POST 方式提交到服务器的数据。
    注意:只有 POST 请求才有请求体,GET 请求没有请求体。

HTTP 响应消息

HTTP 响应消息又叫作响应报文。HTTP响应消息由状态行、响应头部、空行 和 响应体 4 个部分组成。

image-20210922202529814

  • 状态行:状态行由 HTTP 协议版本、状态码和状态码的描述文本 3 个部分组成,他们之间使用空格隔开。
    image-20210922202651567
    image-20210922202701352
  • 响应头部:用来描述服务器的基本信息。响应头部由多行键值对组成,每行的键和值之间用英文的冒号分隔。
    image-20210922202732801
    image-20210922202750165
  • 空行:用来分隔响应头部与响应体。在最后一个响应头部字段结束之后,会紧跟一个空行,用来通知客户端响应头部至此结束。
    image-20210922202934241
  • 响应体:存放的着服务器响应给客户端的资源内容。
    image-20210922203009235

HTTP 请求方法

HTTP 请求方法,属于 HTTP 协议中的一部分,请求方法的作用是:用来表明要对服务器上的资源执行的操作。最常用的请求方法是 GET 和 POST。
image-20210922203453243

HTTP 响应状态码

HTTP 响应状态码(HTTP Status Code),也属于 HTTP 协议的一部分,用来标识响应的状态。响应状态码会随着响应消息一起被发送至客户端浏览器,浏览器根据服务器返回的响应状态码,就能知道这次 HTTP 请求的结果是成功还是失败。

HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字用来对状态码进行细分。根据第一位数字可以分为 5 种类型:

image-20210922204507100

2** 成功相关的响应状态码,表示服务器已成功接收到请求并进行处理。常见的 2** 类型的状态码如下:

image-20210922204615484

3** 重定向相关的响应状态码,表示表示服务器要求客户端重定向,需要客户端进一步的操作以完成资源的请求。常见的 3** 类型的状态码如下:

image-20210922204648894

4** 客户端错误相关的响应状态码,表示客户端的请求有非法内容,从而导致这次请求失败。常见的 4** 类型的状态码如下:

image-20210922204719068

5** 服务端错误相关的响应状态码,表示服务器未能正常处理客户端的请求而出现意外错误。常见的 5** 类型的状态码如下:

image-20210922204752286

参考

  1. 2021黑马前端最新进阶班

  2. MDN:JSON

  3. JSON 规范