定义:
它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验(异步,局部更新)
应用场景
- 页面上拉加载更多数据
- 列表数据无刷新分页
- 提交表单之前验证数据合法性
- 搜索框提示文字下来列表
ajax的运行环境
运行在网站环境中才能生效(需要响应的服务器)
ajax运行原理及实现
ajax相当于浏览器发送请求与接受响应的代理人,以实现不影响用户感受的局部刷新页面
ajax的使用步骤
get
// 1.创建ajax对象
let xhr = new XMLHttpRequest();
// 2.告诉ajax请求地址和请求方式
xhr.open('get', 'http://localhost:8090');
// 3.发送请求数据
xhr.send();
// 4.获得服务器端的响应数据
xhr.onload = function () {
console.log(xhr.responseText);
}
post请求
// 1.创建ajax对象
let xhr = new XMLHttpRequest();
// 2.告诉ajax请求地址和请求方式
xhr.open('post', 'http://localhost:8090/post');
let params = {'name':'lijian'}
xhr.setRquestHeader('Conten-Type','application/json')
// 3.发送请求数据
xhr.send(JSON.stringfy(params));
// 4.获得服务器端的响应数据
xhr.onload = function () {
console.log(xhr.responseText);
}
ajax状态码
0: 请求为初始化(还没调用open())
1: 请求已经建立,但是还没发送(还没有调用send())
2: 请求已经发送
3.请求正在处理中,通常响应中已经有了部分数据
4: 响应已经完成,可以获取并使用服务器的响应了
xhr.readyState//获取ajax状态码
当状态改变后系统回自动触发onreadystatechange事件
ajax的错误处理
-
网络通畅,服务器端可以接收到请求,服务器段返回的结果不是预期结果,如此可以判断服务器端返回的状态码,分别进行处理。xhr.status获取http状态码
-
网络通畅,服务器端没有接收到请求,返回404状态码
检查请求地址是否错误
-
网络通畅,服务器端能接收到请求,返回500状态码
服务器代端程序错误
-
网络中断,请求无法发送到服务器
会触发xhr对象中的onerror事件,在实践中对错误进行处理
-
ajax在ie低版本中会存在缓存问题
ajax封装
function ajax(options) {
// 默认参数
let defaults = {
type: 'get',
url: '',
data: {},
header: {
'Content-Type': 'application/json'
},
success: () => { },
error: () => { }
}
// 使用options对象覆盖默认对象,assign方法改变原对象,不用再次赋值
Object.assign(defaults, options)
// 创建ajax对象
let xhr = new XMLHttpRequest()
let params = ''
// url拼接参数
for (key in defaults.data) {
params += `${key}=${defaults.data[key]}&`
}
// 去除最后的&
if (params.length > 0) {
params = params.substr(0, params.length - 1)
}
// 判断请求请求方法为get,则直接在url中加入?
if (defaults.type === 'get') {
defaults.url += '?' + params
}
// 设置请求方法和url
xhr.open(defaults.type, defaults.url)
if (defaults.type === 'get') {
xhr.send()
} else {
// 设置请求参数格式
let contentType = defaults.header['Content-Type']
xhr.setRequestHeader('Content-Type', contentType)
// 判断post请求的参数格式
if (contentType === 'application/json') {
// json
xhr.send(JSON.stringify(defaults.data))
} else {
// 非json
xhr.send(defaults.data)
}
}
// 请求完成
xhr.onload = function () {
// 获取响应头中的数据
let resContentType = xhr.getResponseHeader('Content-Type')
let resText = xhr.responseText
if (resContentType.includes('application/json')) {
resText = JSON.parse(resText)
}
// 当http状态码为200时,调用成功处理函数
if (xhr.status == 200) {
defaults.success(resText, xhr)
} else {
// 当http状态码为200时,调用成功处理函数
defaults.error(resText, xhr)
}
}
}
FormData
FromData构造方法可以将一个表单的dom对像,转换成fromData数据对象。表单中的name属性里面的值为属性名,输入的值为属性值
普通数据处理方法
-
get():依据属性名获得属性值
-
set(): 依据属性名设置属性值,当没有该属性名时则添加后在设置值
-
delete(): 输出表单属性中的值,(比如:注册时删除两个密码中的再次输入密码属性)
-
append(): 向表单对象中追加属性值
注意:set方法与append方法的区别是,在属性名已经存在的时候,set是直接覆盖,而append是保留两个属性值
<form id="form">
<input type="text" name="username">
<input type="password" name="password">
<input type="button" value="提交" id="submit">
</form>
<script>
let submit = document.getElementById("submit")
let form = document.getElementById("form")
submit.onclick = function () {
// 将普通的html表单转化为表单对象
let fromData = new FormData(form)
// FormData.get()依据属性名获取表单属性值
// FormData.set()设置表单属性值
let username = fromData.get('username')
fromData.set('username', username + new Date())
fromData.set('sex', '男')
let xhr = new XMLHttpRequest()
xhr.open('post', 'http://localhost:8090/formData')
xhr.send(fromData)
xhr.onload = function () {
if (xhr.status == 200) {
console.log('xhr.responseText', JSON.parse(xhr.responseText));
}
}
}
</script>
<script>
let submit = document.getElementById("submit")
let form = document.getElementById("form")
submit.onclick = function () {
// 将普通的html表单转化为表单对象
let fromData = new FormData(form)
// FormData.get()依据属性名获取表单属性值
// FormData.set()设置表单属性值
let username = fromData.get('username')
fromData.set('username', username + new Date())
fromData.set('sex', '男')
let xhr = new XMLHttpRequest()
xhr.open('post', 'http://localhost:8090/formData')
xhr.send(fromData)
xhr.onload = function () {
if (xhr.status == 200) {
console.log('xhr.responseText', JSON.parse(xhr.responseText));
}
}
}
</script>
文件上传
<!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>
<style>
.progress {
position: relative;
width: 300px;
height: 20px;
text-align: center;
background-color: gainsboro;
}
.progress-text {
position: absolute;
left: 49%;
}
.progress-bar {
width: 0;
height: 20px;
background-color: greenyellow;
}
</style>
</head>
<body>
<form id="form">
<label for="file">选择文件
<input type="file" name="file" id="file">
</label>
<div class="view" id="view_img">
</div>
<div class="progress">
<span class="progress-text" id="progress_text"></span>
<div class="progress-bar" id="progress_bar"></div>
</div>
</form>
<script>
// 获取文件元素
let file = document.getElementById('file')
// 文件选择空间,在用户选择文件时触发
file.onchange = function () {
let formData = new FormData()
// 将用户选择的文件追加在fromDatta表单中
formData.append('attrName', this.files[0])
// 创建ajax
let xhr = new XMLHttpRequest()
xhr.open('post', 'http://localhost:8090/upload')
// 在文件上传的过程中持续触发
xhr.upload.onprogress = function (ev) {
// ev.loaded:文件已经上传了多少
// ev.total:上传文件的总大小
let bar = document.getElementById('progress_bar')
// 计算出当前进度
let progress = ((ev.loaded / ev.total) * 100).toFixed(2) + '%'
// 设置进度条宽度
bar.style.width = progress
// 将百分比显示在进度条中
document.getElementById('progress_text').innerHTML = progress
}
xhr.send(formData)
// 监听服务器的响应事件,处理响应数据
xhr.onload = function () {
// 如果返回的时成功状态
if (xhr.status == 200) {
let result = xhr.responseText
//创建一个图片元素进行文件预览
let img = document.createElement('img')
img.setAttribute('src', result)
img.onload = function () {
document.getElementById('view_img').appendChild(img)
}
} else {
console.log(xhr.responseText);
}
}
}
</script>
</body>
</html>
JSONP解决跨域问题
同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介
跨域请求
当前发起请求的域与该请求指向的资源所在的域不一样
jsonp解决跨域
服务器代码:server.js
const express = require('express')
const path = require('path')
const app = express()
//app.use(express.static(path.join(__dirname, 'public')))
app.get('/jsonp', (req, res) => {
let obj = {
name: '小明',
age: '99'
}
console.log(req.query);
let result = `${req.query.callback}(${JSON.stringify(obj)})`
res.send(result)
//express框架中的jsonp方法实现了以上过程
// res.jsonp({name: '小明',age: '99'})
})
app.listen(9999)
console.log('9999端口开始监听');
客户端代码:
function jsonp(options) {
// 动态创建script标签
let script = document.createElement('script')
let url = options.url
// 处理参数
let params = ''
for (let value in options.data) {
params += `&${value}=${options.data[value]}`
}
let callback = options.callback
// 将匿名的callback函数的作用域提升到全局,并赋予一个具体变量名(随机生成)
// 为什么不能写死变量名呢?因为写死后,则后面的求情回覆盖前面的请求(window.callback会被覆盖)
let funcName = 'jsonpCallBack' + Math.random().toString().replace('.', '')
window[funcName] = callback
// 设置src属性值
script.setAttribute('src', `${url}?callback=${funcName}${params}`)
document.body.appendChild(script)
script.onload = function () {
document.body.removeChild(script)
}
}
jsonp({
url: 'http://localhost:9999/jsonp',
data: {
name: '张三',
age: 101
},
callback: function (data) {
console.log(data);
}
})
jsonp可能遇到的问题
-
问题:当使用document.body.append添加script标签时,会导致多次点击,文档中添加过多script标签
解决:script元素加载完后移除掉
-
问题:回调函数必须在全局定义域中定义,与请求调用者联系不密切
解决:将回调函数挂载在window全局对象上
-
问题:回调函数挂载在window全局变量上面后,会出现后来调用者的回调函数覆盖前面的回调函数
解决:随机生成函数名后再挂载到window对象上