AJAX 的原理与应用
AJAX的全称: Asynchronous JavaScript And XML
大纲链接 §
[toc]
0. 服务器的基本概念与初识Ajax ⇧
- 客户端和服务器
- URL地址
- 网页打开的过程
- 服务器对外提供的资源包括图片以及数据
- Ajax概念和应用场景
- 使用 Axios 代替过时的jQuery发起Ajax请求数据
- 接口和接口文档
- 过时的form表单与模板引擎
- 案例
上网的目的:通过互联网的形式来获取和消费网络资源
0.1 客户端和服务器 ⇧
- 客户端和服务器基本概念:
- 上网过程中,负责存放和对外提供资源的计算机,叫服务器
- 上网过程中,负责获取和消费资源的计算机,叫客户端
- 客户端和服务器通信过程
0.2 URL地址 ⇧
- 统一资源定位符,同于标识互联网上每个资源的唯一存放位置(不可重复)
- 客户端只有通过URL地址,才能正确定位的存放位置,从而成功访问到对应的资源
URL地址一般有三部分组成
- 客户端与服务器的通信协议,
https
等,在://
之前
- 存有该资源的服务器名称,
www.xxx.com
- 资源在服务器上的具体存放路径
0.3 网页打开的过程 ⇧
- 即客户端和服务器通信过程,分为:请求->处理->响应
- 在浏览器的地址栏输入网站地址,回车,向服务器发起资源请求
- 服务器处理这次资源请求
- 服务器将这次请求的响应发送给客户端
浏览器Network面板简介
- 资源筛选页签
- XHR 表示数据请求对象
XMLHttpRequest
,有的浏览器显示为 Fetch/XHR
- Doc 表示文档资源,有的浏览器显示为
HTML
- Headers 页签
Request URL
Request Method
Status Code
Remote Address
Referrer Policy
Response
表示服务器的响应数据,格式为对应数据的格式
0.4 服务器对外提供的资源 ⇧
网页中的数据也是服务器对外提供的一种资源
- 网页中 HTML控制骨架、CSS控制样式、JS控制逻辑交互行为、数据则提供主要信息,骨架、样式和交互都为数据服务
- 常见的数据传输格式有
JSON
、XML
(前端不常用)
0.5 网页中如何请求数据 ⇧
数据也是服务器对外提供的一种资源,只要是资源,必然通过 请求 -> 响应 -> 的方式获取
- 如果要在网页中请求服务器上的数据资源,则需要用到
XMLHttpRequest
对象
XMLHttpRequest
对象简称xhr
,是浏览器提供给JS内建的浏览器对象,属于BOM
- 通过
new XMLHttpRequest()
方法构造实例对象,可以请求服务器上的数据资源
const xhr = new XMLHttpRequest()
0.6 资源的请求方式(请求动词) ⇧
客户端请求服务器时,请求的方式有很多种,最常见的两种方式分别为get
和post
请求
get请求
用于获取服务端资源
- 根据 URL 地址,从服务器获取 HTML、CSS、JS、图片和数据等文件资源
post请求
用于向服务器提交数据,即发送资源
- 例如:登录时向服务器提交的登录消息、注册时向服务器提交的注册信息、添加用户时向服务器提交的用户数据信息等给中数据提交操作
0.6 Ajax概念和应用场景 ⇧
- 在网页中利用 XMLHttpRequest 对象和服务器进行数据交互的方式就是 Ajax
- 页面可以无刷新的请求数据
- Ajax 能实现静态网页无法实现的和服务器之间数据交互功能
- 用户通过和网页或应用这一数据载体交互,实现和服务器进行数据交互
- 代替过时的 form 表单默认提交(页面强制刷新)和 jQuery 发起 Ajax 请求数据
Ajax的典型应用场景
- 用户名的检测:注册用户时,通过 Ajax 的形式,动态检测用户名是否被占用,名称查重
- 搜索提示:当输入关键字时,通过 Ajax 的形式,动态加载搜索提示列表
- 数据分页显示:点击页码时,通过 Ajax 的形式,根据页码值动态刷新该页页面数据
- 数据增删改查,实现数据的交互
在现代浏览器上实现一个Ajax请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
const p = () => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest() // 创建XMLHttpRequest对象
// 异步ajax,监听readystatechange事件,设置回调函数
xhr.onreadystatechange = () => { // 状态发生变化时,函数被回调
if (xhr.readyState === 4) { // 成功完成
// 判断响应状态码
if (xhr.status !== 200) return reject(xhr.status) // 失败,根据响应码判断失败原因:
return resolve(xhr.responseText) // 成功,通过responseText拿到响应的文本:
} else {
// HTTP请求还在继续...
}
}
// 发送请求:
xhr.open('GET', '/api/categories')
xhr.setRequestHeader('Content-Type', 'application/json') // 设置请求头
xhr.send() // 到这一步,请求才正式发出
})
}
|
0.7 代替过时的jQuery发起Ajax请求数据 ⇧
- 使用黑马的免费教学接口API:
http://www.liulongbin.top:3006/api
jQuery
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
// 不带参数的请求
$(() => {
$('button1').on('click', () => {
$.get('http://www.liulongbin.top:3006/api/getbooks', (res) => {
console.log(res)
})
})
})
// 带参数的请求 // http://www.liulongbin.top:3006/api/getbooks?id=1
$(() => {
$('button2').on('click', () => {
$.get('http://www.liulongbin.top:3006/api/getbooks', /* 第二个参数为参数对象 */{id: 1}, (res) => {
console.log(res)
})
})
})
// 提交数据的请求
// $.post 功能单一,仅发起post请求向服务器提交数据
// $post(url, [data /*可选 要提交的数据*/], [callback /* 数据提交成功的回调*/])
$(() => {
$('button3').on('click', () => {
$.post('http://www.liulongbin.top:3006/api/addbook',
/* 第二个参数为提交的数据对象 */{
bookname: '水浒传',
author: '施耐庵',
publisher: '上海图书出版社'
},
(res) => {
console.log(res)
})
})
})
/*
// 综合配置
$.ajax({
type: '', // 请求的方式
url: '', // 请求的 URL 地址
data: {}, // 此次请求要携带的数据
success: (res) => {}, // 请求成功后的回调函数
})
*/
// Get
$(() => {
$('button4').on('click', () => {
$.ajax({
type: 'GET',
url: 'http://www.liulongbin.top:3006/api/getbooks',
params: {id: 3},
success: (res) => {
console.log(res)
}
})
})
})
// Post
$(() => {
$('button4').on('click', () => {
$.ajax({
type: 'POST',
url: 'http://www.liulongbin.top:3006/api/addbook',
data: {
bookname: '三国演义',
author: '罗贯中',
publisher: '重庆图书出版社'
},
success: (res) => {
console.log(res)
}
})
})
})
|
Axios ⇧
- 易用、简洁且高效的http库
- 支持node端和浏览器端
- 基于 Promise 管理异步,是对原生XHR的封装,用Promise的实现版本,符合最新的ES规范
- 支持HTTP拦截器等高级配置,拦截请求和响应
- 取消请求
- 自动转换 JSON 数据,转换请求数据和响应数据
- 响应超时
- 同时请求
- 客户端支持防御 XSRF
安装
1
2
|
yarn add axios
# pnpm add axios
|
引入
1
|
import axios from 'axios';
|
基本用法
axios.get(url[, config])
- 代替 jQuery 的
$.get(url: string, [data: object /*请求参数*/], [callback: function /*成功回调*/])
axios.post(url[, data[, config]])
- 代替 jQuery 的
$.post(url: string, [data: object /*要提交的数据*/], [callback: function /*数据提交成功回调*/])
axios({...})
代替 jQuery 的 $.ajax()
- 为其他支持的请求方法提供的别名
axios.request(config)
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
- 在使用别名方法时, url、method、data 这些属性都不必在配置中指定
axios-get.js
,语法:axios.get(url[, config])
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
42
43
44
45
46
47
48
49
50
51
52
53
54
|
import axios from 'axios'
// 直接发起不带参数的请求
axios.get('/api/getbooks',
{
baseURL: 'http://www.liulongbin.top:3006/',
timeout: 1000
})
.then(
(res) => {
const {data, status} = res
console.log('直接发起不带参数的请求')
console.log('data', data)
console.log('status', status)
console.log('-----------------')
}
)
// 先配置,后发起请求
const instance = axios.create({
baseURL: 'http://www.liulongbin.top:3006/',
timeout: 1000
})
const instance2 = axios.create({
baseURL: 'http://www.liulongbin.top:3006/',
timeout: 1000,
params: {
id: 1
}
})
instance.get('/api/getbooks')
.then(
(res) => {
const {data, status} = res
console.log('// 先配置,后发起不带参请求')
console.log('data', data)
console.log('status', status)
console.log('-----------------')
}
)
instance2.get('/api/getbooks')
.then(
(res) => {
const {data, status} = res
console.log('// 先配置,后发起带参请求')
console.log('data', data)
console.log('status', status)
console.log('-----------------')
}
)
|
1
2
3
4
5
6
7
8
9
10
|
{
"status": 200,
"msg": "获取图书列表成功",
"data": [
{ "id": 1, "bookname": "西游记", "author": "吴承恩", "publisher": "北京图书出版社"},
{ "id": 2, "bookname": "三国演义", "author": "罗贯中", "publisher": "重庆图书出版社"},
{ "id": 3, "bookname": "红楼梦", "author": "曹雪芹", "publisher": "上海图书出版社"}
]
}
|
使用 APIFox 配置好接口 直接生成代码,以 Fetch API
为例
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
|
// pnpm add node-fetch // 在Node环境中使用 Fetch API 安装 pnpm add node-fetch
import fetch, {Headers} from 'node-fetch'
const myHeaders = new Headers()
myHeaders.append('User-Agent', 'apifox/1.0.0 (https://www.apifox.cn)')
const requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
}
/*
fetch('http://www.liulongbin.top:3006/api/getbooks', requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error))
*/
const response = await fetch('http://www.liulongbin.top:3006/api/getbooks', requestOptions)
const {data} = await response.json()
console.log(data)
const response2 = await fetch('https://api.github.com/users/github', requestOptions)
const data2 = await response2.json()
console.log(data2)
|
axios-post.js
,语法:axios.post(url[, data[, config]])
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
42
43
44
45
46
|
import axios from 'axios'
// 直接发起
axios.post('/api/addbook',
{
bookname: '金瓶梅',
author: '兰陵笑笑生',
publisher: '上海图书出版社'
},
{
baseURL: 'http://www.liulongbin.top:3006/',
timeout: 1000
}
).then(res => {
const {data} = res
console.log(data)
})
.catch(err => {
console.log(err)
})
/* 不允许重复添加 */
// 先配置,后发起请求
const instance = axios.create({
baseURL: 'http://www.liulongbin.top:3006/',
timeout: 1000
})
instance.post('/api/addbook',
{
bookname: '三国演义',
author: '罗贯中',
publisher: '重庆图书出版社'
})
.then(
(res) => {
const {data, status} = res
console.log('// 先配置,后发起不带参请求')
console.log('data', data)
console.log('status', status)
console.log('-----------------')
}
)
.catch(err => {
console.log(err)
})
|
axios-axios.js
,语法:axios(config: object)
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
42
43
44
45
46
47
48
49
50
|
import axios from 'axios'
// 默认Get 读取全部数据
axios('http://www.liulongbin.top:3006/api/getbooks')
.then((res) => {
const {data: dataObject} = res
console.log('默认Get 读取全部数据')
console.log('dataObject.data: ', dataObject.data)
console.log('--------------')
})
// 配置选项,读取全部数据
axios({
method: 'GET',
url: 'http://www.liulongbin.top:3006/api/getbooks'
})
.then((res) => {
const {data: dataObject} = res
console.log('配置选项,读取全部数据')
console.log('dataObject.data: ', dataObject.data)
console.log('--------------')
})
// 配置params选项,读取部分数据
axios({
method: 'GET',
url: 'http://www.liulongbin.top:3006/api/getbooks',
params: {id: 2}
})
.then((res) => {
const {data: dataObject} = res
console.log('配置params选项,读取部分数据')
console.log('dataObject: ', dataObject)
console.log('--------------')
})
// 发起提交请求
axios({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/addbook',
data: {
bookname: '哈利波特',
author: 'JK罗琳',
publisher: '上海译文图书出版社'
}
})
.then((res) => {
const {data: dataObject} = res
console.log('发起提交请求')
console.log('dataObject: ', dataObject)
console.log('--------------')
})
|
0.8 接口和接口文档 ⇧
使用 Ajax 请求数据时,被请求的 URL 地址,就叫做后端数据接口(简称接口),同时,每个接口必须有请求方式,例如:
GET
请求 http://www.liulongbin.top:3006/api/getbooks
获取图书列表的接口
POST
请求 http://www.liulongbin.top:3006/api/addbook
添加图书的接口
分析接口的请求过程
GET
请求过程:用户希望获取数据 -> 浏览器网页(数据载体)通过 Ajax 发起 GET 数据请求 -> 服务器 处理请求 -> 服务器发送响应给 浏览器 Ajax -> 浏览器网页展现结果
POST
请求过程:用户希望提交数据 -> 浏览器网页(数据载体)通过 Ajax 发起 POST 数据请求 -> 服务器 处理请求 -> 服务器发送响应给 浏览器 Ajax -> 浏览器网页展现结果
接口测试工具
- 为了验证接口能否被正常访问,常常需要使用接口测试工具,来对后端数据接口进行检测
- 接口测试工具能在不写任何代码的情况下,对接口进行调用和测试
- 一般有如下几种方式
- 命令行
curl
- IDE比如WebStorm自带的 工具 > HTTP客户端
- 国外 Postman
- 国内 apifox 和 apipost
使用 Postman 和 apifox 和 apipost
- Apifox 使用文档
- Postman 添加标签 Tab
- 选择 请求类型
- 填写 URL地址
- 在请求参数区域,填写参数和数据
- GET 需要在 Params 中填写
- POST 主要在
Body
中填写
- 一般点选
x-www-form-urlencoded
后填写
- 发送请求按钮 Send
- 查看服务器响应结果
使用 rap2 接口平台
0.9 案例,以vue
为例 ⇧
- 案例
- 图书管理接口(Vue + Axios)
- 聊天机器人接口
0.9.1 图书管理接口(Vue + Axios) ⇧
BooksInfo.vue
页面组件
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
|
<template>
<div class="books-info-demo">
<!-- 添加图书的Panel面板 -->
<AddBookInfo></AddBookInfo>
<!-- 展示图书的表格 -->
<ShowBookInfo></ShowBookInfo>
</div>
</template>
<script lang="ts">
import AddBookInfo from '@/components/ajax-demo/book-demo/AddBookInfo.vue';
import ShowBookInfo from '@/components/ajax-demo/book-demo/ShowBookInfo.vue';
import {defineComponent, provide, reactive} from '@vue/composition-api';
import Vue from 'vue';
export default defineComponent({
name: "BooksInfo",
components: {
AddBookInfo,
ShowBookInfo
},
props: {},
setup() {
const eventbus = reactive(new Vue());
provide<Vue>('eventbus', eventbus);
return {
eventbus
}
}
});
</script>
|
实现 AddBookInfo.vue
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
<template>
<div class="add-book card">
<div class="card-header">
<h3 class="card-title">添加新图书</h3>
</div>
<div class="card-body form-inline">
<VueInput labelName="书名"
class="form-control"
ref="iptBookname"
placeholder="请输入书名"
v-model="msgBook"/>
<VueInput labelName="作者"
class="form-control"
ref="iptAuthor"
placeholder="请输入作者"
v-model="msgAuthor"/>
<VueInput labelName="出版社"
class="form-control"
ref="iptPublisher"
placeholder="请输入出版社"
v-model="msgPublisher"/>
<VueButton @click="addBookInfo"
ref="btnAdd">添加
</VueButton>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {defineComponent, ref, inject, Ref} from '@vue/composition-api';
import VueButton from '@/components/button/VueButton.vue';
import VueInput from '@/components/input/VueInput.vue';
import {addBook} from '@/components/ajax-demo/http-request/addbook';
export default defineComponent({
name: 'AddBookInfo',
props: {},
components: {
VueButton,
VueInput
},
setup(props, ctx) {
const eventbus = inject<Vue>('eventbus');
const msgBook = ref('');
const msgAuthor = ref('');
const msgPublisher = ref('');
const addBookInfo = () => {
msgBook.value = (ctx.refs.iptBookname as unknown as Ref<string>).value.trim();
msgAuthor.value = (ctx.refs.iptAuthor as unknown as Ref<string>).value.trim();
msgPublisher.value = (ctx.refs.iptPublisher as unknown as Ref<string>).value.trim();
if (msgBook.value.length * msgAuthor.value.length * msgPublisher.value.length === 0) {return alert('请填写完整的图书信息!');}
// 发送添加书本信息请求
addBook({
bookname: msgBook.value,
author: msgAuthor.value,
publisher: msgPublisher.value
})
.then(res => {
const {data} = res;
if (data.status === 502) {return alert(data.msg);}
alert(data.msg);
// 通知兄弟组件 ShowBookInfo.vue 更新渲染图书列表
eventbus?.$emit('renderBookList');
});
};
return {
msgBook,
msgAuthor,
msgPublisher,
addBookInfo
};
}
});
</script>
<style lang="scss" scoped>
...
</style>
|
实现 ShowBookInfo.vue
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
<template>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">书名</th>
<th scope="col">作者</th>
<th scope="col">出版社</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody id="tb" @click="handleClick">
<tr v-for="{id, bookname, author, publisher} in rowsData" :key="id">
<th scope="row">{{ id }}</th>
<td>{{ bookname }}</td>
<td>{{ author }}</td>
<td>{{ publisher }}</td>
<td>
<a href="javascript:null;"
class="del"
:data-id="id">删除</a>
</td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import {defineComponent, ref, inject} from '@vue/composition-api';
import {getBookList} from '@/components/ajax-demo/http-request/getBookList';
import {delBook} from '@/components/ajax-demo/http-request/delBook';
export default defineComponent({
name: 'ShowBookInfo',
setup() {
const eventbus = inject<Vue>('eventbus');
const rowsData = ref<string[]>(['']);
const getBookInfo = () => {
getBookList()
.then(res => {
if (res.status === 200) {
const {data} = res.data;
rowsData.value = data;
}
})
.catch(err => {
return `获取数据失败!${err.message}`;
});
};
getBookInfo();
// 通过代理的方式为动态添加的元素绑定点击事件 删除图书
const handleClick = (e: MouseEvent) => {
if (Object.keys((e?.target as HTMLElement).dataset).includes('id')) {
const bookId = (e?.target as HTMLElement).dataset.id;
(bookId && (+bookId) <= 3)
? alert('请勿删除原始数据')
: bookId && delBook(
{id: bookId}
)
.then(res => {
if (res.status === 200) {
// const {data} = res.data;
getBookInfo();
return '删除图书成功!';
}
})
.catch(err => {
return `删除图书失败!${err.message}`;
});
}
};
eventbus?.$on('renderBookList', () => {
getBookInfo();
});
return {
rowsData,
getBookInfo,
handleClick
};
}
});
</script>
<style lang="scss" scoped>
...
</style>
|
实现 addbook.ts
1
2
3
4
5
6
7
8
9
10
11
12
|
import axios from 'axios';
export const addBook = (paramsData: Record<string, string>) => {
// 发送添加书本信息请求
return axios.post('/api/addbook',
paramsData,
{
baseURL: 'http://www.liulongbin.top:3006/',
timeout: 0
}
);
};
|
实现 delBook.ts
1
2
3
4
5
6
7
8
9
10
|
import axios from 'axios';
export const delBook = (paramsData: Record<string, string>) => {
return axios.get(
'/api/delbook',
{
baseURL: 'http://www.liulongbin.top:3006',
params: paramsData
});
};
|
实现 getBookList.ts
1
2
3
4
5
6
7
8
9
10
|
import axios from 'axios';
export const getBookList = () => {
return axios.get(
'/api/getbooks',
{
baseURL: 'http://www.liulongbin.top:3006'
});
};
|
0.9.2 聊天机器人接口 ⇧
头铁聊天AI机器人
- 文字
http://www.liulongbin.top:3006/api/robot?spoken=
- 语音
http://www.liulongbin.top:3006/api/synthesize?text=
<form></form>
表单的常用属性
- 通过 Ajax 提交表单数据
- 案例 评论列表
- 快速获取表单的数据
- 使用表单库
formily
表单在网页中主要负责 数据采集 的功能,即用于采集用户输入的信息,并通过 <form>
标签的提交操作,将采集的信息提交对服务端进行处理
1
2
3
4
5
6
7
8
9
|
<form action="">
<label for="info">Enter info: </label>
<input id="info" name="info" type="text"/>
<label for="password">Enter password: </label>
<input id="password" name="password" type="text"/>
<input id="checkbox" name="checkbox" type="text"/>
<label for="checkbox">记住我</label>
<button type="submit">提交</button>
</form>
|
- 表单由三个基本部分组成:
- 表单标签
<form></form>
- 表单域可以包含以下常用元素:
- 文本框
- 密码框
- 隐藏域
- 多行文本框
- 复选框
- 单选框
- 下拉选择框
- 表单按钮
<button type="submit"></button>
0.10.1 表单标签的属性
<form></form>
标签用来采集数据,<form></form>
标签的属性则是用来规定 如何将采集到的数据发送到服务器
属性 |
值 |
描述 |
action |
URL地址 |
规定当提交表单时,向何处发送表单数据 |
method |
GET(/POST) |
规定以何种方式将表单数据提交到 action URL |
enctype |
*1 默认application/x-www-form-urluncoded |
规定在发送表单数据之前,如何对其进行编码的格式 |
target |
*2 默认_self |
规定在浏览器何处打开 action URL |
*1
编码格式有:
application/x-www-form-urluncoded
multipart/form-data
text/plain
*2
打开方式有:
_blank
_self
_parent
_top
framename
action
action
规定当提交表单时,向何处发送表单数据
action
属性的值应该是 后端提供的 一个 URL 地址,这个 URL 地址专门负责接受表单提交过来的数据(即表单的数据最终提交到 action
属性所指向的 URL 地址来进行处理)
- 当
<form></form>
表单在未指定 action
属性值得情况下,action
的默认值为当前页面的 URL 地址
- 当提交表单后,页面会立即跳转到
action
属性指定的 URL 地址,同时将表单的数据以查询参数字符传的形式放在 URL 后
target
- 和
<a></a>
标签的属性相同,规定在浏览器何处打开 action URL
- 可选值有5个,默认情况下,
target
的值为_self
,表示在相同的框架或窗口中打开,一般为在当前页面中打开 action URL
值 |
描述 |
_blank |
在新标签页打开 |
_self |
默认值,在相同的框架或窗口中打开 |
_parent |
在父框架集中打开(很少用) |
_top |
在整个窗口中打开(很少用) |
framename |
在指定的框架中打开(很少用) |
method
- 规定以何种方式将表单数据提交到
action URL
- 可选值只有两个,分别是
GET
和 POST
,不支持其他方式(请求动词)
- 默认情况下,
method
的值为GET
,表示通过 URL 的形式,把表单数据提交到 action URL
- 数据为以
?
开头,数据名=数据值
的形式,并以&
连接不同的数据,一般称为查询字符串参数 Query String Parameters
- 发出
POST
请求时,提交的数据并不会显示在地址栏 URL 中
发出请求后,查看浏览器Network
面板中请求相关信息
- 请求地址
Request URL
完整地址
- 发出
GET
请求时,查询字符串参数 Query String Parameters
被单独取出,并分别显示数据
- 发出
POST
请求时,Request URL
只包含action URL
- 数据是通过
Form Data
的形式提交
- 数据相对更隐蔽,但不使用
https
协议的话,仍为明文传输数据,不安全
注意
GET
的语义为获取数据,提交的数据为查询参数,目的为查询并获取服务器上的数据,在表单中较少使用
POST
的语义为提交数据,适合用来在表单提交数据给服务器,包括文件上传
- 登录。注册或添加数据等表单操作,都只使用
POST
方式来提交表单
enctype
- 规定在发送表单数据之前,如何对其进行 编码的格式
- 可选值有三个,默认值为
application/x-www.form-urlencoded
,表示在发送前编码所有的字符
值 |
描述 |
application/x-www.form-urlencoded |
默认值,表示在发送前编码所有的字符,一般字符串数据 |
mutipart/form-data |
表示不对字符编码,在使用包含 文件上传空间的表单 时,必须使用该值 |
text/plain |
三空格转换为 + 号,但不对特殊字符编码,极少用 |
表单提交注意
- 在涉及 文件上传 操作时,必须将
enctype
的值设置为 mutipart/form-data
- 不涉及 文件上传 操作时,则将
enctype
的值设置为 application/x-www.form-urlencoded
0.10.2 表单的同步提交及缺点
- 通过
submit
按钮,触发表单提交的操作,从而使页面跳转到 action URL
的行为,叫做表单的同步提交
表单同步提交的缺点
<form></form>
表单同步提交后,整个页面会发生跳转,跳转到 action URL
所指向的地址,用户体验相对不好
<form></form>
表单同步提交后,回退到页面之前的状态和填写的数据会丢失
0.10.3 通过 Ajax
提交表单
可以解决表单同步提交的两个缺点
表单 只负责采集数据, Ajax
负责将数据提交到服务器
- 监听表单的提交事件
submit
- 阻止表单默认提交行为
event.preventDefault()
- 快速获取表单中的数据
- 使用 Ajax 异步提交数据
监听表单的提交事件 submit
阻止表单默认提交行为
即阻止默认表单提交后的跳转行为
- 当监听到表单的提交事件后,可以在回调函数中调用事件对象
e
的 e.preventDefault()
函数,来阻止表单的提交和页面的跳转
0.10.4 获取表单中的数据
在发送请求之前,需要获取用户填写表单的数据
参考
1. AJAX原理和应用 § ⇧
1.1 AJAX是浏览器上的功能 ⇧
- 通过JS发请求和收响应实现前后端交互
- 浏览器可以发请求,收响应
- 浏览器在
window
上加一个 XMLHttpRequest
构造函数
XMLHttpRequest
属于 BOM 浏览器模型 * MDN
XMLHttpRequest
对象实例可以发出HTTP请求与接收HTTP响应
- 使用浏览器内置的
XMLHttpRequest
和fetch API
(ES6), 实现在页面不刷新的情况下和服务端进行数据交互
- 优点:交互数据时不需要刷新整个页面,只需局部刷新,页面不发生用跳转
- 一旦拿到服务器返回的数据,AJAX 不会刷新整个网页,而是只更新网页里面的相关部分,从而不打断用户正在做的事情,体验较好
- 注意,AJAX 只能向 同源网址 (协议、域名、端口都相同)发出 HTTP 请求,如果发出跨域请求,就会报错
- 支持所有 HTTP 请求方法(请求动词)
1
2
|
typeof window.XMLHttpRequest
// function
|
- 用这个构造函数(类)可以构造出一个对象
- JS通过它实现发请求,收响应
1.2 准备服务器 ⇧
- 使用 server.js 作为服务器
- 下载或复制代码即可用
node sever.js 8888
启动
- 使用
node-dev
工具 ,安装
1
2
3
4
|
yarn global add node-dev
# 或者用
npm install -g node-dev
|
- 用
node-dev
代替node
,实现自动重启服务器
- 但不能监听页面自动刷新
1
|
node-dev server.js 8888
|
- 添加
index.html / main.js
两个路由
- 路由就是
if..else
- 在
index.html
中加载路由读取的main.js
- 加载的文件中引用到的路径,必须和路由的
if..else
中的路径path
一致,server.js
才会加载
- 而
response.write(fs.readFileSync('src/xxx'))
中的是相对路径,和path
的不同
1.3 复习 ⇧
- 在
server.js
中添加路由(条件),访问文件
- 添加
response.statusCode = 200
- 设置
Content-Type
为对应的类型response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
- 把文件读成字符串,再传入
response.write
- 结束,关闭
response.end()
1
2
|
const string = fs.readFileSync('src/index.html')
response.write(string)
|
文件实际上对于node
服务器来说就是字符串,只不过把字符串写到文件里而已
【HTTP非全解】请求和响应 & Node.Js Server
1.4 如何使用JS原生XMLHttpRequest
发起 Ajax
请求 ⇧
1.4.1 XMLHttpRequest
的基本使用 ⇧
XMLHttpRequest
(简称xhr)时浏览器提供的JS内置对象,通过它,可以 请求服务器上的数据源
- JQ中的
.ajax
函数和axios
是库基于xhr对象封装
- 和它同一等级的底层接口,功能相同的ES6 API是
Fetch API
以发送GET请求为例:
- 创建对象
new XMLHttpRequest()
一般使用命名为 xhr 的变量接收
- 配置参数
.open('GET', url)
指定 请求方式 和 URL地址
- 绑定事件
.onreadystatechange = () => {}
- 异步监听响应函数,判断响应状态
.readyState === 4
xhr 对象的请求状态
.status >= 200 && xhr.status < 300) || xhr.status === 304
服务器响应状态
- 使用服务器响应回来的数据
xhr.responseText
- 发送请求
.send()
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
|
const xhr = new XMLHttpRequest()
// console.log('readyState', xhr.readyState) // readyState 0
// xhr.open('GET', url, true)
// xhr.open('GET', '/login?username=john&password=123', true)
xhr.open('GET', url)
// console.log('readyState', xhr.readyState) // readyState 1
/* 异步代码 */
// xhr.onload = () => {} // 老方法
// xhr.addEventListener('load', () => {}) // 老方法
// 触发`load`事件表示 readyState已经是 4 了
// xhr.error = () => {} // 浏览器控制台模拟断网
// xhr.status === 304 重定向 缓存
xhr.onreadystatechange = () => {
// console.log('readyState', xhr.readyState)
// readyState 2
// readyState 3
// readyState 4
if(xhr.readyState === 4) {
if((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304){
console.log('xhr.status', xhr.status) // 200
const data = xhr.responseText
console.log(data)
}else{
console.log('error')
}
}
}
/* 异步代码 */
xhr.send()
|
- 区别
xhr.readyState
加载状态和xhr.status
服务器HTTP响应状态
实例,以黑马的免费教学API为例:
1
2
3
4
5
6
7
8
|
const xhr = XMLHttpRequest()
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
xhr.onreadystatechange = () => {
if(xhr.readystate === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.send()
|
1.4.2 xhr 对象的 readyState 属性 ⇧
XMLHttpRequest
对象的 readyState
属性,用来表示 当前 Ajax 请求所处的状态
每个 Ajax 请求必然处于以下5个状态中的一个
值 |
状态 |
描述 |
0 |
UNSENT |
XMLHttpRequest 对象已被创建,但尚未调用 open 方法 |
1 |
OPENED |
open 方法已被调用 |
2 |
HEADERS_RECIEVED |
send() 方法已被调用,响应头页已被接收 |
3 |
LOADING |
数据接收中,此时response 属性中已经包含部分数据 |
4 |
DONE |
Ajax 请求完成,数据传输已经完成或者失败 |
参考
1.4.3 使用 xhr 发起带参数的GET请求 ⇧
使用 xhr 发起带参数的GET请求时,需要在调用 xhr.open()
期间,为 URL 地址指定参数即可
1
2
3
4
5
6
7
8
9
10
11
12
|
const xhr = XMLHttpRequest()
// 查询查询字符串 ?id=1
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks?id=1')
xhr.onreadystatechange = () => {
if(xhr.readystate === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.send()
|
- 在URL地址后面拼接的参数
?id=1
,称为 查询字符串
- 查询字符串(URL参数)是指在 URL 末尾加上 用于向服务器发送信息 的字符串(变量)
- 格式为:
- 将英文的
?
放在 URL 的末尾,然后再加上 参数=值
- 加上多个参数,使用
&
符号进行分隔
- 一般只在
GET
请求中添加使用,将所需发送给服务器的数据添加到 URL 中
1
2
3
4
5
6
|
// 不带参数的 URL 地址
http://www.liulongbin.top:3006/api/getbooks
// 带参数的 URL 地址
http://www.liulongbin.top:3006/api/getbooks?id=1
// 带两个参数的 URL 地址
http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=西游记
|
1.4.4 GET请求携带参数的本质 ⇧
- 发起 GET 请求时,当 URL 需要携带参数的时候,本质上,都是直接将参数以查询字符串的形式,追加到 URL 地址的后面,发送到服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 为给定 ID 的 user 创建请求
// 发送 GET 请求(默认的方法)
axios('/user/12345')
.then(...);
// 等价于
axios.get('/user?ID=12345')
.then(...);
// 等价于
axios.get('/user', {
params: {
ID: 12345
}
})
.then(...);
|
打开浏览器控制面板查看GET请求
1
2
3
4
5
6
7
8
9
10
11
12
13
|
export const getBook = (id: number, bookname?: string) => {
return axios.get(
'/api/getbooks',
{
params : {
id: `${id}`,
bookname: bookname
}
});
};
getBook(1, '西游记');
|
- URL地址编码
请求 URL: http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=%E8%A5%BF%E6%B8%B8%E8%AE%B0
URL编码
- URL地址中,只允许出现英文相关的字母、标点符号、数字
- URL地址中,不允许出现中文字符
- 如果 URL 中需要包含中文字符,则必须对中文字符进行编码(转义)
- URL 编码原则:使用安全的字符(没有特殊用途或者意义的可打印字符)去表示不安全的字符
- 通俗理解:使用英文字符表示非英文字符
1
2
3
|
http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=西游记
# 经过 URL 编码之后,URL 地址变为如下格式
http://www.liulongbin.top:3006/api/getbooks?id=1&bookname=%E8%A5%BF%E6%B8%B8%E8%AE%B0
|
URL编码与解码 API
浏览器提供了 URL 编码与解码的 API,分别是:
1
2
3
4
|
encodeURI(`西游记`) // '%E8%A5%BF%E6%B8%B8%E8%AE%B0'
decodeURI(`%E8%A5%BF%E6%B8%B8%E8%AE%B0`) // '西游记'
encodeURI(`西`) // '%E8%A5%BF'
decodeURI(`%E8%A5%BF`) // '西'
|
浏览器自动对 URL 地址进行编码操作,无需关心 URL 地址的编码与解码操作
1.4.5 使用 xhr 发起带参数的POST请求 ⇧
步骤
- 创建
xhr
对象
- 调用
xhr.open()
函数 设置请求类型和 URL
- 调用
xhr.setRequestHeader()
设置 Content-Type
属性(固定写法)
- 监听
xhr.onreadystatechange
事件
- 调用
xhr.send()
函数,同时要在send()
函数的参数中指定要发送的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 1. 创建 `xhr` 对象
const xhr = new XMLHttpRequest()
// 2. 调用 `xhr.open` 函数
xhr.open('POST', 'http://www.liulongbin.top:3006/api/addbook')
// 3. 设置 `Content-Type` 属性(固定写法)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// 4. 监听 `xhr.onreadystatechange` 事件
xhr.onreadystatechange(() => {
if(xhr.readyState === 4 && xhr.status ===200) {
const {responseText} = xhr
console.log(responseText)
}
})
// 5. 调用 `xhr.send()`函数
// 同时要在`send()`函数的参数中指定要发送的数据,以查询字符串的形式,无需以 ? 开头
xhr.send('bookname=水浒传&author=施耐庵&publisher=天津出版社')
|
1.4.6 数据交换格式 ⇧
数据交换格式就是 服务器端与客户端 之间进行数传输与交换的格式
- 在涉及网络传输方面,XML 格式已经过时了
- 区别于 HTML (网页内容的载体),XML 作为数据的载体用来传输和存储数据
- XML 格式臃肿,和数据无关的代码多,传输效率低
- XML 牺牲了体积,增加冗余大小来实现数据语义化
- 需要额外的解析器
- JSON 是主流的交换格式
JavaScript Object Notation ⇧
- 中文翻译为 对象表示法
- JSON 就是 JavaScript 对象和数组 的 字符串表示法
- JSON 使用文本表示一个 JS 对象和数组的信息
- JSON 的本质可以理解为字符串数据,即用字符串来表示 JS 对象数据或数组数据
- JSON 是一种轻量级的文本数据交换格式。
- 用于在计算机和网络之间存储和传输数据
- 相较于 XML 优点为 体积更小、更快、更易解析
- 2.6 回顾复习JSON § ⇧
JSON 的两种结构 ⇧
JSON是用字符串来表示 JavaScript 的对象和数组
所以,JSON中包含 对象 和 数组 两种结构,通过这两种结构的互相嵌套,可以表示复杂的数据结构
JSON 的对象结构 ⇧
对象结构在 JSON 中表现为花括号 {}
括起来的内容
- 数据结构形式为
{key: value, key: value, ...}
的键值对结构
- 其中,key必须是使用 英文的双括号包裹 的字符串
- value 的数据类型只可以是 字符串、数字、布尔值、数组、对象 和 null 6种
错误范例:
1
2
3
4
5
6
7
8
9
|
{
name: "zd",
'age': 20,
"gender": 'female',
"address": undefined,
"hobby": ["美食", "睡觉", '追剧'],
// 错误的示范
"say": function() {},
}
|
- 属性名必须由双引号包裹
- 值为字符串类型时,必须由双引号包裹而不是由单引号或反引号
- 表示空值时,必须使用
null
而不是undefined
- 所有含有字符串的值时,必须加双引号而不是单引号或反引号
- 不允许函数作为值或属性
- 不允许在末尾数据项后加逗号
- JSON 中不可以写注释
- JSON 的最外层必须是对象或数组格式
- JSON 不允许使用
undefined
、function() {}
函数作为 JSON的属性或值
正确范例:
1
2
3
4
5
6
7
8
|
{
"name": "zd",
"age": 20,
"gender": "female",
"address": null,
"hobby": ["美食", "睡觉", "追剧"],
"isMarried": false
}
|
JSON 的数组结构 ⇧
JSON 的数组结构在 JSON 中表现为以 []
括起来的内容
- 数据结构为
["js", 30, true, {}, [], null]
- 数组中数据类型可以是 字符串、数字、布尔值、数组、对象 和 null 6种
合法的 数组形式的 JSON 数据
1
|
[{"name": "zs", "age": 20}, {"name": "ls", "age": 30}]
|
1
|
[["苹果", "榴莲", "椰子"], [4, 10, 5]]
|
注意 JSON 中不支持 bigInt 类型的数据
- JSON 标准(IETF 7159)中定义了 JSON 支持的数据展示类型为 string、number、boolean、null 和这四个基础类型所组成的 object、array 结构
- 其他 JavaScript 类型在编解码时都会被静默消除或转换
- 比如
undefined
、function() {}
- 使用
json-bigint
:可以像JSON.parse
一样方便转换(json-bigint 是一个第三方包,可以很好的处理这个问题。
- 参考文章:
JSON 和 JS 对象的关系 ⇧
- JSON 是 JS 对象的字符串表示法,他使用文本表示一个 JS 对象的信息,本质是一个字符串
1
2
3
4
|
// 一个对象
const obj = {a: 'Hello', b: 'World'}
// 一个 JSON 字符串,本质是一个字符串,遵循了 JSON 的语法规则
const json = '{"a": "Hello", "b": "World"}'
|
JSON 和 JS 对象的互转 ⇧
要实现从 JSON 字符串转换为 JS 对象,使用 JSON.parse()
方法
1
2
|
const obj = JSON.parse('{"a": "Hello", "b": "World"}')
// {a: 'Hello', b: 'World'}
|
要实现从 JS 对象串转换为 JSON 字符,使用 JSON.stringify()
方法
1
2
|
const obj = JSON.stringify({a: 'Hello', b: 'World'})
// '{"a": "Hello", "b": "World"}'
|
演示 JSON.parse()
在 发送 Ajax 请求时的应用
1
2
3
4
5
6
7
8
9
10
11
|
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
console.log(typeof xhr.responseText)
const result = JSON.parse(xhr.responseText)
console.log(typeof result) // object
}
}
xhr.send()
|
JSON.parse()
转换为结构化的对象从而在 JS 代码中方便处理数据
JSON.stringify()
转换为JSON字符串用来传输数据
JSON 的序列化和反序列化 ⇧
把 数据对象 转换为 字符串 的过程,叫序列化
- 调用
JSON.stringify(JSONObject)
方法的操作,叫 JSON 序列化
把 字符串 转换为 数据对象 的过程,叫反序列化
- 调用
JSON.parse(JSONString)
方法的操作,叫 JSON 反序列化
1.5 封装 Ajax
函数 ⇧
导入自己封装的 Ajax 函数
- 可以实现 调用自定义 myAjax 函数,发起 Ajax 请求
- 接受一个配置对象作为参数,配置对象中可以配置如下属性:
- method: 请求类型
- url: 请求地址
- data: 请求参数对象
- successCb: 请求成功后的回调函数
1
2
3
4
5
6
7
|
import myAjax from 'myAjax.js'
myAjax({
method: '', // 请求类型
url: '', // 请求地址
data: {}, // 请求参数对象
successCb: () => {}, // 请求成功后的回调函数
})
|
处理 data 参数
- 用户提交数据时,以对象形式提交参数
- 需要把 data 对象,转化成查询字符串的格式,提交给服务器
- 因此需提前定义
resolveData
函数如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
/*
* 处理 data 参数
* @param {Record<string, string>} data需要发送到服务器的数据
* @returns {string} 返回拼接好的查询字符串
*/
function resolveData(data) {
const arr = []
for(let k in data) {
arr.push(`${k}=${data.k}`)
}
return arr.join('&')
}
|
定义 myAjax
函数
- 需要创建 xhr 对象,并监听
onreadystatechange
事件
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function myAjax(options) {
const {data, successCb} = options
const xhr = new XMLHttpRequest()
// 拼接查询字符串
const qs = resolveData(data)
// 监听请求状态改变事件
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
const res_JSONObj = JSON.parse(xhr.responseText /* xhr.responseText JSONString*/)
successCb(res_JSONObj)
}
}
}
|
判断请求类型
- 不同的请求类型,对应 xhr 对象的不同操作。因此需要对请求类型进行条件分支的判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
...
const reqMap = {
'GET': () => {
xhr.open(method, `${url}?${qs}`)
xhr.send()
},
'POST': () => {
xhr.open(method, url)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(qs)
},
'PUT': () => {},
'DELETE': () => {},
'HEAD': () => {},
'OPTION': () => {},
}
reqMap[`${method.toUpperCase()}`]()
...
|
完整的 myAjax 函数
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
42
|
// node 环境 // import {XMLHttpRequest} from 'xmlhttprequest'
function resolveData(data) {
const arr = []
for(let k in data) {
arr.push(`${k}=${data[k]}`) // data[k] 注意 变量名属性
}
return arr.join('&')
}
const reqMap = {
'GET': () => {
xhr.open(method, `${url}?${qs}`)
xhr.send()
},
'POST': () => {
xhr.open(method, url)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(qs)
},
'PUT': () => {},
'DELETE': () => {},
'HEAD': () => {},
'OPTION': () => {},
}
export function myAjax(options) {
const {method, url, data, successCb} = options
const xhr = new XMLHttpRequest()
// 拼接查询字符串
const qs = resolveData(data)
// 监听请求状态改变事件
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
const res_JSONObj = JSON.parse(xhr.responseText /* xhr.responseText JSONString*/)
successCb(res_JSONObj)
}
}
reqMap[`${method.toUpperCase()}`]()
}
|
发送请求
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
|
import {myAjax} from './myAjax.js'
myAjax({
method: 'GET',
url: 'http://www.liulongbin.top:3006/api/getbooks',
data: {
id: 1
},
successCb: (res) => {
console.log(res)
}
})
myAjax({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/addbook',
data: {
bookname: '红宝书',
author: '红宝书',
publisher: '红宝书',
},
successCb: (res) => {
console.log(res)
}
})
|
1.6 如何使用XMLHttpRequest Level2
中提供的新特性 ⇧
XMLHttpRequest Level1
的缺点
- 只支持文本数据的传送,无法用来读取和上传二进制文件。
- 传送和接收数据时,没有进度信息,只能提示有没有完成。
- 受到"同域限制"(Same Origin Policy),只能向同一域名的服务器请求数据
XMLHttpRequest Level2
的优点
- 可以设置 HTTP 请求的时限
- 可以使用 FormData 对象管理表单数据
- 可以上传文件
- 可以请求不同域名下的数据(跨域请求)
- 可以获取服务器端的二进制数据
XMLHttpRequest Level2
可以获得数据传输的进度信息,能实现 fetch API
目前无法做到的事情,例如跟踪上传进度
1.6.1 使用XMLHttpRequest Level2
设置 HTTP 请求的时限 ⇧
有时, Ajax 操作很费时,而且无法预知要花多少时间。如果网速慢(模拟slow 3G)用户等待时间久
XMLHttpRequest Level2
的 timeout
属性实现可设置 HTTP 请求的时限
- 超过设置的时限,自动停止 HTTP 请求
- 监听 timeout 事件
xhr.ontimeout
,用来指定时间超限时的回调函数
1
2
3
4
5
|
const xhr = new XMLHttpRequest()
xhr.timeout = 3000 // 类型为number 单位为毫秒
xhr.ontimeout = () => {
console.log('请求超时')
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// yarn add -D xmlhttprequest-ts
import {XMLHttpRequest} from 'xmlhttprequest-ts' // XMLHttpRequest level2
// yarn add -D xhr2
// import XMLHttpRequest from 'xhr2' // XMLHttpRequest level2
const xhr = new XMLHttpRequest();
// timeout
xhr.timeout = 10
xhr.ontimeout = () => {
console.log('请求超时了')
}
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("-> xhr.responseText", xhr.responseText);
}
}
xhr.send()
|
Ajax 操作往往用来提交表单数据,为了方便表单处理,HTML5增加了一个 FormData
内置对象,可以模拟表单操作
- 新建
FormData
对象实例 fd
- 可多次调用
FormData
实例fd
的 .append(key: string, value: unknown)
方法,为 FormData
添加表单项
- 创建
xhr
实例对象
- 调用
xhr.open(method, url)
指定请求类型与地址,method
一般为POST
- 监听
onreadystatechange
事件,指定回调函数
- 调用
xhr.send(fd)
,将实例fd
作为参数发送请求,直接提交
- 无需设置
xhr.setHeader
的数据类型,内部自动设置 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 新建 FormData 对象实例 fd
const fd = new FormData()
// 可多次为 FormData 添加表单项
fd.append('uname', 'zz')
fd.append('upwd', '123456')
// 创建 xhr 实例对象
const xhr = new XMLHttpRequest()
// 指定请求类型与地址
xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata')
// 直接提交 FormData 对象实例,将实例fd作为参数发送请求
// 与提交网页表单的效果一样
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText))
}
}
xhr.send(fd)
// 无需设置 xhr.setHeader 的数据类型
|
FormData
对象也可以用来获取网页表单的值
- 获取网页中的
<form>
标签元素作为参数传入 new FormData()
实例对象中,发送请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 使用 Ajax 的形式提交表单
// 获取表单元素
const form = document.querySelector('#form1')
// 监听表单元素的 submit 事件
form.addEventListener('submit', (e) => {
e.preventDefault() // 使用 Ajax 的形式提交表单避险阻止默认提交行为
// 根据 form 表单创建 FormData 对象,会自动将表单数据填充到 FormData 对象中,无需手动 调用 .append 方法
const fd = new FormData(form)
// 无需设置 `xhr.setHeader` 的数据类型
const xhr = new XMLHttpRequest()
xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata')
xhr.onreadystatechange = () => {}
xhr.send(fd)
})
|
index.html
1
2
3
4
5
|
<form id="form1">
<input type="text" name="uname" autocomplete="off"/>
<input type="password" name="upwd" autocomplete="off"/>
<button type="submit">提交</button>
</form>
|
- 设置输入框标签的
autocomplete="off"
,关闭自动填充
参考
JS原生实现FormData
获取表单数据
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
|
...
<body>
<form id="form1">
<input type="text" name="uname" autocomplete="off" />
<input type="password" name="upwd" />
<button type="submit">提交</button>
</form>
<script>
// 1. 通过 DOM 操作,获取到 form 表单元素
const form = document.querySelector('#form1')
form.addEventListener('submit', function (e) {
// 阻止表单的默认提交行为
e.preventDefault()
// 创建 FormData,快速获取到 form 表单中的数据
const fd = new FormData(form)
const xhr = new XMLHttpRequest()
xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata')
xhr.send(fd)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText))
}
}
})
</script>
</body>
...
|
简单封装FormData.vue
仓库地址
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
<template>
<div>
<form ref="form1">
<VueInput type="text"
labelName="uname"
titleName="注册名:"
autocomplete="off"/>
<VueInput type="password"
labelName="upwd"
titleName="密码:"
autocomplete="off"/>
<VueButton @click="onSubmit">提交</VueButton>
{{ data }}
</form>
</div>
</template>
<script lang="ts">
import {defineComponent, onMounted, ref, reactive} from '@vue/composition-api';
import VueInput from '@/components/input/VueInput.vue';
import VueButton from '@/components/button/VueButton.vue';
export default defineComponent({
name: 'FormData',
components: {VueButton, VueInput},
props: {},
setup(props, ctx) {
const resData = ref<Record<string, unknown>>({});
const data = ref<Record<string, unknown>>({});
let form: HTMLFormElement = ctx.refs.form1 as HTMLFormElement;
// 根据 form 表单创建 FormData 对象,会自动将表单数据填充到 FormData 对象中
// 无需手动 调用 .append 方法
let formData: FormData = reactive(new FormData(form));
const onSubmit = (/*, e: Event*/) => {
// e.preventDefault(); // 使用 Ajax 的形式提交表单 避险阻止默认提交行为
formData = new FormData(form);
// 无需设置 `xhr.setHeader` 的数据类型
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata');
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
resData.value = JSON.parse(xhr.responseText);
(data.value as unknown) = resData.value.data;
}
};
xhr.send(formData);
};
onMounted(() => {
form = ctx.refs.form1 as HTMLFormElement;
// 根据 form 表单创建 FormData 对象,会自动将表单数据填充到 FormData 对象中
// 无需手动 调用 .append 方法
formData = new FormData(form);
});
return {
resData,
onSubmit,
formData,
form,
data
};
}
});
</script>
|
1.6.3 使用XMLHttpRequest Level2
上传文件 ⇧
- 上传图片API
- 接口:
http://www.liulongbin.top:3006/api/upload/avatar
- 请求类型:
POST
- 请求参数:
{'avatar': fileObj}
- 返回值
实现步骤
- 定义UI结构
- 验证是否选择了文件
- 向
FormData
中追加文件
- 使用 xhr 发起上传文件的请求
- 监听
onreadystatechange
事件
- 定义UI结构
1
2
3
4
5
6
|
<!-- 1. 文件选择框 -->
<input type="file" id="file1"/>
<!-- 2. 上传按钮 -->
<button id="btnUpload">上传文件<button/>
<!-- 3. 向服务器发起请求图片,并显示-->
<img src="" alt="" ref="img" width="800">
|
- 验证是否选择了文件
1
2
3
4
5
6
7
8
9
10
11
12
|
// 1. 获取上传文件按钮
const btnUpload = document.querySelector('#btnUpload')
// 2. 为按钮添加点击事件监听
btnUpload.addEventListener('click',() => {
// 3. 获取选择的文件列表
const files = document.querySelector('#file1').files
if(files.length <= 0) {return alert('请选择要上传的文件!')}
// 上传后续的业务逻辑
})
|
- 获取上传文件按钮
- 为按钮添加点击事件监听
- 获取选择的文件列表 使用
<input type="file"/>
元素的API .files
- 向
FormData
中追加文件
1
2
3
4
|
// 1. 创建 FormData 对象
const fd = new FormData()
// 2. 向 FormData 中追加文件
fd.append('avatar', files[0])
|
- 创建 FormData 对象
- 向 FormData 中追加文件
files[0]
- 使用 xhr 发起上传文件的请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 1. 创建 xhr 对象
const xhr = new XMLHttpRequest()
// 2. 调用 open 函数,指定请求类型与 URL 地址,上传文件的请求类型必须为 POST
xhr.open('POST', 'http://www.lilongbin.top:3006/api/upload.avtar')
// 3. 监听 `onreadystatechange` 事件
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText)
data.status === 200
? document.querySelector('#img').src = `http://www.lilongbin.top:3006${data.url}`
: console.log(data.message);
}
}
// 4. 发起请求
xhr.send(fd)
|
- 创建 xhr 对象
- 调用 open 函数,指定请求类型与 URL 地址,上传文件的请求类型必须为 POST
- 监听
onreadystatechange
事件
- 发起请求
封装 Upload.vue
组件
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
|
<template>
<div>
<VueInput type="file" ref="file1"/>
<VueButton ref="btnUpload"
@click="uploadFile">上传图片
</VueButton>
<br/>
<img src="" alt="" ref="img" width="800">
</div>
</template>
<script lang="ts">
import VueButton from '@/components/button/VueButton.vue';
import VueInput from '@/components/input/VueInput.vue';
import {defineComponent} from '@vue/composition-api';
import Vue, {VNode} from 'vue';
export default defineComponent({
name: 'Upload',
components: {VueInput, VueButton},
props: {},
setup(props, ctx) {
const uploadFile = () => {
const files = (ctx.refs.file1 as Vue).$el.querySelector('input')?.files;
if (files && files.length <= 0) {return alert('请选择要上传的文件');}
};
return {
uploadFile
};
}
});
</script>
|
1.6.4 使用XMLHttpRequest Level2
获得数据传输的进度信息 ⇧
在 XMLHttpRequest 2.0
对象中,可以通过监听 xhr.upload.onprogress
事件,来获取到文件上传的进度
- 监听 xhr.upload 的 onprogress 事件,设置回调函数
- 事件形参
e
e.lengthComputable
当前上传的资源是否具有可计算的长度
e.loaded
文件已传输的字节
e.total
需传输文件的总字节
1
2
3
4
5
6
7
8
9
10
11
|
// 创建 xhr 对象
const xhr = new XMLHttpRequest()
// 监听 xhr.upload 的 onprogress 事件
xhr.upload.onprogress = (e) => {
// e.lengthComputable 是一个布尔值,表示当前上传的资源是否具有可计算的长度
if(e.lengthComputable) {
// e.loaded 已传输的字节
// e.total 需传输的总字节
const percentComplete = Math.ceil((e.loaded / e.total) * 100)
}
}
|
可以设置浏览器Network面板中slow 3G 查看上传过程
1
2
3
|
-> percentComplete 39
-> percentComplete 78
-> percentComplete 100
|
动态设置进度条
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
<template>
<div>
<VueInput type="file" ref="file1"/>
<VueButton ref="btnUpload"
@click="uploadFile">上传图片
</VueButton>
<div style="">
<label for="file">文件上传进度:
<progress id="file" max="100" :value="`${percentComplete}`"
style="vertical-align: text-bottom;width: 30%;margin: 0 10px;"></progress>
<span>{{ progressTips }}</span>
</label>
</div>
<br/>
<img src="" alt="" ref="img" width="800">
</div>
</template>
<script lang="ts">
import VueButton from '@/components/button/VueButton.vue';
import VueInput from '@/components/input/VueInput.vue';
import {computed, defineComponent, inject, ref} from '@vue/composition-api';
import Vue from 'vue';
export default defineComponent({
name: 'Upload',
components: {VueInput, VueButton},
props: {},
setup: function (props, ctx) {
// 动态进度条数值
const percentComplete = ref<number>(0);
// 进度条提示文字
const progressTips = computed(() => {
return percentComplete.value === 100
? '上传完成'
: `${percentComplete.value}%`;
});
const toast = inject<Function>('toast');
const uploadFile = () => {
// 获取用户选择的文件列表
const fileList = (ctx.refs.file1 as Vue).$el.querySelector('input')?.files;
if (fileList && fileList.length <= 0) {return toast && toast('请选择要上传的文件!');}
const fd = new FormData();
// 将用户选择的文件添加到 FormData 中
fileList && fd.append('avatar', fileList[0]);
// 创建 xhr 对象
const xhr = new XMLHttpRequest();
// 监听文件上传的进度
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
// 计算出上传的进度 动态设置进度条
percentComplete.value = Math.ceil((e.loaded / e.total) * 100);
}
};
// 调用 open 函数,指定请求类型与 URL 地址,上传文件的请求类型必须为 POST
xhr.open('POST', 'http://www.liulongbin.top:3006/api/upload/avatar');
// 监听 `onreadystatechange` 事件
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
data.status === 200
? (ctx.refs.img as HTMLImageElement).src = `http://www.liulongbin.top:3006${data.url}`
: toast && toast(data.message);
}
};
// 发起请求
xhr.send(fd);
};
return {
uploadFile,
percentComplete,
progressTips
};
}
});
</script>
|
1.6.5 使用XMLHttpRequest Level2
请求不同域名下的数据(跨域请求) ⇧
设置严格检查<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
参考
1.7 复习使用axios
发起 Ajax
请求 ⇧
1.8 实现文件上传与loading
效果 ⇧
2 AJAX应用 ⇧
2.1 AJAX加载CSS § ⇧
以前使用<link rel="stylesheet" href="/style.css">
引入
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,viewport-fit=cover">
<link rel="stylesheet" href="/style.css">
<title>AJAX demo</title>
</head>
<body>
<h1>AJAX demo</h1>
</body>
</html>
|
- 这里
--<link rel="stylesheet" href="/style.css">
中的/style.css"
是路由path
的请求路径
server.js
1
2
3
4
5
6
7
8
9
|
// ...
if (path === '/index.html') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
const string = fs.readFileSync('src/index.html')
response.write(string)
response.end()
}
// ...
|
style.css
1
2
3
4
|
body {
color: red;
background-color: #eaeaea;
}
|
使用 AJAX
加载 CSS
的四个步骤 ⇧
在 server.js
里写路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// ...
if (path === '/index.html') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
const string = fs.readFileSync('src/index.html')
response.write(string)
response.end()
} else if (path === '/main.js') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
response.write(fs.readFileSync('src/main.js'))
response.end()
} else if (path === '/style.css') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/css;charset=utf-8')
response.write(fs.readFileSync('src/style.css'))
response.end()
}
// ...
|
- 区别路由
path
的请求路径和fs.readFileSync('src/xxx')
的查找文件目录路径
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,viewport-fit=cover">
<!-- <link rel="stylesheet" href="/style.css"> -->
<title>AJAX demo 2</title>
</head>
<body>
<h1>AJAX demo CSS</h1>
<p>
<button id="getCSS">请求CSS</button>
</p>
<script src="/main.js"> </script>
</body>
</html>
|
index.html
里写的所有路径(/style.css
和/main.js
),必须和路由的path
路径一致,才能生效
- 创建一个 button 测试
在 main.js
里写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
getCSS.onclick = () => {
const request = new XMLHttpRequest()
request.open('GET', '/style.css');
request.onload = () => {
// console.log('成功了')
console.log('request.response')
console.log(request.response)
// 之后将响应的内容,用对应的标签包裹,插入html页面相应位置
}
request.onerror = () => {
console.log('失败了')
}
request.send()
}
|
- 创建
HttpRequest
对象 (全称是 XMLHttpRequest
)
- 调用对象的
open
方法
- 监听对象的
onload
和 onerror
事件
- 调用对象的
send
方法(发送请求)
- 获取资源用
request.open('GET', '/xxx');
,只用两个参数,mdn 中后面的参数不用XMLHttpRequest.open() MDN
而request.response
就是响应得到的文件内容
1
2
3
4
|
// 将响应的内容,用对应的标签包裹,插入html页面相应位置
const style = document.createElement('style') // 创建 style 标签
style.innerHTML = request.response // 填写 style 内容
document.head.appendChild(style) // 插入head标签里
|
触发onerror
事件
1
2
3
|
request.onerror = () => {
console.log('失败了')
}
|
server.js
路径写错:你输入的路径不存在对应的内容
1
2
3
4
5
6
7
8
|
//...
else {
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(`你输入的路径不存在对应的内容`)
response.end()
}
//...
|
- 只会渲染路径错误时的路由,并不触发
onerror
事件
onerror
对AJAX的支持不好
在第3步中,AJAX一般改用 onreadystatechange
事件,不用onload
和onerror
- 用对象的
status
属性,即状态码来判断,响应是否成功
- 注意
onreadystatechange
全小写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
getCSS.onclick = () => {
const request = new XMLHttpRequest()
request.open('GET', '/style.css');
request.onreadystatechange = () => {
// 监听对象的 readyState属性
// console.log(request.readyState) // 2 3 4
if (request.readyState === 4) {
// console.log('下载完成')
// 下载完成,但不知道下载成功还是失败
if (request.status >= 200 && request.status < 300) {
// 将响应的内容,用对应的标签包裹,插入html页面相应位置
const style = document.createElement('style') // 创建 style 标签
style.innerHTML = request.response // 填写 style 内容
document.head.appendChild(style) // 插入head标签里
} else {
console.log('加载CSS失败')
}
}
}
request.send()
}
|
2.2 AJAX加载JS § ⇧
以前使用<script src="main.js"></script>
引入
使用 AJAX
加载 JS
的四个步骤 ⇧
server.js
1
2
3
4
5
6
7
8
|
// ...
else if (path === '/getJS.js') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
response.write(fs.readFileSync('src/getJS.js')) // 在目录中创建一个测试的getJS.js
response.end()
}
//...
|
index.html
1
2
3
4
|
<p>
<button id="getCSS">请求CSS</button>
<button id="getJS">请求JS</button>
</p>
|
- 创建
HttpRequest
对象 (全称是 XMLHttpRequest
)
- 调用
open
方法
- 监听对象的
onreadystatechange
事件
- 调用对象的
send
方法(发送请求)
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//...
// AJAX 加载 JS
getJS.onclick = () => {
const request = new XMLHttpRequest
request.open('GET', '/getJS.js')
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status >= 200 && request.status < 300) {
// 创建script标签
const script = document.createElement('script')
// 填写 script内容
script.innerHTML = request.response
// 插到body里
document.body.appendChild(script)
}
}
request.send()
}
//...
|
- 在事件处理函数里操作 JS 文件内容
- 将响应的内容,用对应的标签包裹,插入html页面相应位置
- AJAX下载并执行响应的内容
2.3 AJAX加载HTML § ⇧
以前没有加载过 HTML
- 在目录
src/
下创建测试用indexOld.html
,不写全,只写个标签
1
2
|
<h2>AJAX demo 2</h2>
<div style="background-color: bisque;border: 1px solid orange;width:300px;height:300px;">动态内容</div>
|
配置路由server.js
1
2
3
4
5
6
7
8
|
//...
else if (path === '/indexOld.html') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(fs.readFileSync('src/indexOld.html'))
response.end()
}
//...
|
index.html
加测试按钮
1
2
3
4
5
|
<p>
<button id="getCSS">请求CSS</button>
<button id="getJS">请求JS</button>
<button id="getHTML">请求HTML</button>
</p>
|
使用 AJAX
加载 HTML
的四个步骤 ⇧
- 创建
HttpRequest
对象
- 调用对象的
open
方法
- 监听对象的
onreadystatechange
事件
- 调用对象的
send
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//...
// AJAX 加载 HTML
getHTML.onclick = () => {
const request = new XMLHttpRequest
request.open('GET', '/indexOld.html')
request.onreadyStatechange = () => {
if (request.readyState === 4 && request.status >= 200 && request < 300) {
// 创建 div 标签
const div = document.createElement('div')
// 填写 div 内容
div.innerHTML = request.response
// 插到body里
document.body.appendChild(div)
}
}
request.send()
}
//...
|
- 在事件处理函数里操作
HTML
文件内容
- 动态展示
index.html
- 控制台
Network
查看请求 点Preview
预览请求的页面
- 点
Response
看源代码
- 把页面拆成不同的部分,在用户点击时,请求这个部分
- 可以请求一小块 HTML,而
iframe
是请求整个网页,更加臃肿
AJAX
可以做到轻量级的请求,任意 HTTP 支持的文件类型
2.4 AJAX加载XML § ⇧
使用 AJAX
加载 XML
的四个步骤 ⇧
server.js
1
2
3
4
5
6
7
8
|
//...
else if (path === '/getXML.xml') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/xml;charset=utf-8')
response.write(fs.readFileSync('src/getXML.xml'))
response.end()
}
//...
|
index.html
1
2
3
4
5
6
|
<p>
<button id="getCSS">请求CSS</button>
<button id="getJS">请求JS</button>
<button id="getHTML">请求HTML</button>
<button id="getXML">请求XML</button>
</p>
|
getXML.xml
1
2
3
4
5
6
|
<?xml version="1.0" encoding="UTF-8"?>
<message>
<warning>
Hello World
</warning>
</message>
|
- 创建
HttpRequest
对象(全称是 XMLHttpRequest
)
- 调用对象的
open
方法
- 监听对象的
onreadystatechange
事件
- 调用对象的
send
方法(发送请求)
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// AJAX 加载 XML
getXML.onclick = () => {
const request = new XMLHttpRequest()
request.open('GET', '/getXML.xml')
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status >= 200 && request.status < 300) {
// console.log(request.responseXML)
const dom = request.responseXML
const text = dom.getElementsByTagName('warning')[0].textContent.trim();
console.log(text)
console.log(request.responseXML)
}
}
request.send()
}
|
- 在事件处理函数里操作
responseXML
request.responseXML
是 DOM 对象
2.5 AJAX应用小结 § ⇧
HTTP 可以请求的类型 ⇧
- 可以请求
HTML
CSS
JS
…
- 设置正确的
Content-Type
- 响应的内容,用对应的标签包裹,插入html页面相应位置
- 知道怎么解析,就可以使用
解析方法 ⇧
- 得到 CSS 后生成
style
- 得到 JS 后生成
script
- 得到 HTML 使用
innerHTML
和 DOM API
- 得到 XML 后使用
responseXML
和 DOM API
- 不同类型的数据有不同的解析办法
2.6 回顾复习JSON § ⇧
JavaScript Object Notation
- JSON 是 轻量级的数据交换格式语言
- 和HTML CSS JS一样是门独立的语言
- 和HTML XML Markdown 是 标记语言,用来展示数据
- 中文官网
- 一页纸
- 铁轨图
支持的类型 ⇧
string
只支持双引号,不支持单引号、反引号和无引号
number
支持科学技术法
bool
true
和 false
null
没有 undefined
object
array
区别于 JS 的类型
和JS语法的区别:
- JSON 的字符串必须用双引号。
- JSON 无法表示 undefined,只能表示 “undefined”
- JSON 无法表示函数
- JSON 的对象语法不能有引用
2.7 AJAX加载JSON § ⇧
server.js
1
2
3
4
5
6
|
else if (path === '/getJSON.json') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/json;charset=utf-8') // 'application/json;charset=utf-8'也可
response.write(fs.readFileSync('src/getJSON.json'))
response.end()
}
|
getJSON.json
1
2
3
4
5
6
|
{
"name": "frank",
"gender": "male",
"age": 18,
"xxx": null
}
|
index.html
1
2
3
4
5
6
7
8
|
<p>
<button id="getCSS">请求CSS</button>
<button id="getJS">请求JS</button>
<button id="getHTML">请求HTML</button>
<button id="getXML">请求XML</button>
<button id="getJSON">请求JSON</button>
</p>
<h2>Hello <span id="myName"></span></h2>
|
使用 AJAX
加载 JSON
的四个步骤 ⇧
- 创建
HttpRequest
对象(全称是 XMLHttpRequest
)
- 调用对象的
open
方法
- 监听对象的
onreadystatechange
事件
- 调用对象的
send
方法(发送请求)
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// AJAX 加载 JSON
getJSON.onclick = () => {
const request = new XMLHttpRequest()
request.open('GET', '/getJSON.json')
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status === 200) {
console.log(request.response)
const object = JSON.parse(request.response)
console.log(object)
myName.textContent = object.name
}
}
request.send()
}
|
- 在事件函数里使用
JSON.parse
JSON.parse
把符合 JSON 语法的字符串变成对应的对象或者是其他东西
2.8 JS内置对象 window.JSON § ⇧
JSON.parse('string')
⇧
- 解析字符串
- 将符合
JSON
语法的字符串转换成 JS
对应类型的数据
- 返回值 Object 类型, 对应给定 JSON 文本的对象/值
JSON
字符串 -> JS 数据
- 转换成的不一定是对象,可以是 JSON 值类型的其他类型( typeof )
- 由于 JSON 只有六种类型,所以转成的数据也只有6种
- 如果不符合
JSON
语法,则直接抛出一个 Error
对象
- 一般用
try...catch
捕获错误
- 异常 若传入的字符串不符合 JSON 规范,则会抛出 SyntaxError 异常
1
2
3
4
5
6
7
8
9
|
let object
try{
object = JSON.parse(`{'name':'frank'}`)
}catch(error){
console.log('Error!')
console.log(error)
object = {"name":"no name"}
}
console.log(object)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
const json = '{"result":true, "count":42}'
const obj = JSON.parse(json)
console.log(obj.count)
// expected output: 42
console.log(obj.result)
// expected output: true
JSON.parse('{}'); // {}
JSON.parse('true'); // true
JSON.parse('"foo"'); // "foo"
JSON.parse('[1, 5, "false"]'); // [1, 5, "false"]
JSON.parse('null'); // null
JSON.parse() 不允许用逗号作为结尾
// both will throw a SyntaxError
JSON.parse("[1, 2, 3, 4, ]");
JSON.parse('{"foo" : 1, }');
|
JSON.stringify(value)
序列化 ⇧
- 是
JSON.parse()
的逆运算
- JS 数据 ->
JSON
字符串
- 即序列化成 一个 JSON 字符串的值
- 将值转换为相应的JSON格式
- 由于 JS 的数据类型比 JSON 多,所以不一定成功
- 如果失败,则直接抛出一个
Error
对象
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
42
43
44
45
46
47
48
49
50
51
|
const obj = {"hi": "Ho", "fn": () => {}}
JSON.stringify(obj) // 不报错 直接忽略不合法的类型
JSON.stringify({}); // "{}"
JSON.stringify(true); // "true"
JSON.stringify("foo"); // ""foo""
JSON.stringify([1, "false", false]); // "[1,"false",false]"
JSON.stringify({ x: 5 }); // "{"x":5}"
JSON.stringify({x: 5, y: 6});
// "{"x":5,"y":6}"
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// '[1,"false",false]'
JSON.stringify({x: undefined, y: Object, z: Symbol("")});
// '{}'
JSON.stringify([undefined, Object, Symbol("")]);
// '[null,null,null]'
JSON.stringify({[Symbol("foo")]: "foo"});
// '{}'
JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]);
// '{}'
JSON.stringify(
{[Symbol.for("foo")]: "foo"},
function (k, v) {
if (typeof k === "symbol"){
return "a symbol";
}
}
);
// undefined
// 不可枚举的属性默认会被忽略:
JSON.stringify(
Object.create(
null,
{
x: { value: 'x', enumerable: false },
y: { value: 'y', enumerable: true }
}
)
);
// "{"y":"y"}"
|
2.9 AJAX综合应用:加载分页 § ⇧
2.9.1 加载列表 ⇧
- 用户打开页面,开到第一页数据
- 用户点击下一页,看到第二页数据
- 用户点击下一页,看到第三页数据
- 用户点击下一页,提示没有更多了
/db/page1.json page2.json page3.json
index.html
1
2
3
4
5
6
7
8
|
<body>
<!-- ... -->
<h2>Hello <span id="myName"></span></h2>
<div>
{{page1}}
</div>
<!-- ... -->
</body>
|
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
if (path === '/index.html') {
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
let string = fs.readFileSync('src/index.html').toString() // 调用toString()前 是buffer
// AJAX分页
const page1 = fs.readFileSync('db/page1.json')
// li
const array = JSON.parse(page1) // [{ "id": 1 }, { "id": 2 }, { "id": 3 }, { "id": 4 }, { "id": 5 }, { "id": 6 }, { "id": 7 }, { "id": 8 }, { "id": 9 }, { "id": 10 }]
const result = array.map(item => {
return `<li>${item.id}</li>`
}).join('')
string = string.replace('{{page1}}', `<ul>${result}</ul>`)
response.write(string)
response.end()
}
|
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
let pageCount = 1
// AJAX 加载 分页
getPage.onclick = () => {
const request = new XMLHttpRequest()
request.open('GET', `/page${pageCount + 1}`)
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status === 200) {
const array = JSON.parse(request.response)
array.forEach(item => {
const li = document.createElement('li')
li.textContent = item.id
xxx.appendChild(li)
});
// console.log(request.response)
const object = JSON.parse(request.response)
// console.log(object)
myName.textContent = object.name
pageCount += 1
}
}
request.send()
}
|
2.9.2 优化点 ⇧
main.js
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
|
//...
let page = [1, 2, 3]
let currentPage = page[0]
// AJAX 加载 分页
getPage.onclick = () => {
if (currentPage < 3) {
const request = new XMLHttpRequest()
request.open('GET', `/page${currentPage + 1}`)
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status === 200) {
const array = JSON.parse(request.response)
array.forEach(item => {
const li = document.createElement('li')
li.textContent = item.id
xxx.appendChild(li)
});
// console.log(request.response)
const object = JSON.parse(request.response)
// console.log(object)
myName.textContent = object.name
currentPage += 1
}
}
request.send()
} else {
// console.log("Ban")
getPage.disabled = true
}
}
//...
|
2.9.3 请求天气数据 ⇧
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
|
// mock 接口 GET 方法 查询参数直接跟url在后面
let url = 'http://rap2api.taobao.org/mock/244238/weather?city=北京'
let xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = () => {
// xhr.DONE === 4
if(xhr.readyState === 4){
if( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304){
// 将xhr.responseText的JSON格式转换为对象
console.log(JSON.parse(xhr.responseText))
// xhr.responseType = 'json' // 兼容性注意
console.log(xhr.response)
} else {
console.log('服务器异常')
}
}
}
xhr.responseType = 'json' // 兼容性注意
xhr.onerror = () => {
console.log('服务器异常')
}
xhr.send()
|
2.9.4 发送POST请求 ⇧
应用场景:涉及到登录、注册、用户填写信息(调查问卷)需提交,而不是查询的场合时,要用POST请求
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
42
43
44
|
let url = 'http://rap2api.taobao.org/app/mock/24238/login'
let xhr = new XMLHttpRequest()
// 设置xhr请求超时时间
xhr.timeout = 3000
xhr.open('POST', url)
// 设置请求头 告诉服务器请求参数的编码方式
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded') // post 方法 用xhr.send()接受发送的数据,区别于get方法中写在url里
// 封装 url
function makeUrl(obj) {
let arr = []
for(let key in obj){
arr.push(`${key} = ${obj[key]}`)
}
return arr.join('&')
}
const testUser = makeUrl({
username: 'John',
password: '123456'
})
// xhr.send('username=John&password=123456')
// 将参数变成 `key1=value1&key2=value2` 的形式,传给xhr.send()
xhr.send(testUser)
// 如果要上传文件(图片)
// xhr.setRequestHeader('Content-type', 'multipart/form-data')
//
xhr.onload = () => {
if( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 ){
console.log(JSON.parse(xhr.responseText))
} else {
console.log('响应异常')
}
}
xhr.ontimeout = () => {
console.log('请求超时')
}
xhr.onerror = () => {
console.log('服务器异常')
}
|
2.9.3 注册登录发送POST请求 ⇧
默认的Content-type
,是multipart/form-data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
let formData = new FormData()
// 添加参数
formData.append('username', 'jzm')
formData.append('password', '132456')
let url = 'http://rap2api.taobao.org/app/mock/244238/register'
let xhr = new XMLHttpRequest()
xhr.open('POST', url, true)
xhr.onload = () => {
if(xhr.status ===200 || xhr.status === 304) {
console.log(JSON.parse(xhr.responseText))
console.log('登陆成功')
} else {
console.log('接口异常')
}
}
xhr.send(formData)
|
2.9.4 POST ⇧
1
2
3
4
5
6
7
8
9
10
|
<body>
<form>
<input type="text" name="username" placeholder="用户名">
<input type="password" name="password" placeholder="密码">
<input type="file" name="file">
<input type="submit">
<p id="msg"></p>
</form>
</body>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
const $ = string => {return document.querySelector(string)}
const $submit = $('[type = submit]')
const $form = $('#form')
const $msg = $('#msg')
let url = 'http://rap2api.taobao.org.app/mock/244238/register'
$form.onsubmit = (e) => {
e.preventDefault()
// 校验开始
let formData = new FormData($form)
let xhr = new XMLHttpRequest()
xhr.open('POST', url, true)
xhr.onload = () => {
if(xhr.status === 200 || xhr.status === 304) {
let data = JSON.parse(xhr.responseText)
$msg.innerText = data.msg || data.errMsg
} else {
$msg.innerText = '接口异常'
}
}
xhr.send(formData)
}
|
3. 手写AJAX § ⇧
使用原生 JS 写出一个 AJAX 请求
1
2
3
4
5
6
7
8
9
10
11
|
myButton.onclick = () => {
let request = new XMLHttpRequest()
request.open('GET','/xxx')
request.onreadystatechange = () => {
if(request.readystate === 4 && request.status === 200) {
const string = request.responseText
const stringObj = window.JSON.parse(string)
}
}
request.send()
}
|
后台的 server.js
,处理请求
1
2
3
4
5
6
|
if(path === '/xxx') {
response.statusCode = 200
response.setHeader('Content-Type','text/json;charset=utf-8')
response.write(`{"status":"ok"}`)
response.end()
}
|
4. ajax
的简陋封装 § ⇧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
const request = (url, cityParams, onsuccess, onerror) => {
url = url + '?' + Object.entries(cityParams).map(arr => arr[0] + '=' + arr[1]).join('&')
let xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onload = () => {
if(xhr.status === 200 || xhr.status ===304){
onsuccess(JSON.parse(xhr.responseText))
} else {
onerror()
}
}
xhr.onerror = onerror
xhr.send()
}
request('http://rap2api.taobao.org/app/mock/244238/weather',{city:'内蒙古'}, data => {
console.log('请求成功')
console.log(data)
}, () => {
console.log('接口异常')
})
|
5. fetch
代替JS原生AJAX § ⇧
AJAX的API设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。
- Fetch API 是一种ES6规范,用来取代XMLHttpRequest对象
- 两个特点,一是接口合理化,Ajax 将所有不同性质的接口都放在 XHR 对象上,而 Fetch 将它们分散在几个不同的对象上,设计更合理;
- 二是 Fetch 操作返回Promise对象,避免了嵌套的回调函数。
1
2
3
4
5
6
|
let url = 'http://api.jirengu.com/getWeather.php?city=北京'
fetch(url).then(response => {return response.json()})
.then(data => {
document.body.innerText = data.results[0].weather_data[0].weather
})
|
POST
1
2
3
4
5
6
7
8
9
10
11
12
|
let url = 'http://api.jirengu.com/getWeather.php?city=北京'
let data = {username: 'jirengu', password: '13456'}
fetch(url,{
method: 'POST',
body: Object.entries(data).map(arr => { return arr[0] + '=' + arr[1]}).join('&'),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(res => {return res.json()})
.then(response => console.log('Success:', response))
.catch(error => {console.log('Error:', error)})
|
使用githubpagesmock
数据 § ⇧
搭建服务器
同步开发,约定格式,模拟数据,前后端联调
使用easymock
来mock
数据 § ⇧
轮询与长轮询Comet聊天室 ⇧
双工通信:每隔固定时间发一次请求
客户端
- 发请求,等待响应(pending)
- 当响应时,再次发一个请求
服务器端
- 请求到来,如果没新数据,则不发响应
- 当有新数据,需要通知客户端,再响应
参考文章 ⇧
相关文章 ⇧
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河
掘
思
知
简