AJAX
- 传统网站的弊端
应用场景
- 动态数据加载
- 表单验证
实现
原生方式
let xhr = new XMLHttpRequest();
// 发送get请求 通过url传递参数
xhr.open('get',"/home");
xhr.send();
xhr.onload = function(){
    console.log(xhr.responseText);
}
- post
xhr.open('post',"/home");
xhr.setRequestHeader('Content-Type',"application/json");
xhr.send('{"name":"jntm"}');
ajax状态码
- 0:请求未初始化(还没有调用open())
- 1:请求已经建立,但是还没有发送(还没有调用send())
- 2:请求已经发送
- 3:请求正在处理中,通常响应中已经有部分数据可以用了
- 4:响应已经完成,可以获取并使用服务器的响应了
xhr.onreadystatechange = function(){
    console.log(xhr.readyState);
}
| 区别描述 | onload事件 | onreadystatechange事件 | 
|---|---|---|
| 是否兼容IE低版本 | 不兼容 | 兼容 | 
| 是否需要判断Ajax状态码 | 不需要 | 需要 | 
| 被调用次数 | 一次 | 多次 | 
错误处理
- 后端能调通,但是返回错误
xhr.status
- 网络中断,后端无法调通
会触发xhr对象下面的onerror事件,在onerror事件处理函数中对错误进行处理
- 低版本ie存在ajax缓存问题
ajax封装
function ajax(req) {
    let xhr = new XMLHttpRequest();
    let params = "";
    for (let attr in req.data) {
        params += attr + "=" + req.data[attr] + "&";
    }
    params = params.substr(0, params.length - 1);
    xhr.open(req.type, req.type != 'get' ? req.url:req.url + "?" + params);
    for(let attr in req.headers){
        xhr.setRequestHeader(attr,req.headers[attr]);
    }
    if (req.type == 'get'){
        xhr.send();
    }else{
        if (req.headers['Content-Type']=='application/json'){
            xhr.send(JSON.stringify(req.data));
        }else{
            xhr.send(params);
        }
    }
    xhr.onload = function () {
        let contentType = xhr.getResponseHeader("Content-Type");
        let responseText = xhr.responseText;
        if (contentType.includes("application/json")){
            responseText = JSON.parse(responseText);
        }
        if (xhr.status === 200) {
            req.success && req.success(responseText);
        } else {
            req.error && req.error(responseText);
        }
    };
}
ajax({
    url: '/home',
    type: 'get',
    data: {name: "cxk", age: 18},
    headers:{
        "Content-Type":"application/json"
    },
    success: function (res) {
        console.log('normal res', res);
    },
    error: function (res) {
        console.error('error res', res);
    }
})
JQuery实现方式
- ajax
$.ajax({
    url:"./" , // 请求路径
    type:"POST" , //请求方式
    //data: "username=jack&age=23",//请求参数
    data:{"username":"jack","age":23},
    success:function (data) {
        alert(data);
    },//响应成功后的回调函数
    error:function () {
        alert("出错啦...")
    },//表示如果请求响应出现错误,会执行的回调函数
});
发送jsonp请求
$.ajax({
    url: 'http://www.example.com',
    // 指定当前发送jsonp请求
    dataType: 'jsonp',
    // 修改callback参数名称
    jsonp: 'cb',
    // 指定函数名称
    jsonCallback: 'fnName',
    success: function (response) {} 
})
- $.get
- $.post
- serialize
$("#form").serialize(); // 将表单输入的内容转换成如k=v&a=b这种形式
全局事件
.ajaxStart()     // 当请求开始发送时触发
.ajaxComplete()  // 当请求完成时触发
配合nprogress来实现页面加载进度条
FormData
- 表单上传
- 二进制文件上传
简单使用
<form id="form">
    <input type="text" name="username"/>
    <input type="password" name="password"/>
    <input type="file" name="file">
    <input type="button" id="btn"/>
</form>
let form = document.querySelector("#form");
let formData = new FormData(form);
let xhr = new XMLHttpRequest();
xhr.open('post','/formData');
xhr.send(formData);
实例方法
// 获取表单对象属性
formData.get('username');
// 设置表单对象属性
formData.set("username","cxk");
// 删除表单对象属性
formData.delete("username");
// 追加表单对象属性,属性名已存在的情况下,set会覆盖,append会保留
formData.append("username","cxk");
文件上传进度展示
xhr.upload.onprogress = function (ev) {
    console.log(ev.loaded / ev.total)
}
图片上传实时预览
var file = document.querySelector('#file');
file.onchange = function(){
    var reader = new FileReader();
    reader.readAsDataURL(this.files[0]);
    reader.onload = function(){
        document.querySelector('.img-thumbnail').src = reader.result;
    }
}
使用Promise发送ajax
function query() {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.onreadystatechange = () => {
            if (xhr.readyState != 4) {
                return;
            }
            if (xhr.readyState == 4 && xhr.status == 200) {
                resolve(xhr.responseText);
            } else {
                reject(xhr.status);
            }
        };
        xhr.open('get', '/home');
        xhr.send();
    });
}
query().then(r => console.log(r)).catch(r => console.error(r))
fetch
Fetch API是新的ajax解决方案
fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
fetch('/home').then(data=>{
    return data.text();
})
.then(data=>{
    console.log(data);
});
data是一个response对象,其中包括返回的一堆原始字节,这些字节需要在收到后,需要我们通过调用方法将其转换为相应格式的数据,比如JSON,BLOB或者TEXT等等
- 传递参数
let formData = new FormData();
formData.append('username','cxk');
formData.append('password','jntm');
fetch('/formData',{
    method:'post',
    body:formData
})
axios
axios.get('/home')
    .then(res=>{
        console.log(res);
    })
let formData = new FormData();
formData.append('username','cxk');
formData.append('password','jntm');
// 默认传递的是json
axios.post('/formData',formData)
 .then(res=>{
     console.log(res);
 })
- 同步调用
async function f(){
    let res =  await axios.post('/formData',formData);
    console.log(res);
}
全局参数
// 配置公共的请求头 
axios.defaults.baseURL = 'https://api.example.com';
// 配置 超时时间
axios.defaults.timeout = 2500;
// 配置公共的请求头
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// 配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
拦截器
// 请求拦截器
axios.interceptors.request.use(function(config) {
    console.log(config.url)
    // 这里一定要return   否则配置不成功
    return config;
}, function(err){
    // 对请求错误做点什么
    console.log(err)
})
// 响应拦截器
axios.interceptors.response.use(function(res) {
    console.log(res.data);
    return res.data;
}, function(err){
    console.log(err)
});
同源策略
ajax受同源策略限制
JSONP
原理:
客户端定义一个函数:
function fn (data) { // 接收到服务器data后的一些处理 }
服务端返回一个函数调用:
const data = 'fn({name: "张三", age: "20"})';
客户端使用script可以跨域加载数据
<script src="server_ajax_address"></script>
这样客户端就可以获取服务端的数据
function fn(data){
    console.log('server res',data);
}
let script = document.createElement('script');
script.src = '/jsonp?v=1';
document.body.appendChild(script);
@GetMapping("/jsonp")
public String jsonp(){
    return "fn({name:'cxk',age:18})";
}
优化
- 回调函数名称参数化
- 回调函数参数化- 注意回调函数覆盖问题
 
- url参数化
- 接收到回调后删除script标签
CORS
全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制
通过设置Access-Control-Allow-Origin和Access-Control-Allow-Methods响应头来允许哪些源可以使用哪些方法访问
@RestController
@CrossOrigin("*")
public class Controller {...}
withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false