Ajax

203 阅读5分钟

AJAX简介

Ajax:是异步的JS和XML、通过AJAX可以在浏览器中向服务器发送【异步请求】,最大的优势:页面无刷新获取数据

AJAX的优点:
1.可以无需刷新页面,就可以与服务器端进行通信。
2.允许你根据用户事件,来更新部分页面的内容。

AJAX的缺点:
1.没有浏览历史,不能回退。
2.存在跨域的问题。
3.SEO(搜索引擎优化)不太友好。

SEO:爬虫不能爬取AJAX请求的数据,但是可以通过很多其他手段获取到。

初始AJAX

XMLHttpRequest:AJAX的所有操作都是通过这个对象来进行的。

请求八大请求,常用的主要有:GET、POST、PUT、DELETE

点击按钮,发送请求

<button id = "btn">点我发送请求</button>
<div id = "content"></div>

<script>
    //获取按钮
    const btn = document.querySelector('#btn')
    
    //给按钮绑定监听
    btn.addEeventListener('click',()=>{
        
        //1.创建xhr实例对象(new)
        const xhr = new XMLHttpRequest()
        
        //1.5:当状态发生改变时,状态码
        xhr.onreadystatechange = ()=>{
            if(xhr.readyState === 4){
                //readyState为4时,数据完美响应
                console.log(xhr.response)
                content.innerHTML = xhr.response
            }
        }
        
        //2.指定发送请求的url、method
        xhr.open('GET','http://localhost:8080/test_get')
        
        //3.发送请求
        xhr.send()
        
    })
</script>

xhr实例对象通过open()来指定【请求方法】和【url】,然后通过send()来发送请求。

跨域问题:本地的ip地址是:127.0.0.1(本地回环地址)和【localhost】意义相同。

一般是 ip地址+端口号组成,我们的项目是:127.0.0.1:5500/src/项目.html,但是我们请求的地址是:localhost:8080,端口号不同,就跨域了。(主机名不一样也是跨域,后期我们再说)。

xhr的5种状态

xhr有5种readyState(状态),值分别为:0、1、2、3、4(最重要);

当创建请求--》收到请求,状态码会发生4次变化:0--1、1--2、2--3、3--4;

xhr=0时,表示【实例化】的那一刻。0也叫初始状态。
xhr=1时,open()已经调用了,但是send()还没有调用,此时可以修改请求头内容

//设置请求头
if(xhr.readyState === 1){
    xhr.setRequestHeader('demo',123)
}

