Ajax学习笔记

一、AJAX

AJAX(Asynchronous JavaScript and XML)即异步的 JS 和 XML

通过 AJAX 可在浏览器中向服务器发送异步请求,最大的优势:无刷新获取数据

AJAX 不是编程语言,而是一种将现有的标准组合在一起使用的新方式

特点:可在不刷新网页的情况下向后端发送 http 请求,得到 http 响应,实现懒加载效果(用则加载不用则步加载)提高网页加载速度

二、XML

XML()可扩展标记语言

XML 被设计用来传输(如服务器以 XML 形式返回数据给客户端)和存储数据(如可用来保存一些用户数据、订单数据、商品数据等)

<student>
    <name>xxx</name>
    <age>12</age>
    <gender>男</gender>
</student>

最初 AJAX 在数据交换时使用的是 XML,现在一般使用 JSON 格式

XML 和 HTML 区别

XML 和 HTML 类似都是由标签组成

不同的是 HTML 中都是预定义标签,而 XML 中没有预定义标签,全都是自定义标签,用来表示数据

XML 被设计用来传输和存储数据,而 HTML 用来呈现数据

AJAX 优缺点

优点

(1)可无需刷新页面与服务器端进行通信(这是创造 AJAX 的初衷)

(2)允许根据用户事件(如鼠标事件、键盘事件、表单事件、文档事件等)来更新部分页面内容

缺点

(1)没有浏览历史,不能回退

(2)存在跨域问题(同源)

(3)SEO 不友好,即源码(即响应体)第一次向服务器请求的数据中没有某元素,然后通过 AJAX 向服务端发请求后通过 JS 动态创建元素到页面中,则爬虫爬不到该元素

三、HTTP 协议

HTTP 协议(hypertext transport protocol)即超文本传输协议,协议详细规定了浏览器和万维网服务器之间互相通信的规则

请求报文

请求报文包括四部分:请求行、请求头、空行、请求体

请求行:请求类型(如 GET/POST/…)、URL、HTTP 协议版本(如 HTTP/1.1)

请求头:Host、Cookie、Content-type、User-Agent

当请求类型为 GET 时请求体为空,为 POST 时请求体可不为空

行  POST /bv?p=123 HTTP/1.1
头  Host: xxx.com
    Cookie: name=xxx
    Content-type: application/x-www-form-urlencoded
    User-Agent: chrome 83
空行
体  username=admin&password=admin

响应报文

响应报文包括四部分:行、头、空行、体

响应行:HTTP 协议版本(如 HTTP/1.1)、响应状态码(如 200、301、302、303(301 - 303 重定向到服务器)、304(重定向到缓存)、404 找不到页面、403 Forbidden、401 未授权、500 服务器内部错误等,2xx都表示成功)、响应状态字符串(如 OK,与状态码对应)

响应头(对响应体作描述):Content-type、Content-length、Content-encoding

行  HTTP/1.1 200 OK
头  Content-type: text/html;charset=utf-8  表示请求体内容的类型
    Content-length: 2048
    Content-encoding: gzip
    ...
空行
体  <html>
        <head></head>
        <body>
            <h1>xxx</h1>
        </body>
    </html>

四、服务端的准备

AJAX 需要向服务端发请求,所以需要准备服务端环境,安装 Node.js、使用 Express 框架搭建后端

1、Node.js

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,是一个可以解析 js 代码的应用程序,通过 js 代码对计算机资源做一些操作

因为 AJAX 的应用当中需要一个服务端,所以需要安装 Node.js

2、Express 框架的简单使用

Express 是基于 Node.js 平台的 Web 开发框架

在终端

//做初始化,npm 是 Node.js 平台下的包管理工具
npm init --yes  
//安装 express
npm i express

js 代码

//引入 express
const express = require('express');
//创建应用对象
const app = express();
//创建路由规则
//当请求头中的 URL 匹配时会执行下方函数中的回调函数
app.get('/请求头第二个字段URL',(request,response)=>{  //参数request是对请求报文的封装,response是对响应报文的封装
    response.setHeader('Access-Control-Allow-Origin','*');  //设置响应头,设置允许跨域
    response.send('xxx');  //设置响应体
});
//监听端口启动服务
app.listen(8000,()=>{
    console.log("服务已启动,正在监听 8000 端口")
})

在终端启动服务

node 文件名.js

当 js 文件内容发生变化时需重新启动服务

3、nodemon 自动重启服务工具

当 js 文件改变时 nodemon 可启动重启 node 应用

在终端安装

npm install -g nodemon

