Ajax XMLHttpRequest & 跨域:
一.XMLHttpRequest:
XMLHttpRequest是浏览器内置的一个构造函数
作用:基于 new 出来的 XMLHttpRequest 实例对象,可以发起 Ajax 的请求。 axios 中的 axios.get()、axios.post()、axios() 方法,都是基于 XMLHttpRequest(简称:XHR) 封装出来的!
使用 XMLHttpRequest 发起 GET 请求:
主要的 4 个实现步骤:
-
创建 xhr 对象
-
调用 xhr.open() 函数
-
调用 xhr.send() 函数
-
监听 load 事件
<script> let xhr = new XMLHttpRequest(); xhr.open('GET', 'http://xxx.com/api/xx'); xhr.send(); xhr.addEventListener('load', function () { console.log(this.response); }) </script>
请求时携带URL参数 或 提交请求体 :
- URL参数,只能拼接在 URL 地址 后面
- 请求体,放到 send() 里面
<script>
let xhr = new XMLHttpRequest();
// 将请求参数拼接到url后面
xhr.open('请求方式', 'http://www.itcbc.com/api/xx?id=1&username=zhangsan');
xhr.send(请求体);
xhr.addEventListener('load', function () {
console.log(this.response);
})
</script>
提交请求体数据,需指定Content-Type头:
当需要提交请求体数据时,需要在 xhr.open() 之后,调用 xhr.setRequestHeader() 函数,指定请求体的编码格式
<script>
let xhr = new XMLHttpRequest();
// 将请求参数拼接到url后面
xhr.open('POST', 'http://www.itcbc.com/api/post');
// 根据请求体格式的不同,需设置对应的Content-Type头
xhr.setRequestHeader('Content-Type', '值')
xhr.send('username=zs&age=20');
xhr.addEventListener('load', function () {
console.log(this.response);
})
</script>
请求体格式 和 对应的Content-Type值:
为了方便服务器接收数据,当提交请求体时,需要指定一个叫做Content-Type的请求头
| 请求体格式 | Content-Type | 是否需要在代码中指定 |
|---|---|---|
| 参数=值&参数=值 | application/x-www-form-urlencoded | 是 |
| '{ "id": 1, "name": "zs" }' | application/json | 是 |
| new FormData() | multipart/form-data; xxxxxxxxx随机字符 | 否,浏览器自动设置 |
例:xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
使用axios就不用关心这个请求头了,因为axios已经帮我们处理好了加请求头这件事,不过写原生代码,就需要自己指定了
二.数据交换格式:
数据交换格式,就是服务器端与客户端之间数据传输的格式
两种数据交换格式:
- XML(很少用)
- JSON(主流)
什么是 JSON:
-
JSON(全称:JavaScript Object Notation)是一种数据交换格式,它本质上是用字符串的方式来表示对象或数组类型的数据
-
JSON 数据的格式有两种:对象格式 数组格式
-
JSON 的语法要求
-
属性名必须使用双引号包裹
-
字符串类型的值必须使用双引号包裹
-
JSON 中不允许使用单引号表示字符串
-
JSON 中不能写注释
-
JSON 的最外层必须是对象或数组格式(其他类型也可以,但多数是对象或数组格式)
-
不能使用 undefined 或函数作为 JSON 的值
-
-
对象格式的 JSON 数据,最外层使用 { } 进行包裹,内部的数据为 "key": "value" 的键值对结构:
- key 必须使用英文的双引号进行包裹
- value 的值只能是字符串、数字、布尔值、null、数组、对象类型(可选类型只有这 6 种)
-
数组格式的 JSON 数据
- 最外层使用 [ ] 进行包裹,内部的每一项数据之间使用英文的 , 分隔。其中: 每一项的值类型只能是字符串、数字、布尔值、null、数组、对象这 6 种类型之一。
-
调用浏览器内置的 JSON.parse() 函数,可以把 JSON 格式的字符串转换为 JS 数据
-
调用浏览器内置的 JSON.stringify() 函数,可以把 JS 数据转换为 JSON 格式的字符串
序列化和反序列化:
-
把真实数据转换为字符串的过程,叫做序列化 调用 JSON**.stringify()** 函数进行 JSON 的序列化
-
把字符串转换为真实数据的过程,叫做**反序列化 ** 调用 JSON.parse() 函数进行 JSON 的反序列化
把 XMLHttpRequest 请求到的 JSON 数据反序列化为 JS 对象:
在 xhr 对象的 load 事件中,通过 xhr.response 访问到的是 JSON 格式的字符串数据。可以调用 JSON.parse() 函数将 xhr.response 转化为 JS 对象
<script>
let xhr = new XMLHttpRequest();
// 将请求参数拼接到url后面
xhr.open('请求方式', 'http://www.itcbc.com/api/xx?id=1&username=zhangsan');
xhr.send(请求体);
xhr.addEventListener('load', function () {
let res = JSON.parse(this.response);//需要将xhr.response 转化为 JS 对象
console.log(res);
})
</script>
补充 - JSON 文件:
后缀名是 .json 的文件叫做 JSON 文件,专门用来存储 JSON 格式的数据。
注意:.json 结尾的文件,一般都是项目的配置文件
三.封装自己的 Ajax 函数:
定义ajax函数的参数选项:
自定义的 Ajax 函数,它接收一个配置对象作为参数。配置对象中包含如下 5 个参数选项:
| 参数选项 | 说明 |
|---|---|
| method | 请求的类型(GET 或 POST) |
| url | 请求的 URL 地址 |
| data | 请求体数据,有三种格式,分别是(FormData 格式、JSON 格式、普通字符串格式) |
| success | 请求成功之后的回调函数 |
实现步骤:
- 创建 xhr 实例对象
- 根据用户指定的 method 和 url 发起对应的请求
- 把响应的 JSON 数据转换为 JS 对象
- 调用 success 成功的回调,把请求的结果当作参数传递进去
注意事项:
- 有get post 请求分开类型写
- data数据有三种类型 string object formdata 区分开
- post请求 xhr.setRequestHeader设置对应的请求头的 Content-Type 类型
- 当data=FormData时 instanceof 的运用 实例 instanceof 构造函数
请求通用代码如下:
<input type="file" accept="image/*">
<script>
axios({
type: 'get',
url: 'http://www.itcbc.com:3006/api/getbooks',
data: {
publisher: '黑马出版社',
appkey: 'lijiahao123'
},
success(result) {
console.log(result);
}
})
function ajax({ url, type, data = '', success }) {
const xhr = new XMLHttpRequest();
// 判断 请求类型
if (type === 'get') {
// get请求的相关的代码
if (typeof data === 'object') {
data = new URLSearchParams(data).toString();
}
xhr.open(type, url + '?' + data);
xhr.send();
} else if (type === 'post') {
// post请求的相关的代码
xhr.open(type, url);
// 判断是不是字符串
if (typeof data === 'string') {
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.send(data);
} else if (typeof data === 'object') {
// 判断是不是对象
// 判断是不是 FormData 实例
if (data instanceof FormData) {
// 是 FormData 实例
xhr.send(data);
} else {
// 普通的对象
xhr.setRequestHeader('Content-type', 'application/json');
const str = JSON.stringify(data);
xhr.send(str); // 传递 a=b&c=d
}
}
}
xhr.addEventListener('load', function () {
const obj = JSON.parse(this.response);
success(obj);
});
}
</script>
四.同源策略 & 跨域:
同源:
同源指的是两个 URL 地址具有相同的协议、主机名、端口号
例如,下表给出了相对于 www.test.com/index.html 页面的 5 个同源检测结果:
| URL | 是否同源 | 原因说明 |
|---|---|---|
| www.test.com/other.html | 是 | 同源(协议、域名、端口相同) |
| www.test.com/about.html | 否 | 协议不同(http 与 https) |
| blog.test.com/movie.html | 否 | 域名不同(www.test.com 与 blog.test.com) |
| www.test.com:7001/home.html | 否 | 端口不同(默认的 80 端口与 7001 端口) |
| www.test.com:80/main.html | 是 | 同源(协议、域名、端口相同) |
同源策略:
同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。 浏览器的同源策略规定:不允许非同源的 URL 之间进行资源的交互。
例:
- www.escook.cn/index.html 同源,接口调用成功 www.escook.cn/api/login
- www.escook.cn/index.html 非同源,接口调用失败 www.liulongbin.top/api/login
跨域:
同源指的是两个 URL 的协议、主机名、端口号完全一致,反之,则是跨域。
出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。
- 网页:www.test.com/index.html
- 接口:www.api.com/userlist
- 受到同源策略的限制,上面的网页请求下面的接口会失败!
浏览器允许发起跨域请求。但跨域请求回来的数据,会被浏览器拦截,无法被页面获取到
突破浏览器跨域限制的两种方案:
JSONP 和 CORS 是实现跨域数据请求的两种技术方案
| 方案 | 诞生的时间 | 方案来源 | 优点 | 缺点 |
|---|---|---|---|---|
| JSONP | 出现较早 | 民间(非官方) | 兼容性好(兼容低版本 IE) | 仅支持 GET 请求 |
| CORS | 出现较晚 | W3C 官方标准 | 支持 GET、POST、PUT、DELETE、PATCH等常见的请求方式 | 不兼容某些低版本浏览器 |
注意:目前 JSONP 在实际开发中很少会用到,CORS 是跨域的主流技术解决方案
CORS 的两个主要优势:
- CORS 是真正的 Ajax 请求,支持 GET、POST、DELETE、PUT、PATCH 等这些常见的 Ajax 请求方式
- 只需要后端开启 CORS 功能即可,前端的代码无须做任何改动
JSONP:
JSONP 是实现跨域数据请求的一种技术解决方案。它只支持 GET 请求,不支持 POST、DELETE 等其它请求
- 在实际开发中很少被使用
- 在面试中可能会问到 JSONP 的原理
在解决跨域问题时:
- CORS 方案用到了 XMLHttpRequest 对象,发起的是纯正的 Ajax 请求
- JSONP 方案没有用到 XMLHttpRequest 对象,因此,JSONP 不是真正的 Ajax 技术
结论:只要用到了 XMLHttpRequest 对象,发起的就是 Ajax 请求!
JSONP 的底层实现原理:
JSONP 在底层,用到了
原因:
JSONP 的底层实现原理:
-
把非同源的 JavaScript 代码请求到本地,并执行:
<body> <!-- 把非同源的 JavaScript 代码请求到本地,并执行 --> <script src="http: //www.itcbc.com:3006/api/getscript"></script> </body> -
如果请求回来的 JavaScript 代码只包含函数的调用,则需要程序员手动定义 show 方法。
<body> <script> // 1. 手动定义 show 方法 function show(data) { console.log(data) } </script> <!-- 2. 把非同源的 JavaScript 代码请求到本地,并执行 --> <script src="http: //www.itcbc.com:3006/api/getscript2"></script> </body>缺点:服务器响应回来的代码中,调用的函数名是写死的
-
在指定 时,可以通过查询参数中的 callback,自定义回调函数的名称:
<script> // 1. 手动定义 showInfo 方法 function showInfo(data) { console.log(data) } </script> <!-- 通过 callback 参数,自定义回调函数的名字 --> <script src="http: //www.itcbc.com:3006/api/jsonp?callback=showInfo"></script -
在指定 时,还可以通过查询参数的方式,指定要发送给服务器的数据:
<script> // 1. 手动定义 showInfo 方法 function showInfo(data) { console.log(data) } </script> <!-- 通过 callback 参数,自定义回调函数的名字 --> <!-- 指定 name 和 age 这两个参数 --> <script src="http: //www.itcbc.com:3006/api/jsonp?callback=showInfo&name=zs&age=20"></script>
五.防抖 & 节流:
防抖:
防抖(debounce)指的是:频繁触发某个操作时,只执行最后一次
防抖的应用场景:
场景:搜索框只在输入完后,才执行查询的请求。 好处:这样可以有效减少请求的次数,节省网络资源。
核心代码:
<script>
/* 防抖 防止抖动
1 用在输入框中 实现 不用用户按下回车键 就发送请求
2 技术原理
1 用新的一次输入来清除上一次的延时器
2 同时开启一个新的延时器 */
const input = document.querySelector('input');
input.addEventListener('input', function (event) {
clearTimeout(timeid);
// 开启了一个延时器 里面代码 1s后会执行
timeid = setTimeout(function () {
const value = input.value.trim();
const queryStr = `?bookname=${value}`;
getData(queryStr);
}, 1000);
});
</script>
节流:
节流(throttle)指的是:单位时间内,频繁触发同一个操作,只会触发 1 次。
节流的应用场景:
射击游戏中,单位时间内只能发射一颗子弹。
核心代码:
<script>
// 这一次请求还没有结束 就不能开启下一个请求
// 业务 分页 业务
// 开关
let isLoadding = false; // 有没有请求在发送当中
// 点击按钮的时候先判断 isLoadding true还是false
// true 请求在发送中 return
// false 没有请求
// 先设置 isLoadding true
// 发送请求出去
// 请求回来了 设置 isLoadding = false
document.querySelector('button').addEventListener('click', function () {
if (isLoadding) {
return;
}
isLoadding = true;
// 发送请求的时候 禁用按钮
// this.disabled=true;
getData();
});
function getData(query = '') {
console.log('请求发送出去');
axios({
method: 'get',
url: 'http://www.itcbc.com:3006/api/getbooks' + query,
// params:{},
}).then((result) => {
console.log('数据回来了');
// document.querySelector('button').disabled=false
isLoadding = false;
});
}
</script>