Ajax(上)

1,338 阅读9分钟

什么是Ajax

Ajax 的全称是 Asynchronous JavaScript and XML(异步的 JavaScript 和 XML),是一种用于创建动态网页(与后端交互)的技术。

Ajax 技术指的是脚本独立向服务器请求数据,拿到数据以后,进行处理并更新网页。整个过程中,后端只是负责提供数据,其他事情都由前端处理,实现了 获取数据 → 处理数据 → 展示数据 的完整业务逻辑。

同步与异步

同步

所有的操作都做完,才返回给用户。这样用户在线等待的时间太长,给用户一种卡死了的感觉(就是系统迁移中,点击了迁移,界面就不动了,但是程序还在执行,卡死了的感觉)。这种情况下,用户不能关闭界面,如果关闭了,即迁移程序就中断了。

异步

将用户请求放入消息队列,并反馈给用户,系统迁移程序已经启动,你可以关闭浏览器了。然后程序再慢慢地去写入数据库去。这就是异步。但是用户没有卡死的感觉,会告诉你,你的请求系统已经响应了。你可以关闭界面了。

工作原理

在客户端(如浏览器)和服务器之间加了一个中间层:Ajax 引擎。由 Ajax 引擎独立向服务器请求数据,前端获取到 Ajax 返回的数据后,可以使用新数据来更新页面、或进行其它操作,使用户请求和服务器响应异步化,从而保证了在不刷新页面的前提下可以局部更新网页内容。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Ajax</title>
</head>
<body>
    <div id="view">
        <p>点击下面的按钮,将 Ajax 请求回来的数据更新在该文本内</p>
    </div>
    <button type="button" id="btn">发起 Ajax 请求</button>

    <script>
        document.getElementById("btn").onclick = ajaxRequest;
        function ajaxRequest () {
            //实例化一个XMLHttpRequest对象
            var xhr = new XMLHttpRequest();
            //open()规定请求的类型、url、是否异步
            xhr.open("GET","https://www.w3cschool.cn/statics/demosource/ajax_info.txt", true);
            xhr.send();
		   //send()发送请求,必须结合open一起使用
            xhr.onreadystatechange = function(){
                //监测服务器响应的状态
                if (xhr.readyState === 4 && xhr.status === 200) {
                    //当 readyState 等于 4 且状态为 200 时,表示成功响应
                    document.getElementById("view").innerHTML = xhr.responseText;
                }                
            }
        }
    </script>
</body>

优点与缺点

优点:

  1. 页面无刷新更新,用户的体验非常好;
  2. 异步通信,响应更快
  3. 可以将一些服务器工作转移到客户端,利用客户端资源来处理,减轻服务器和带宽的压力,节约空间和带宽租用成本;
  4. 技术标准化,并被浏览器广泛支持,不需要下载插件或者小程序;
  5. Ajax 可使因特网应用程序更小、更快、更友好。

缺点:

  1. Ajax 不支持浏览器 back 返回按钮;
  2. 有安全问题,Ajax 暴露了与服务器交互的细节;
  3. 对搜索引擎不友好;
  4. 破坏了程序的异常机制;
  5. 不容易调试。

同源策略

url的组成

一个 URL 地址可以有以下几个组成部分:scheme: //host:post/path?query#fragment

  • scheme:通信协议,一般为 http 、https;
  • host:域名;
  • post:端口号,此项为可选项,http 协议默认的端口号为 80,https 协议默认的端口号为 443;
  • path:路径,由 "/ "隔开的字符串;
  • query:查询参数,此项为可选项;
  • fragment:信息片段,用于指定网络资源中的某片断,此项为可选项;

网址:https://www.w3cschool.cn:8080/search?w=ajax

  • https是协议;
  • www.w3cschool.cn是域名;
  • 8080是端口号;
  • /search是路径;
  • ?w=ajax是 URL 地址带的参数;
  • 缺少 fragment 信息片段;

