AJAX基础学习

Ajax学习记录

该文章仅作为笔者的学习笔记。

参考资料

尚硅谷

AJAX 教程 | 菜鸟教程 (runoob.com)

AJAX 简介 (w3school.com.cn)

AJAX – JavaScript 标准参考教程(alpha) (ruanyifeng.com)

什么是AJAX

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,也就是说页面无刷新获取数据。

普通的HTML,我们可以使用form表单来发送请求,但是呢?它会引起页面的跳转,会刷新页面。但是很多时候我们并不需要页面的跳转和刷新。

在日常生活中有很多这样的需求,比如说页面下拉获取新闻,这肯定是不需要刷新界面的。

AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。

  • 不刷新页面更新网页
  • 在页面加载后从服务器请求数据
  • 在页面加载后从服务器接收数据
  • 在后台向服务器发送数据

AJAX的工作流程

AJAX基础学习

XML简介

XML 指可扩展标记语言(eXtensible Markup Language)。

XML 被设计用来传输和存储数据

XLM和HTML类似,不同的是HTML都是预定义的标签,而XML中没有预定以的标签,全部都是自定义标签,用来表示一些数据。

<?xml version="1.0" encoding="UTF-8"?>
<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>

可以看到,所以内容都必须被标签包括,而且都是自定义的标签。可以看出xml的体积很大。

现在基本已经被JSON取代了。

{
    "note":[
        "to":"Tove",
        "from":"jani",
        "heading":"Reminder",
        "body":"Don't forget me this weekend!"
    ]
}

用JSON表示结构清晰,数据量也不大,解析也很方便JSON.paser()JSON.stringify()

AJAX的特点

优点

  1. 可以无需刷新页面与服务器进行数据交互
  2. 允许根据用户事件来更新部分界面

缺点

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

    如果使用表单,那么它会跳转到另外一个页面,那么这样就可以形成历史记录,但是对于AJXA来说,它是无刷新获取数据的,那么就没有形成历史记录。

  2. 存在跨域问题

  3. SEO不友好

JavaScript原生AJAX的使用

使用的对象

XMLHttpRequestAJAX的所有操作都是通过该对象进行的。

使用步骤

搭建一个简单的本地服务器

使用express + Nodejs 搭建

const express = require('express');

const app = express();
// 暴露静态资源
// 若想要与服务器交互,不能在使用本地打开或者liveserver打开了。他们使用的协议或者端口是不一样的,会产生跨域问题
app.use(express.static(__dirname + '/src'))

app.get('/test_get',(request,response)=>{
    console.log("有请求接入");
    response.send("Hello 啊");
})

app.listen(80 ,(err)=>{
    if(!err){
        console.log('测试ajax的服务器开启成功');
        console.log('测试地址是:http://localhost/1.ajax小试牛刀.html');
    }
});

初体验

html

<!DOCTYPE html>
<html>
<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>ajax初体验</title>
    <style>
        #content {
            margin-top: 10px;
            width: 300px;
            height: 100px;
            border: 1px solid red;
        }
    </style>
</head>
<body>
    <h1>该页面是测试ajax的基础使用</h1>
   
    <button id="btn">点我发送(原生js-ajax-get)</button>
    <div id="content">

    </div>
    <script>
        const btn = document.querySelector("#btn");
        const content = document.querySelector("#content");
        btn.onclick = () =>{
            // 发送ajax请求
            //1. 创建xhr实例对象
            const xhr = new XMLHttpRequest();
            // xhr 内部有5种状态,值分别为 0 1 2 3 4
            // 4 就是数据完整回来
            // xhr实例对象,在实例完成的那一刻,状态为0 
            /* 
             5中状态值:0,1,2,3,4

             实例刚出来就是0
             在发送请求的时候,xhr中的状态一直带改变
                0:初始状态
                1:open已经调用了,但是send还没有调用,此时可以修改请求头内容
                2: send已经调用了,无法修改请求头了 
                3:已近回来一部分数据了, 如果是较小的数据会在此阶段一次性接受
                    ,较大的数据有待进一步接受,但是响应头一定会被接受。
                4: 所有数据响应回来
            
             
            */
            xhr.onreadystatechange = ()=>{
                // 函数体
                if(xhr.readyState === 3 ){
                    console.log("3时接受到的数据",xhr.response);
                    console.log("3时接受到的数据",xhr.getAllResponseHeaders);// 返回响应头的所有信息
                    console.log("3时接受到的数据",xhr.getResponseHeader);// 返回响应头中的一条信息,可以指定
                    console.log(xhr.getResponseHeader("content-length"));//获取响应体数据的信息
                }
                if(xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300){
                    console.log("数据回来了");
                    console.log(xhr.response);
                    content.innerHTML = xhr.response;
                }
            }
            //2. 指定发送请求的url 和 method
            xhr.open("GET","http://localhost/test_get");
            //3. 发送请求
            xhr.send();
        }
    </script>