安装完 nodemon 后在启动服务需使用

nodemon 文件名.js

五、原生 AJAX

1、基本使用

步骤:

— 1、创建对象,new XMLHttpRequest()

— 2、初始化,xhr.open('xxx','xxx') 设置请求方法和 url,url 中设置参数 ?参数1=值&参数2=值&参数n=值

— 3、发送,xhr.send()

— 4、事件绑定,xhr.onreadystatechange 处理服务端返回的结果

其中 readyState 是 xhr 对象中的属性,表示状态,有 5 个值:

0(未初始化)
1(open方法已调用完毕)
2(send方法已调用完毕)
3(服务端返回了部分结果)
4(服务端返回了所有结果)

例子(前端部分)

const xhr = new XMLHttpRequest();  //创建对象
xhr.open('GET','http://127.0.0.1:8000/server?a=1&b=2&c=3'); //初始化
xhr.send();  //发送
xhr.onreadystatechange = function(){  //事件绑定,会触发四次,0-1,1-2,2-3,3-4
    if(xhr.readyState === 4){  //服务端返回了所有结果
        if(xhr.status >= 200 && xhr.status < 300){  //判断响应状态码
            //处理结果--行 头 空行 体
            console.log(xhr.status);  //状态码
            console.log(xhr.statusText);  //状态字符串
            console.log(xhr.getAllResponseHeaders());  //所有响应头
            console.log(xhr.response);  //响应体

        }else{}
    }
}

2、发送 POST 请求

无参数的 post 请求与 get 类似,参考上面的例子,同时后端也需有相应路径下的 post 方法

post 中设置请求体

const xhr = new XMLHttpRequest(); 
xhr.open('POST','http://127.0.0.1:8000/server');
xhr.send('a=1&b=2&c=3');  //发送,设置请求体,请求体的内容可以任意
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
            //处理结果
        }else{}
    }
}

post 中设置请求头

一般会把身份校验信息放在头信息中

const xhr = new XMLHttpRequest(); 
xhr.open('POST','http://127.0.0.1:8000/server');
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');  //设置请求头
xhr.send('a=1&b=2&c=3');  //发送,设置请求体,请求体的内容可以任意
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
            //处理结果
        }else{}
    }
}

若使用自定义请求头名,此时发送完 post 请求后还会再发送一个 options 请求,需在后端设置

const express = require('express');
const app = express();   //创建应用对象
app.all('/server',(request,response)=>{  //设置 all 方法可接收任意类型的请求
    response.setHeader('Access-Control-Allow-Origin','*');  //设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Headers','*');  //设置使所有类型头信息都能接受,从而允许那些自定义的头信息
    response.send('xxx');  //设置响应体
});
//监听端口启动服务
app.listen(8000,()=>{
    console.log("服务已启动,正在监听 8000 端口")
})

3、传输 JSON 数据

后端

const express = require('express');
const app = express();  //创建应用对象
//创建路由规则
app.get('/请求头第二个字段URL',(request,response)=>{
    response.setHeader('Access-Control-Allow-Origin','*');  //设置响应头,设置允许跨域
    const data = {
        name:'xxx'
    }
    let str = JSON.stringfy(data);  //将对象进行字符串转换
    response.send(str);  //设置响应体
});
//监听端口启动服务
app.listen(8000,()=>{
    console.log("服务已启动,正在监听 8000 端口")
})

前端方式一:手动对数据转化

const xhr = new XMLHttpRequest(); 
xhr.open('POST','http://127.0.0.1:8000/server');
xhr.send();
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
            //方式一:手动对数据转化
            let data = JSON.parse(xhr.response);
        }else{}
    }
}

前端方式一:(自动转换)设置响应体数据类型

const xhr = new XMLHttpRequest(); 
//方式二:设置响应体数据类型
xhr.responseType = 'json';
xhr.open('POST','http://127.0.0.1:8000/server');
xhr.send();
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
            console.log(xhr.response)
        }else{}
    }
}

4、IE 缓存问题

IE 浏览器会缓存 AJAX 的请求结果,下次发相同请求时使用的是本地的缓存结果而不是服务器返回的最新数据,不利于一些时效性要求比较高的场景

解决:

在前端中设置 url 时添加一个时间戳参数来表示每次发送的是不同请求

const xhr = new XMLHttpRequest(); 
xhr.open('POST','http://127.0.0.1:8000/server?t='+Date.now());  //通过 url 解决 IE 缓存问题
xhr.send();
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
            console.log(xhr.response)
        }else{}
    }
}