什么是同源策略

同源策略是一种安全协议,是客户端脚本(尤其是 JavaScript)中重要的安全度量标准,指一段脚本只能读取同一来源的窗口和文档的属性。

何为同源?

同源指的是 URL 地址中的 协议域名端口 三者 相同。

路径不同,同源

协议不同,不同源

域名不同,不同源

端口不同,不同源

为什么要有同源策略

我们在使用 Ajax 请求后端数据时,只能跟同源的后端接口进行数据交互,即:后端接口的 URL 与发起 Ajax 请求的页面 URL 之间,需要满足同源策略。

不满足 "同源策略" 的请求浏览器通常都会报错,下面是一个 本地服务器 请求 远程服务器 产生的报错信息:

为了数据的安全性。若没有同源策略的限制,那么黑客就可以在他的页面上任意请求你的后端数据,造成数据库内容被盗取、隐私数据泄漏。

跨域请求

虽然 Ajax 请求需要满足同源策略,然而在一些场景中,你真的需要 Ajax 访问其它 "源" 的数据(称为跨域访问),这时需要后端服务器进行相应的设置。

如果服务器端支持 CORS,可以通过设置Access-Control-Allow-Origin来实现跨域。如果浏览器检测到相应的设置,就会允许 Ajax 进行跨域访问。

解决跨域的方式:

  • JSONP 技术
  • 在服务端设置代理模块;
  • 通过修改 window.name 实现跨域;
  • 使用 HTML5 中新引进的 window.postMessage 方法来跨域传送数据;
  • 等...

XMLHttpRequest

Ajax 技术的核心XMLHttpRequest类,简称 XHR,它允许脚本异步调用 HTTP API。浏览器在XMLHttpRequest类上定义了 HTTP API,这个类的每个实例都表示一个 独立请求/响应 对象,并且这个实例对象上的属性和方法允许 指定细节提取响应数据

创建实例

var xhr = new XMLHttpRequest();

兼容

绝大多数的浏览器都支持XMLHttpRequest在 IE7 之前的版本(IE5、IE6)并不支持XMLHttpRequest()构造函数,它们需要使用ActiveX对象进行模拟:

var xhr = new ActiveXObject("Microsoft.XMLHTTP");

兼容写法

var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");

open()

创建 XMLHttpRequest 对象之后,发起 HTTP 请求的下一步是调用 XMLHttpRequest 对象的open方法,指定 HTTP 请求的两个必需部分:请求方法URL

xhr.open(method, url, async)

  • method:

    • 第一个参数用于指定 HTTP 请求的方法,不区分大小写;

    • 该参数可取的值包括:"GET"、"POST"、"HEAD"、"PUT"、"OPTIONS"、"DELETE",其中,"GET" 和 "POST" 是得到广泛支持的请求方法;

  • url:

    • 第二个参数用于指定 HTTP 请求的 URL 地址,可以是 绝对URL相对URL

    • 绝对URL:需要满足 "同源策略"(服务器明确允许跨域请求的情况除外);

    • 相对URL:即相对于文档的 URL;

  • async:

    • 第三个参数是可选的,可用布尔值指定脚本是否以异步的方式调用此次 Ajax 请求;

    • 该参数默认为 true,表示异步调用此次 Ajax 请求,不阻塞后续脚本的执行;

注意

open()方法其实还可以有第四、第五个参数,分别是用于 HTTP 请求访问认证的用户名和密码,使用它们需要在服务器做相应的配置,较为少用。

setRequestHeader

如果你的 HTTP 请求需要设置请求头,那么调用 open 方法之后的下个步骤就是设置它,使用的方法是:setRequestHeader

// 在 open 方法之后设置请求头
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

xhr.setRequestHeader(name, value)

  • name:请求头名称;
  • value:请求头的值。

send()

使用 XMLHttpRequest 发起 HTTP 请求最后一步是指定可选的请求主体、并向服务器发送它,使用的方法是:send