</body>
</html>

创建XMLHttpRquest对象

在IE7+及其其他浏览器创建此对象

const xhr = new XMLHttpRequest();

在IE5和IE6中创建此对象

const xhr=new ActiveXObject("Microsoft.XMLHTTP");
var xmlhttp;
if (window.XMLHttpRequest)
{
    //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
    xmlhttp=new XMLHttpRequest();
}
else
{
    // IE6, IE5 浏览器执行代码
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}

AJAX向服务器发送请求

如果想要向服务器发送请求,那么就需要使用open() 和 send() 方法

例如

xhr.open("GET","http://localhost/test_get");
xhr.send();
open(method,url,async) and send(string)
方法 描述
open(method,url,async) 规定请求的类型、URL 以及是否异步处理请求。method: 请求的类型,GET或者POST或其他
url: 文件在服务器上的问题(也就是路径)
async:true(异步)、false(异步)
send(string) 将请求发送到服务器。对于string来说它是当请求方法时POST的时候的请求体字符串。

AJAX基础学习

GET请求

对于GET的请求有两种参数:query 和 params

对于GET请求来说它是没有请求体的

querystring 是查询字符串:它的编码方式是 urlencoded 编码方式,也就是 key=value&key=value的方式。

使用express获取查询字符串对象的方式

requset.query

params也是get 的参数,它的格式是 /xx/xxx, 这很容易与路径相混淆,在express中它是这样处理的

app.get('/:id/:name',(req,res)=>{
    console.log(req.params.id);    			            console.log(req.params.name);
})

里面也可以使用正则表达式,或者- 和 .

'/:name-:dd' => /fujiaxu-dasd\
'/:name.dd' = > /fujiaxu.xxx
'/\^s\' => /s

一般代码

const btn = document.querySelector("#btn");
const content = document.querySelector("#content");
        btn.onclick = () =>{
            // 发送ajax请求
            //1. 创建xhr实例对象
            const xhr = new XMLHttpRequest();
            
            // 绑定监听
            // on 当。。。。。的时候
            // ready 准备
            // state 状态
            // change 改变
            // 当准备的状态发生改变
            // xhr 内部有5种状态,值分别为 0 1 2 3 4
            // 4 就是数据完整回来
            // xhr实例对象,在实例完成的那一刻,状态为0 
            xhr.onreadystatechange = ()=>{
                // 函数体
                if(xhr.readyState === 4){
                    console.log("数据回来了");
                    console.log(xhr.response);
                    content.innerHTML = xhr.response;
                }
            }
            //2. 指定发送请求的url 和 method
            xhr.open("GET","http://localhost/test_get");
            //3. 发送请求
            xhr.send();
        }

POST请求

POST的请求也有query、params,但是最常用的还是请求体。在AJAX中使用send(string)方法发送。

请求体最常用的类型是 urlencoded 和 JSON

对于发送urlendcoded类型的请求体时,请使用 setRequestHeader() 来添加 HTTP 头

xhr.setRequestHeader("Content-type","application/x-www-form-ur");
xhr.send("name=fujiaxu&&age=17");

对于发送JSON类型的请求体时,请使用 setRequestHeader() 来添加 HTTP 头

xhr.setRequestHeader("Content-type","application/json");
let obj = {
    "name":"fujiaxu",
    "age" : 18
}
xhr.send(JSON.stringify(obj));