5、AJAX 请求超时和网络异常处理

添加请求超时及网络异常处理,在前端添加

const xhr = new XMLHttpRequest(); 
//设置超过 2s 为超时,则取消请求
xhr.timeout = 2000;
//超时回调
xhr.ontimeout = function(){
    alert("网络异常,请稍后重试");
}
//网络异常回调
xhr.onerror = function(){
    alert("网络出问题了");
}
xhr.open('POST','http://127.0.0.1:8000/server);
xhr.send();
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
            console.log(xhr.response)
        }else{}
    }
}

6、AJAX 取消请求

通过 xhr 对象的 abort 方法来取消请求 xhr.abort()(xhr 是XMLHttpRequest 对象实例)

7、AJAX 请求重复发送问题

当服务器响应较慢时,用户可能多次重复发送相同请求,会增加服务器的压力,因此当多次发相同请求时可把前面未响应完成的请求取消

在前端添加一个标识变量来表示是否正在发送请求

let xhr = null;
let isSending = false;  //是否正在发送请求
if(isSending){  //若正在发送请求,则取消该请求
    xhr.abort();
}
xhr = new XMLHttpRequest(); 
isSending = true;
xhr.open('GET','http://127.0.0.1:8000/server);
xhr.send();
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        isSending = false;
    }
}

六、jQuery 中使用 AJAX

get 请求

$get(url,[请求携带的参数],[载入成功时回调函数],[返回内容格式])

其中返回内容格式可以是 xml、html、script、json、text、_default

$.get('http://127.0.0.1:8000/server',{a:1,b:2},function(data){
    console.log(data)
},'json')

若使用 CDN 引入jQuery,在发送请求时出现警告 A cookie associated with a cross-site resource at http://bootcss.com/ was set without the ‘SameSite’ attibute….

可在引入时添加跨源请求的属性设置 crossorigin="anonymous",添加后向对于 src 资源请求时不会带当前域名下的 cookie

<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

post 请求

$post(url,[请求携带的参数],[载入成功时回调函数],[返回内容格式])

$.post('http://127.0.0.1:8000/server',{a:1,b:2},function(data){
    console.log(data)
},'json')

通用发送请求的方法

前端

$.ajax({
    url: 'http://127.0.0.1:8000/server',
    data: {a:1,b:2},   //参数
    type: 'GET',       //请求类型
    dataType: 'json',  //响应体结果类型,这样可直接解析json字符串
    success: function(data){  //成功的回调
        console.log(data);
    },
    timeout: 2000,  //设置超时时间
    error: function(){  //失败的回调,包括超时、异常等
        console.log('错误');
    },
    headers: {
        c:3,
        d:4
    }
})

后端

const express = require('express');
const app = express();
app.all('/server',(request,response)=>{
    response.setHeader('Access-Control-Allow-Origin','*');  //设置允许跨域
    response.setHeader('Access-Control-Allow-Headers','*');  //设置使所有类型头信息都能接受,从而允许那些自定义的头信息
    response.send('xxx');  //设置响应体
});
//监听端口启动服务
app.listen(8000,()=>{
    console.log("服务已启动,正在监听 8000 端口")
})

七、axios

axios 是目前最热门的 AJAX 工具库,也是 Vue 和 React 推荐的 AJAX 工具包

可在项目中使用 npm install axios 安装或使用 <script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script> 引入并使用

get 请求

const axios = require('axios');  //通过 npm 方式安装需先引入 axios

axios.defaults.baseURL = 'http://127.0.0.1:8000'; //配置 baseURL,也可不配置,都写在请求的 url 里
axios.get('/server',{
    params: {  //url 参数
        a:1,
        b:2
    },
    headers: {   //请求头信息
        c:3,
        d:4
    }
}).then(value => {  //数据返回和处理和 jQuery 不同,jQuery 使用回调函数,而 axios 是基于 promise
    console.log(value)
})

post 请求

axios.post(url,{请求体},{其他配置})

axios.defaults.baseURL = 'http://127.0.0.1:8000'; //配置 baseURL,也可不配置,都写在请求的 url 里
axios.get('/server',{
    e:5,       //请求体
    f:6
},{
    params: {  //url 参数
        a:1,
        b:2
    },
    headers: {   //请求头信息
        c:3,
        d:4
    }
}).then(value => {  //数据返回和处理和 jQuery 不同,jQuery 使用回调函数,而 axios 是基于 promise
    console.log(value)
})

通用发送请求的方法