var xhr = new XMLHttpRequest();
xhr.open("GET", "/statics/demosource/demo_get_json.php");

// 由于GET请求,没有请求主体,所以在调用 send 方法时可以传递 null或省略这个参数;
xhr.send(null);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/statics/demosource/demo_post_json.php");

// 把 msg 作为请求主体发送
xhr.send(msg);
  1. POST 请求通常都拥有请求主体,可在 send 方法中指定它;
  2. POST 请求的请求主体,应该匹配 setRequestHeader方法所指定的 "Content-Type" 头。

获取响应

一个完整的 HTTP 响应由 状态码、响应头和 响应主体 组成,这三者都可以通过XMLHttpRequest对象提供的属性和方法获取。

如何确定 HTTP 请求已经返回了响应内容呢?

为了能够在 HTTP 响应准备就绪时得到通知,必须监听XMLHttpRequest对象上的readystatechange事件。但为了理解这个事件类型,需要先了解下readyState属性,因为该事件监听的是readyState属性值的改变。

XMLHttpRequest对象上的readyState属性在 HTTP 请求过程中,会从 0 变到 4

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Tryrun 2</title>
</head>
<body>
    <button id="btn">点我观察 readyState 属性的改变</button>
    
    <script>
        var oBtn = document.getElementById("btn");
        oBtn.onclick = function () {
            //兼容处理
            var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
            alert(xhr.readyState);//0
            xhr.onreadystatechange = function () {
                alert(xhr.readyState);
            }
            xhr.open("GET", "/statics/demosource/demo_get_json.php");
            xhr.send();
        }
    </script>
</body>
</html>

readyState 属性

readyState属性是一个整数,它的值代表了不同的 HTTP 请求状态。

  • 0:初始值,表示请求未初始化,open方法尚未调用;
  • 1:启动请求,open 方法已经调用,但尚未调用 send 方法;
  • 2:请求发送,已经调用 send 方法,但尚未接收到响应;
  • 3:接收响应,已经接受到部分响应数据,主要是响应头;
  • 4:HTTP 响应完成,已经接收到全部响应数据,而且可以在客户端使用。

每次readyState属性值的改变都会触发readystatechange事件,但只有readyState属性值为 4 时才是我们所关心的状态,因为只有这个状态才表示 HTTP 的响应准备就绪,可以真正意义上的结合服务器所响应的数据来实现我们的业务需求。

发送请求规范

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Tryrun 3</title>
</head>
<body>
    <button id="btn">点我观察 readyState 属性的改变</button>
    <div id="tip"></div>
    
    <script>
        var oBtn = document.getElementById("btn"),
            oTip = document.getElementById("tip");
        oBtn.onclick = function () {
            var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    oTip.innerText = "HTTP 响应完成";
                }
            };

            xhr.open("GET", "/statics/demosource/demo_get_json.php");
            xhr.send();
        }
    </script>
</body>
</html>

注意

  • readyState的属性值只代表此时的 HTTP 请求处于哪个阶段:是发送了请求还是未发送请求,是只接收到了响应头还是响应完成;
  • "响应完成" 只代表 HTTP 请求结束,至于服务器的响应状态:是请求成功还是请求错误,又或者是服务器错误,需要通过 HTTP 状态码判断,它存储在XMLhttpRequeststatus属性上;

status属性

status属性会以数字的形式保存服务器响应的 HTTP 状态码,诸如使用最频繁的 "200" 表示请求成功,"404" 表示 URL 不能匹配服务器上的任何资源。

HTTP 状态码是用来表示网页服务器响应状态的 3 位数字代码,所有状态码的第一个数字代表了响应的五种状态之一:

  • 1xx:临时响应
  • 2xx:成功
  • 3xx:重定向
  • 4xx:请求错误
  • 5xx:服务器错误

哪些 HTTP 状态码表示我们可以获取到 HTTP 响应数据呢?