对于使用express框架开启的服务需要加载一些中间件或者使用body-parser。要不然无法解析。

app.use(express.urlencoded({extended:true}));// 解析urlencoded的请求体
app.use(express.json());// 解析JSON类型的请求体。

AJAX响应

ajax获取和解析数据

获取响应体数据

xhr.response;
xhr.responseText	//获得字符串形式的响应数据。
xhr.responseXML	//获得 XML 形式的响应数据。

解析JSON

可以使用JSON.parse(xhr.response),但是如果返回的数据不是JSON,那么将会报错**。**

我们可以这样做xhr.responseType = “json”;必须是在返回数据前就设定好

这样有如果返回的数据不是json,那么将返回结果null,不会报错

readyState

直接看教程。

AJAX – onreadystatechange 事件 | 菜鸟教程 (runoob.com)

解决IE中get请求缓存问题

304状态码

(5条消息) HTTP 304状态码的详细讲解_胡杰的专栏-CSDN博客_304状态码

一文读懂http缓存(超详细) - 简书 (jianshu.com)

304状态码的意思是浏览器走的是协商缓存。但是IE浏览器如果看到请求的同一个东西,那么它将不会询问服务器是否是同一内容,直接走缓存,这么造成服务器修改内容无效。

解决方法:

首先ie不支持模板字符串和箭头函数。

既然ie是通过每次请求的url是否相同来判断请求内容是否相同,那么我们可以给它加一个时间戳的参数

"http://localhost/xxx?t="+Date.now()

这样就可以解决了。

POST请求是不会有这个问题的,这是当时设计的问题。ie认为既然GET所有的参数(query和params)都在url中,那么url没有变化就意味着内容没有变化。

AJAX请求异常和超时的处理

错误处理

xhr有一个处理错误的api

xhr.onerr = ()=>{
    
}

超时处理

这样说还等待返回结果,但是等待时间位置,我们也不可能无限等待,所以要设置一个最大的等待时间。

AJAX基础学习

xhr.timeout = 2000;// 2s  // 设置超时等待的时间
// 设置超时的回调函数
xhr.ontimeout = ()=>{
    alert("你的网网速实在是太垃圾了");
}

如果等了2s,数据还没有回来,那么就不再等待了,直接取消

AJAX基础学习

取消请求

必须要在数据全完返回之前取消请求,要不然无法起作用。

xhr.abort();// 终止请求

避免多次重复请求

如果多次请求,那么将会出现重复请求,消耗大量的资源,我们可以通过一些设置来取消最后一次之前的所有请求。使用flag来判断上次请求是否成功,若还没有成功,则取消上一次的请求,只需要本次请求的结果。

Jquery中的AJAX

[$.ajax(url,settings]) | jQuery API 3.2 中文文档 | jQuery API 在线手册 (cuishifeng.cn)

jQuery ajax - ajax() 方法 (w3school.com.cn)

回调地狱:回调函数里面套回调函数,使用promise技术可以解决。

同源策略(same origin policy)

浏览器的同源策略 - Web 安全 | MDN (mozilla.org)

浏览器同源政策及其规避方法 - 阮一峰的网络日志 (ruanyifeng.com)

跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com)

同源策略规定了不同域

不能获取Cookie、LocalStorage 和 IndexDB 无法读取。

对于cookie来说可以使用 document.domain 来允许子域安全访问其父域时,您需要在父域和子域中设置 document.domain 为相同的值。这是必要的,即使这样做只是将父域设置回其原始值。不这样做可能会导致权限错误。

不能获取ajax返回的数据,可以发送请求,但是响应被拦截

不能获取DOM

解决ajax跨域问题

JSONP

这其实是一种规定跨域问题的方法,而没有真正的解决问题。不需要调用XMLHttpRquest对象来发送请求了。

只能用于get,因script标签只能使用get。

前端定义函数,后端调用函数。

利用的原理:

<script> <img> <link> <style> <form>等这些标签不会受到同源策略的限制,可以发送请求,并取得响应的结果。

它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