axios({
    method: 'POST',
    url: 'http://127.0.0.1:8000/server',
    params: {    //url 参数
        a:1,
        b:2
    },
    headers: {   //请求头信息
        c:3,
        d:4
    },
    data: {       //请求体
        e:5,
        f:6
    }    
}).then(response => {
    console.log(response)
})

八、使用 fetch 函数发送请求

fetch 函数属于全局对象,返回一个 promise 对象,这个 promise 会在请求响应后被 resolve,并传回 Response 对象

fetch('http://127.0.0.1:8000/server?c=1',{
    method: 'POST',
    headers: {   //请求头信息
        c:3,
        d:4
    },
    body: 'e=5&f=6'  //请求体,请求体可以是 Blob、BufferSource、Formdata、URLSearchParam 对象或字符串
}).then(response => {
    return response.text(); //获取响应体结果,不能直接看到响应体结果,需使用 return,若返回的是 json,则使用 return response.json()
}).then(response => {
    console.log(response)
})

九、跨域

1、同源策略

同源策略(Same-Origin Policy)最早由网景公司提出,是浏览器的一种安全策略

同源即当前网页的 URL 和 AJAX 请求的目标资源的 URL 的协议、域名、端口号完全相同

若违背同源策略就是跨域

AJAX 默认是遵循同源策略的,若不满足同源策略则无法直接发送 AJAX 请求

在同源情况下的请求中 url 可不用加协议、域名、端口(浏览器会自动加上),可简写为 /xxx

2、跨域的解决方案

(1)JSONP

JSONP(JSON with Padding)是一个非官方的跨域解决方案,只支持 get 请求

JSONP 如何工作

在网页有一些标签天生具有跨域能力,如 img、link、iframe、script

JSONP 就是利用 script 标签的跨域能力来发送请求的,script 标签发送的是普通的 http 请求而非 ajax 请求,因此可以跨域

JSONP 的返回结果是一个定义好的函数的调用(即 js 代码,不能是字符串等数据),这样浏览器引擎才能解析并执行,若是自定义函数,需在前端代码中定义好,函数中的参数一般放要返回的数据内容

前端

<script>
    function handle(data){
        console.log(data.a)
    }
</script>
<script src="http://127.0.0.1:8000/server"></script>

后端

const express = require('express');
const app = express();
app.all('/server',(request,response)=>{
    const data = {
        a:1
    }
    response.send('console.log('xxx')');
    //或 response.end('handle(${str})'); //end 和 send 差不多,但 end 方法不会加特殊响应头
});
//监听端口启动服务
app.listen(8000,()=>{
    console.log("服务已启动,正在监听 8000 端口")
})

JSONP 的使用

  1. 动态创建一个 script 标签

    var script = document.createElement(“script”);

  2. 设置 script 的 src,设置回调函数

    script.src = ‘http://127.0.0.1:8000/sever';

  3. 将 script 插入文档中

    document.body.appendChild(script);

jQuery 发送 JSONP 请求

需在 url 参数中设置 ?callback=?,发送请求时 jQuery 会注册一个函数作为 callback 参数的值,后端返回该函数的调用则前端就可以对数据做处理

前端

$.getJSON('http://127.0.0.1:8000/sever?callback=?',function(data){
    console.log(data)
})

后端

const express = require('express');
const app = express();
app.all('/server',(request,response)=>{
    const data = {
        a:1
    }
    let str = JSON.stringify(data);
    let cb = request.query.callback;
    response.end('${cb}(${str})')
});
//监听端口启动服务
app.listen(8000,()=>{
    console.log("服务已启动,正在监听 8000 端口")
})

(2)CORS

CORS(Cross-Origin Resource Sharing)跨域资源共享,CORS 是官方的跨域解决方案

CORS 的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持 get 和 post 等请求。跨域资源共享标准新增了一组 HTTP 首部字段(响应头),允许服务器声明哪些源站通过浏览器有权限访问哪些资源

CORS 如何工作

CORS 是通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应后会对该响应放行

后端

const express = require('express');
const app = express();
app.all('/server',(request,response)=>{
    response.setHeader('Access-Control-Allow-Origin','*');  //设置允许跨域
    //或 response.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5000');  //仅允许 http://127.0.0.1:5000 的网页可向该服务器发送跨域请求
    response.setHeader('Access-Control-Allow-Headers','*');  //可接收自定义头信息
    response.setHeader('Access-Control-Allow-Method','*');  //允许 get、post 等任意方法
    response.send('xxx');  //设置响应体
});
//监听端口启动服务
app.listen(8000,()=>{
    console.log("服务已启动,正在监听 8000 端口")
})