一、概述
1、AJAX 简介
AJAX 全称为 Asynchronous JavaScript And XML,就是异步的 JS 和 XML。
通过 AJAX 可以在浏览器中向服务器发送异步请求,最大的优势:无刷新获取数据。
AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式。
2、XML 和 JOSN 格式数据
2.1 XML
概念:XML是可扩展标记语言,被设计用来传输和存储数据。
XML 与 HTML:HTML 中都是预定义标签,而 XML 中没有预定义标签, 全都是自定义标签,用来表示一些数据。
<student>
<name>孙悟空</name>
<age>18</age>
<gender>男</gender>
</student>
2.2 JOSN
{"name":"孙悟空","age":18,"gender":"男"}
3、AJAX 的特点
3.1 AJAX 的优点
(1)可以无需刷新页面而与服务器端进行通信。
(2)允许你根据用户事件来更新部分页面内容。
3.2 AJAX 的缺点
(1)没有浏览历史,不能回退.
(2)存在跨域问题 ( 同源 ) 。
(3)SEO 不友好(爬虫)。
4、AJAX 的使用
4.1 核心对象
XMLHttpRequest,AJAX 的所有操作都是通过该对象进行的。
4.2 使用步骤
使用步骤:
(1)创建 XMLHttpRequest 对象。
(2)设置请求信息(可以设置请求头,看具体需要)。
(3)发送请求。
(4)接收响应。
示例:
<script> const btn = document.querySelector('button'); const content = document.querySelector('.content'); // 4. 接收响应 btn.addEventListener('click', () => { // 1. 创建 XMLHttpRequest 对象 const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) { content.innerHTML = xhr.response; } }) // 2. 设置请求信息 xhr.open('POST', 'http://127.0.0.1:8080/test_post'); // POST 请求也可以使用query 和 params 参数 // ps: 设置请求头,主要适用于当数据类型是 urlencoded 或者 json 时。 xhr.setRequestHeader('Content-type', 'application/json') // 3. 发送请求 xhr.send(); }) </script>
二、AJAX 案例( GET / POST )
1、搭建测试 ajax 请求服务器(server.js)
// 1. 导入 express 模块
const express = require('express');
// 2. 创建 web 服务器
const app = express();
// 暴露静态资源
app.use(express.static(__dirname + '/src'))
// 3. 调用 app.listen(端口号,启动成功后的回调函数) 启动服务器
app.listen(8080, (err) => {
if (!err) {
console.log(`http://127.0.0.1:8080/01_4种状态.html`);
}
})
2、使用 ajax 创建简单的 html 页面(xxx.html)
<script>
const btn = document.querySelector('button');
const content = document.querySelector('.content');
// 给按钮绑定监听
btn.addEventListener('click', () => {
// 1. 创建 XMLHttpRequest 实例对象
const xhr = new XMLHttpRequest();
// 2. 指定发送请求的:method (GET / POST / ...) 、URL
xhr.open('POST', 'http://127.0.0.1:8080/test_post'); // POST 请求也可以使用query 和 params 参数
// 3. 发送请求
xhr.send();
})
</script>
xhr 的 4 种状态:
0:实例出来的那一刻状态就是0,初始状态。
1:open 已经调用了,但是send 还没有调用,此时可以修改请求头内容。
2:send 已经调用了,已经无法修改请求头。
3:已经回来一部分数据,小数据会在此阶段一次性接收完毕,较大的数据有待进一步接收,响应头回来了。
4:数据全部接收完毕。
3、GET / POST
3.1 GET
(1)在 server.js 中增加 GET 监听。
监听 GET 请求:
(1)参数1:客户端请求的 URL 地址。
(2)参数2:请求对应的处理函数
① req:请求对象(包含了于请求相关的属性与方法)。
② res:响应对象(包含了与响应相关的属性和方法)。
如果 res.send() 中发送的是 json 格式的数据,可以在 xxx.html ==> xhr.open() 后面增加【xhr.responseType = 'json'】 来自动解析 json 格式数据。
app.get('/test_get', (req, res) => {
console.log('hello, get!');
// 接收 query 参数
console.log(req.query);
res.send(`--- hello, get! ---`)
})
(2)在 xxx.html 中设置 GET 请求。
GET 没有请求体,可以携带 query 和 params 参数。
query:http://127.0.0.1:8080/test_get?name=zs&age=20
params:http://127.0.0.1:8080/test_get/zs/20
// GET 请求
xhr.open('GET', 'http://127.0.0.1:8080/test_get?name=zs&age=20'); // query 参数
xhr.open('GET', 'http://127.0.0.1:8080/test_get/zs/20'); // params 参数
3.2 POST
(1)在 server.js 中增加 POST 监听。
监听 POST 请求:
(1)参数1:客户端请求的 URL 地址。
(2)参数2:请求对应的处理函数
① req:请求对象(包含了于请求相关的属性与方法)。
② res:响应对象(包含了与响应相关的属性和方法)。
app.post('/test_post', (req, res) => {
console.log('hello, post!');
// 接收 body 参数
console.log(req.body);
res.send(`--- hello, post! ---`)
})
(2)在 xxx.html 中设置 POST 请求。
POST 有请求体,一共有三种参数:query、 params、body(一般用body)。
body 有两种编码格式:urlencoded、json。
// POST 请求
xhr.open('POST', 'http://127.0.0.1:8080/test_post');
3.3 解析 POST 请求体中 urlencoded、json 格式的数据
(1)在 server.js 中使用中间件(中间件是 nodejs 里的东东)。
// 使用中间件解析 urlencoded 编码形式的请求体参数
app.use(express.urlencoded({ extended: true }));
// 使用中间件解析 json 编码形式的请求体参数
app.use(express.json());
(2)在 xxx.html 中追加请求头
// 追加响应头,用于标识携带请求体参数的编码形式 -- urlencoded
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
// 追加响应头,用于标识携带请求体参数的编码形式 -- json
xhr.setRequestHeader('Content-type', 'application/json')
3.4 完整 code
server.js
// 1. 导入 express 模块
const express = require('express');
// 2. 创建 web 服务器
const app = express();
app.use(express.static(__dirname + '/src'))
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.get('/test_get', (req, res) => {
console.log('hello, get!');
console.log(req.query);
res.send(`--- hello, get! ---`)
})
app.post('/test_post', (req, res) => {
console.log('hello, post!');
console.log(req.body);
res.send(`--- hello, post! ---`)
})
// 3. 调用 app.listen(端口号,启动成功后的回调函数) 启动服务器
app.listen(8080, (err) => {
if (!err) {
console.log(`http://127.0.0.1:8080/01_4种状态.html`);
}
})
xxx.html
<body>
<button>点我发送请求 ( 原生 js-ajax-get ) </button>
<div class="content"></div>
<script>
const btn = document.querySelector('button');
const content = document.querySelector('.content');
// 4. 接收响应
btn.addEventListener('click', () => {
// 1. 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', () => {
if (xhr.readyState === 4) {
content.innerHTML = xhr.response;
}
})
// GET 请求
// xhr.open('GET', 'http://127.0.0.1:8080/test_get?name=zs&age=20'); // query 参数
// xhr.open('GET', 'http://127.0.0.1:8080/test_get/zs/20'); // params 参数
// POST 请求
// 2. 设置请求信息
xhr.open('POST', 'http://127.0.0.1:8080/test_post'); // POST 请求也可以使用query 和 params 参数
// ps: 设置请求头,主要适用于当数据类型是 urlencoded 或者 json 时。
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhr.setRequestHeader('Content-type', 'application/json')
// 3. 发送请求
xhr.send();
})
</script>
</body>
三、缓存问题、请求异常与超时(了解)
1、解决 IE 浏览器 GET 缓存的问题
问题:IE 浏览器存在强缓存机制,ajax 只会发送的第一次请求,剩余多次请求不会在发送给浏览器而是直接加载缓存中的数据。(简单的说,就是当我第二次发送请求时,浏览器发现地址没有变,并没有给服务器发送请求,直接显示第一次请求的信息,这就导致在相同地址下,我发送了不同的数据但是没有进行更改。)
解决方案:浏览器的缓存是根据 url 地址来记录的,所以我们只需要修改 url 地址即可避免缓存问题。
// 增加时间戳保证 URL 地址不同
xhr.open("get","/testAJAX?t="+Date.now());
2、请求异常与超时的处理
// 配置出错的回调
xhr.onerror = () => {
console.log('当前网络不稳定,请稍后重试');
}
// 设定超时时间 ( 2000 就是 2ms )
xhr.timeout = 2000
// 设置超时的回调
xhr.ontimeout = () => {
console.log('网速不给力,请稍后重试');
}
四、取消请求、避免多次重复请求
1、取消请求
// 语法
xhr.abort()
思考:取消请求之前这个请求有没有到达服务器?
答:存在两种可能:① 还没有发送到服务器就被取消了。② 已经发送到服务器并且返回了信息,但是不要了。
2、避免多次重复请求
基本思想:在创建下一个 xhr 实例之前先判断一下目前的 xhr 是不是正在加载中。如果是,则取消之前的请求,创建新的 xhr 实例。
<script type="text/javascript" >
const btn = document.getElementById('btn')
const content = document.getElementById('content')
let xhr
let isLoading
btn.onclick = ()=>{
if(isLoading) xhr.abort()
//实例 xhr
xhr = new XMLHttpRequest()
//绑定监听
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status <300){
isLoading = false
}
}
}
//配置请求
xhr.open('GET','http://127.0.0.1:8080/get_person_delay')
//发送请求
xhr.send()
isLoading = true
}
</script>
五、跨域与同源问题(重要!)
1、概述
1.1 为什么会有跨域的问题?
原因是浏览器为了安全,而采用的同源策略 ( Same origin policy )。
1.2 什么是同源策略?
(1)同源策略是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略。
(2)Web 是构建在同源策略基础智商的,浏览器只是针对同源策略的一种实现。
(3)同源:协议、域名(IP)、端口必须要完全相同,即协议、域名(IP)、端口都相同,才算是在同一个域里。
请求地址 | 形式 | 结果 |
http://study.cn/test/a.html | 协议、域名、端口都相同 | 成功 |
http://a.study.cn/test/a.html | 域名不同 | 失败 |
http://study.cn:8080/test/a.html | 端口不同 | 失败 |
https://study.cn/test/a.html | 协议不同 | 失败 |
1.3 非同源(跨域)受到哪些限制?
(1)Cookie 不能读取。
(2)DOM 无法获得。
(3)AJAX 请求不能获取数据。【注意:是不能获取数据,并不是不能发送请求】
2、JSONP 解决跨域问题
2.1 JSONP 是什么?
JSONP(JSON with Padding),是一个非官方的跨域解决方案,纯粹凭借程序员的聪明 才智开发出来,只支持 get 请求。
2.2 JSONP 是怎么工作的?
在网页有一些标签天生具有跨域能力,比如:img link iframe script。
JSONP 就是利用 script 标签的跨域能力来发送请求的。
2.3 JSONP 的实现
// xxx.html
<body>
<button>获取数据</button>
<script>
function demo(params) {
console.log(params);
}
const btn = document.querySelector('button');
btn.onclick = () => {
// 1. 动态的创建一个 script 标签
const scriptNode = document.createElement('script');
// 2. 设置 script 的 src,设置回调函数(将函数名通过参数的形式动态传过去)
scriptNode.src = 'http://127.0.0.1:8080/test_jsonp?callback=demo';
// 3. 将 script 添加到 body 中
document.body.appendChild(scriptNode);
document.body.removeChild(scriptNode);
}
</script>
</body>
// server.js
app.get('/test_jsonp', (req, res) => {
console.log('收到请求!');
const { callback } = req.query
const person = { name: 'zs', age: 20 }
res.send(`${callback}(${JSON.stringify(person)})`)
})
注意点:
模板字符串里的内容要是字符串,直接传 person 对象默认使用 toString() ,最终得到结果是 demo([Object Object]),这明显是错误的。要使得对象能够变成字符串并且还能获取到其中的内容,就需要用到JSON.stringify()。
res.send(`${callback}(${JSON.stringify(person)})`)
2.4 JSONP 面试题
(1)jsonp 是如何解决跨域问题的?
答:jsonp 解决跨域问题的原理就是绕开 xhr,借助 script 标签的跨域能力来发送请求。
(2)json 和 jsonp 有关系吗?
答:有一定的关系,但是两者是两回事。json 是一种存储和交互数据的格式,jsonp 是后端解决跨域问题的一种方式。
关系:jsonp 后端返回数据的时候必须得是字符串的形式。
(3)jsonp 解决跨域问题的时候也是用到了 xhr,对吗?
答:不对,jsonp 绕开了 xhr。
(4)jsonp 解决跨域存在什么优缺点?
3、CORS 解决跨域问题
3.1 CORS 是什么?
CORS(Cross-Origin Resource Sharing),跨域资源共享。CORS 是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持 get 和 post 请求。跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些 源站通过浏览器有权限访问哪些资源。
3.2 CORS 是怎么工作的?
CORS 是通过设置响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应以后就会对响应放行。
3.3 利用响应头实现 CORS
3.3.1 简单请求 ( GET / POST )
app.get('/test_cors_get', (req, res) => {
// res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
res.setHeader('Access-Control-Allow-Origin', '*');
// Access-Control-Expose-Headers 列出了哪些首部可以作为响应的一部分暴露给外部
res.setHeader('Access-Control-Expose-Headers', '*');
res.send('hello_cors_get!');
})
3.3.2 复杂请求 ( PUT / DELETE / ... )
复杂请求向服务器发送了两次请求:预检请求、实际请求。
若不处理预检请求,则第一次请求失败,依旧会报跨域错误:
处理预检请求后:
预检请求:
在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求,以检测实际请求是否可以被服务器所接受。预检请求报文中的 Access-Control-Request-Method 首部字段告知服务器实际请求所使用的 HTTP 方法;Access-Control-Request-Headers 首部字段告知服务器实际请求所携带的自定义首部字段。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求。
app.options('/test_cors_put', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Expose-Headers', '*');
// Access-Control-Allow-Methods 在对 preflight request.(预检请求)的应答中
// 明确了客户端所要访问的资源允许使用的方法或方法列表
res.setHeader('Access-Control-Allow-Methods', '*');
res.send();
})
app.put('/test_cors_put', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Expose-Headers', '*');
res.send('hello_cors_put!');
})
3.4 利用中间件实现 CORS
(1)先下载 cors,我用的 npm。
npm install cors
(2)按照之前的基本结构正常的发送请求信息即可。
// server.js
// 1. 导入 express 模块
const express = require('express');
// 1. 导入 cors 模块
const cors = require('cors');
// 2. 创建 web 服务器
const app = express();
app.use(express.static(__dirname + '/src'))
// 3. 全局生效的中间件
app.use(cors())
// 4. 匹配需要的请求
app.get('/test_cors_get', (req, res) => {
res.send('hello_cors_get!');
})
app.put('/test_cors_put', (req, res) => {
res.send('hello_cors_put!');
})