前端页面写法:

	<body>
	  <button id="btn">按钮</button>
	  <script type="text/javascript">
	    var btn = document.getElementById('btn');
	    btn.onclick = function () {
	      //1. 创建一个script标签
	      var script = document.createElement('script');
	      //2. 设置回调函数
	      window.getData = function (data) {
	        console.log(data);//拿到数据
	      }
	      //3. 设置script标签src属性,填写跨域请求的地址
	      script.src = 'http://localhost:3000/jsonp?callback=getData';// 在参数中给出回调函数的名字
	      //4. 将script标签添加到body中生效
	      document.body.appendChild(script);
	      //5.不影响整体DOM结构,删除script标签
	      document.body.removeChild(script);
	    }
	  </script>
	</body>

后端写法(express框架):

app.get('/jsonp', (req, res) => {
  //解构赋值获取请求参数
  const {callback} = req.query
  //去数据库查找对应数据
  const data = [{name: 'tom', age: 18}, {name: 'jerry', age: 20}];
  res.send(callback + '(' + JSON.stringify(data) + ')');
})

其原理就是在页面中先设置一个全局的函数来得到数据,在script标签中得到后端发来的数据(也就是JavaScript代码),自动执行这样就可以调用预先设置好的函数来会的内容了。

作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。这是为什么呢?因为本来发过来就是一堆字符串,一种猜测,浏览器解析的时候它就变成了js代码,那么JSON.stringify(data)的数据在里面自然就变成了对象了。

比如说

“fun({“a”:123})” 在js 代码里面就是 fun({“a”:123}) 这肯定就是一个对象啊,

所以我们再返回数据的时候应该想象到在js代码中,我们的数据会变成什么样子。

Websocket方式解决

以下文字来自于:浏览器同源政策及其规避方法 - 阮一峰的网络日志 (ruanyifeng.com)

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自*)。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

CORS

跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com)

CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

它的特点是:不需要要在客户端进行任何的操作,完全在服务器中进行。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

AJAX基础学习

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

如何解决的?

cors就是利用了响应头,设置响应头来告诉浏览器,改请求允许跨域,浏览器收到申请后对该响应放行。而不是对request拦截。

AJAX基础学习

出现跨域问题,可以看到返回的信息中,有一句话说的是没有Access-Control-Allow-Origin头。

在服务器端设置响应头(express框架下)

app.get("/test",(request,response)=>{
    repsonse.setHeader("Access-Control-Allow-Origin","具体网站的地址");
	repsonse.setHeader("Access-Control-Allow-		Origin","http://127.0.0.1:5500");// 协议+主机名+端口号
	repsonse.setHeader("Access-Control-Allow-Origin","*");//任何网站都可以在此路由中拿到数据
})

这样设置只能解决一部分的问题,对于复杂请求起不到作用,而且使用xhr.getAllResponseHeaders无法所以的响应头,Access-COntrol-Allow-Origin","http://127.0.0.1:5500,也无法拿到。

AJAX基础学习

所以要设置

repsonse.setHeader("Access-Control-Expose-Header","*");// 暴露所以的响应头

对于http的复杂请求,比如PUTDELETE这两种请求,或者Content-Type字段的类型是application/json

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

请求的方式是

AJAX基础学习

AJAX基础学习

就是OPTIONS请求。在后端的路由中我们需要设置

app.options("/test",(requset,response)=>{
    repsonse.setHeader("Access-Control-Allow-Origin","具体网站的地址");
	repsonse.setHeader("Access-Control-Allow-		Origin","http://127.0.0.1:5500");// 协议+主机名+端口号
	repsonse.setHeader("Access-Control-Allow-Origin","*");//任何网站都可以在此路由中拿到数据
    repsonse.setHeader("Access-Control-Allow-Methods","*");//任何请求都可以在此路由中拿到数据
})

当然在express还可以使用一个中间件cors来简化这些步骤。

var express = require('express')
var cors = require('cors')
var app = express()
 
app.use(cors())
 
app.get('/products/:id', function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for all origins!'})
})
 
app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

具体的使用方法cors - npm (npmjs.com)

上一篇:day 67 前后端传输数据的编码格式、 ajax发送json格式数据、 ajax实现二次确认、 django自带的系列化组件、 批量插入、 自定义分页器、 froms组件


下一篇:【WebApi系列】详解WebApi如何传递参数