2开头的状态码304。2开头的状态码都表示请求成功,而 304 是对客户端可读取缓存的一种响应,同样能获取到 HTTP 的响应数据。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Tryrun 4</title>
    <style>
        #btn { margin-top: 7px; }
    </style>
</head>
<body>
    <div id="tip"></div>
    <button id="btn">点我发起 Ajax 请求</button>
    
    <script>
        var oBtn = document.getElementById("btn"),
            oTip = document.getElementById("tip");

        oBtn.onclick = function () {
            var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) return;
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    oTip.innerText = "HTTP 请求成功";
                }
            };

            xhr.open("GET", "/statics/demosource/demo_get_json.php");
            xhr.send();
        }
    </script>
</body>
</html>

responseText 属性

responseText属性以字符串的形式存储了响应主体,即:服务器的响应数据。

无论返回的数据类型是什么,响应主体的内容都会保存在responseText属性中;

响应html

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
    if (xhr.readyState !== 4) return;
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        // 当响应成功,获取响应数据,将数据赋值给本地
        oView.innerHTML = xhr.responseText;
    }
};

xhr.open("GET", "/statics/demosource/demo_get.php");
xhr.send();

响应json

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
    if (xhr.readyState !== 4) return;
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        // 使用JSON.parse把 响应数据转换为json数据
        var res = JSON.parse(xhr.responseText);
        // 将响应数据中的data属性赋值给oTime做内容
        oTime.innerText = res.data;
    }
};

xhr.open("GET", "/statics/demosource/demo_get_json.php");
xhr.send();

查询 HTTP 响应头的方法

XMLHttpRequest对象上,可通过getAllResponseHeadersgetResponseHeader方法查询响应头信息。

getAllResponseHeaders

  • getAllResponseHeaders方法无参数,用于一次性返回可查询的全部响应头信息
var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
    if (xhr.readyState !== 4) return;
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        // 获取所有可查询的响应头信息
        oView.innerText = xhr.getAllResponseHeaders();
    }
};

xhr.open("GET", "/statics/demosource/demo_get_json.php");
xhr.send();

getResponseHeader

  • getResponseHeader方法用于查询单一响应头信息,需要传入一个指定 "头名称" 的字符串作为参数:getResponseHeader(headerName)
var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
    if (xhr.readyState !== 4) return;
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        // 查询 "Content-Type" 响应头信息
        alert( xhr.getResponseHeader("Content-Type") );
    }
};

xhr.open("GET", "/statics/demosource/demo_get_json.php");
xhr.send();

注意

由于XMLHttpRequest会自动处理 cookie,将 cookie 从getAllResponseHeaders方法返回的响应头集合中过滤掉,并且如果给getResponseHeader方法传递 "Set-Cookie" 或 "Set-Cookie2",则返回 null。

同步响应

var xhr = new XMLHttpRequest();

// 指定 open 方法的第三个参数为 false
xhr.open("GET", "/statics/demosource/demo_get_json.php", false);

// send 方法的调用将阻塞后面代码的执行,直到此次 HTTP 请求完成
xhr.send();

// 不再需要监听 readystatechange 事件
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
    oTime.innerText = JSON.parse(xhr.response).date;
} else {
    // 如果请求不成功,就报错
    throw new Error(xhr.status);
}

abort 中止请求

若 HTTP 请求的时间超出预期,可以调用XMLHttpRequest对象上的abort方法来中止 HTTP 请求。

var xhr = new XMLHttpRequest();
var timer = null;    // 用于存储定时器标识

xhr.onreadystatechange = function () {
    if (xhr.readyState !== 4) return;
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        clearTimeout(timer);    // 未超时则取消定时器
    }
};

xhr.open("GET", "/statics/demosource/demo_get_json.php");
xhr.send();

// 2秒后中止此次 GET 请求
timer = setTimeout(function(){
    xhr.abort();
}, 2000)