xhr=2时,send()已经调用了,已经无法修改请求头内容
xhr=3时,已经回来一部分数据了,小的数据会在此阶段一次性接收完毕,较大的数据有待进一步接收。(响应头一定会回来的xhr.getResponseHeaders() / xhr.getResponseHeader('content-length')
xhr=4时,表示数据全部都完美的回来了。

一般在xhr.readyState()中还会附加上一个条件:http是有状态码的。

if(xhr.readyState === 4 && (xhr.status >= 200)&&(xhr.status < 300)){
    console.log(xhr.response)
}

ajax的get请求

btn.addEventListener('click',()=>{
    
    //1.创建xhr实例对象
    const xhr = new XMLHttpRequest()
    
    //1.5绑定监听
    xhr.onreadystatechange = ()=>{
        if(xhr.readyState === 4){
            if(xhr.status>=200&&xhr.status<300){
                console.log(xhr.response)
            }
        }
    }
    
    //2.设置 get请求传递参数 和 url
    xhr.open('GET','http://127.0.0.1:8080/test_get?name=老刘&age=18')
    xhr.open('GET','http://127.0.0.1:8080/test_get/老刘/18')
    
    //3.发送请求
    xhr.send()
})

GET请求有两种参数:query('...?name=tom&age=18'),params('.../老刘/18');get请求没有请求体。

query:用的是urlencoded编码格式,形如:key=value&key=value

param:形如:/xx/xxx/老刘/18

设置参数后,后端就可以请求到参数,param需要设置参数占位,来接收

image.png

ajax的post请求

btn.addEventListener('click',()=>{
    //1.实例化
    const xhr = new XMLHttpRequest()
    
    //绑定监听
    xhr.onreadystatechange = ()=>{
        if(xhr.readyState===4){
            if(xhr.status>=200&&xhr.status<300){
                console.log(response)
            }
        }
    }
    
    //post请求
    xhr.open('POST','http://127.0.0.1:8080/test_post')
    
    xhr.open('POST','http://127.0.0.1:8080/test_post?name=tom&age=18')
    xhr.open('POST','http://127.0.0.1:8080'/test_post/tom/18')
    
    //请求体参数 发送
    xhr.send()
})

POST请求可以携带:query参数params参数body参数(请求体参数)

但是query和params不是经常用,在post请求中,我们常用的是boby请求体参数!

请求体参数:写在xhr.send()中,并且以urlencoded形式,也就是key=value&key=value的形式。

或者以json格式进行传参

xhr.open('POST','http://127.0.0.1:8080/test_post')

---------------------------------------

//这个一定要写(urlencoded),追加响应头用于标识携带请求体参数的编码格式
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')

xhr.send('name=老刘&age=18')

----------------------------------------

//或者json,携带json编码形式的请求体参数
xhr.setRequestHeader('Content-type','application/json')

const person = {name:'老刘',age:18}
xhr.send(JSON.stringify(person))

请求体:

image.png

xhr.responseType = 'json':收到响应的是json格式的数据时,自动转换为JSON对象格式,不再需要手动修改。

image.png

回顾解构赋值

let obj = {a:1,b:{c:2}}

//标准的解构赋值写法
const {c} = obj.b 

//连续写法,可以直接拿到{b},但是我要c,所以要连续写法
const {b:{c}} = obj

//连续解构赋值+重命名
const {b:{c:value}} = obj
console.log(value)//2

重命名:{x:__重命名__},此时key对应的value的名字就被修改了。

请求异常与超时的处理

btn.addEventListener('click',()=>{
    const xhr = new XMLHttpRequest()
    
    xhr.onreadystatechange = function(){
        if(xhr.readyState === 4){
            if(xhr.status>=200&&xhr.status<300){
                console.log(response)
            }
        }
    }
    
    xhr.open('GET','http://127.0.0.1:8080/get_person')
    
    //responseType指定返回数据的格式
    xhr.responseType = 'JSON'
    
    //一旦请求出错,就会调用这个回调函数
    xhr.onerror =() =>{
        console.log('请求出错啦')
    }
    
    //设置超时时间,单位是:毫秒
    //我等了你2s,如果没有请求/响应,直接抛弃这个请求
    xhr.timeout = 2000
    
    xhr.ontimeout =()=>{
        cnosole.log('网速不行,请切网重试')
    }
    
    xhr.send()
    
})

xhr.onerror = ()=>{}:一旦出错,就会调用这个回调函数。
xhr.timeout:设置超时时间,单位是毫秒
xhr.ontimeout = ()=>{}:一旦超过我所设置的超时时间,就会调用这个回调函数。

取消请求

程序员设置了超时时间,但是还没有触发到超时时间,用户缺主动不想要了,这个时候就需要设置一个取消请求。

<button id = "btn2">取消请求</button>

const btn2 = document.querySelector('#btn2')

btn2.addEventListener('click',()=>{
    const xhr = new XMLHttpRequest()
    
    //用户主动取消
    xhr.abort()
    
})

通过xhr.abort()方法,来取消请求。

如果:

xhr.send()
xhr.abort()

一发送,就取消;实际上,如果你请求的速度非常快,还是有一定几率会请求到数据的,如果请求的速度比较慢,那么可能被拦截取消了。

避免多次重复请求

//请求是否处于加载中
let isLoading

btn.addEventListener('click',function(){
    //判断写在创建实例的上面,因为我第二次点击的时候,又创建了一个新的xhr对象;
    //但是我需要判定的是我本次的,而不是新创建的xhr对象
    if(isLoading){
        xhr.abort()
    }

    let xhr = new XMLHttpRequest()

    xhr.onreadystatechange = function(){
        if(xhr.readyState === 4){
            if(xhr.status>=200&&xhr.status<300){
                //这里是数据响应,设置false,说明数据已经收到响应的了
                //代表已经发送过一次请求了
                isLoading = false
            }
        }
    }
    
    xhr.open(...)
    
    xhr.send()
    //数据发送后改为true,说明请求已经发送了,数据正在加载中,等待响应
    isLoading = true
})

跨域与同源策略

为什么会有跨域这个问题?
答:因为浏览器为了自身的安全,而采用了同源策略

同源策略:
1.现在所有支持JavaScript的浏览器都会使用这个策略。
2.web是构建在同源策略的基础之上的,浏览器只是针对同源策略的一种实现
3.同源是指:【协议】、【域名IP】、【端口】必须要完全相同,这样才能算是在同一个域中。

端口号不写,就用默认的端口号(80)。

image.png

补充:<iframe>:用来内嵌别人的一个网页(现在已经不支持了,大伙看个乐子就行)

image.png

image.png

非同源受到的限制:
1.Cookie不能读取。
2.DOM无法获得。
3.Ajax请求不能获取数据(请求能发过去,但是数据回不来)。

jsonp解决跨域

开发基本不用,但是面试老是问你。

JSONP是一个非官方的跨域解决方案,纯粹凭借程序员的聪明才智开发出来的,【只支持get请求】!!!

JSONP是怎么工作的?
1.在网页中,有一些标签、天生就具有跨域的能力,比如:img、link、iframe、script等。
2.JSONP就是利用了script标签发送请求,不受同源策略的限制特点。
3.同源策略只是限制了xhr、Cookie、DOM,所以标签可以跨域

哪些跨域发get请求:form表单、xhr、浏览器地址栏、<img src = '???'><script>;这些常见的带有外部html的发的都是get请求。

<button id = "btn">点我发送数据</button>

const btn = document.querySelector('#btn')
btn.addEventListener('click',()=>{
    
})

CORS后端解决跨域

官方解决跨域的方案,他的特点是:不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持所有常见的特点。

本质是:5500端口向8080端口发送ajax请求数据,8000端口收到请求后,附加上两个
响应头【Access-Control-Allow-Orgin】
响应头【Access-Control-Expose-Headers】,这个用来拿到返回的响应头,否则响应头在前端,就取不到了
写法:response.setHeader('响应头','响应给的ip地址')

这些工作都是后端做的,让5500端口可以访问

app.get('/test_get',(request,response)=>{
  response.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500')
  response.setHeader('Access-Control-Expose-Headers','http://127.0.0.1:5500')
  response.send('hello_test_get')
})

上方仅仅适用于getpost;对于putdelete这种繁琐的请求来说,不管用。

因为put、delete会先发送一次(嗅探请求)域请求(我先扫一遍代码看看有没有问题),然后才会发送一次正常请求。

解决:要多加一条options('put请求路径','put的回调'})

//域请求方式,路径要和put请求保持一致
app.options('/test_put',(request,response)=>{
    response.setHeader('Access-Control-Allow-Origin','*')
    response.setHeader('Access-Control-Expose-Headers','*'))
    //允许所有请求方式的请求头
    response.setHeader('Access-Control-Allow-Methods','*')
}

---------------------------------------------

app.put('/test_put',(request,response)=>{
    response.setHeader('Access-Control-Allow-Origin','*')
    response.setHeader('Access-Control-Expose-Headers','*')
})

前端:

xhr.open('PUT','http://localhost:8080/test_put')
xhr.send()

如果你学了node.js,可以通过yarn add cors下载一个node中专门用来解决跨域的库

const cors = require('cors')

app.use(cors())

//告别手动书写响应头,并且不用书写options域请求
app.put('/test_put',(request,response)=>{

    //不用再写了,会自动添加上
    //response.setHeader('')
    
    response.send('hello_test_put')
})