08月08, 2022

JS进阶(9)--Ajax(3)--Ajax 具体实现与异步理解

Ajax 具体实现与异步理解

Ajax具体实现

前面有介绍过,使用原生的 Ajax 大致包括以下几步骤:

1. 创建 XMLHttpRequest 对象 2. 发出 HTTP 请求 3. 接收服务器传回的数据 4. 更新网页数据

接下来我们来具体看一下每一个部分。

Ajax 技术的核心就是 XMLHttpRequest 对象(简称 XHR)。这是由微软首先引入的一个特性,是一种支持异步请求的技术。(后来其他浏览器开发商也都提供了相同的功能实现。)

简而言之,XMLHttpRequest 用于与服务器交换数据。这意味着用户操作页面后,可以不必刷新页面也能够取得新的数据。

1. 创建XMLHttpRequest对象

创建一个XMLHttpRequest对象,也叫实例化一个XMLHttpRequest对象。因为XMLHttpRequest()本身是一个构造函数。

let xhr = new XMLHttpRequest();

IE5 是第一款引入XMLHttpRequest对象的浏览器。在 IE5 和 IE6 中,XHR 对象是通过 MSXML 库中的一个 ActiveX 对象实现的。

let xhr = new ActiveXObject('Microsoft.XMLHTTP');

而 IE7+ 及其他标准浏览器都支持原生的XMLHttpRequest对象。为了应对所有浏览器,下面是创建XMLHttpRequest对象的兼容写法。

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

2. 打开连接

XMLHttpRequest的实例对象提供了一个open()方法用来打开客户端与服务端之间的连接。该方法规定了请求的类型、请求的路径以及是否异步处理请求。语法结构如下:

xhr.open(method, url, async);

参数说明:

method:发送请求的方式。取值有 GETPOST(不区分大小写,建议使用大写)。
url:请求资源的位置路径。
async:控制是否异步处理请求。取值有 true(异步)和 false(同步),默认为 true

一般来讲,都会产用异步的方式来发送请求,不然 Ajax 将变得毫无意义。

3. 发送请求

XMLHttpRequest的实例对象提供了一个send()方法用于客户端向服务端发送请求。

xhr.send();

请求参数

在向服务端发送请求的同时,是可以传递数据至服务端的。而请求方式的不同,数据的传递方式也不同。

GET 请求:传递的数据是跟在open()方法中的url后面。

xhr.open("GET","/users/isUser?username=zhangsan&pwd=123");
xhr.send(null);

POST 请求:传递的数据是放在send()方法的参数中。调用send()方法之前需要设定Content-Type头信息,模拟 HTTP 的 POST 方法发送一个表单,这样服务器才会知道如何处理上传的内容。

参数的提交格式和 GET 方法中url的写法一样。设置头信息前必须先调用open()方法。

xhr.open("POST","login.php",true);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
xhr.send("username=zhangsan&pwd=123");

和 POST 相比,GET 的请求方式更简单也更快,并且在大部分情况下都能使用。但是,在以下情况中,请使用 POST 请求:

  1. 无法使用缓存文件(更新服务器上的文件或者数据库)
  1. 向服务器发送大量数据(POST 没有数据量的限制)
  2. 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

4. 处理服务器返回的消息

一个完整的 HTTP 响应由状态码、响应头集合和响应主体组成。在收到响应后,可以通过 XHR 对象的属性来获取响应内容。常用的属性有以下 4 个:

responseText: 作为响应主体被返回的文本(文本形式)
responseXML: 如果响应的内容类型是 text/xml 或 application/xml,这个属性中将保存着响应数据的 XML DOM 文档(document 形式)
status: HTTP 状态码(数字形式)
statusText: HTTP 状态说明(文本形式)

另外,第 2 步打开连接时,选择处理请求的方式不同,第 4 步处理服务器返回消息的方式也有所不同。

同步处理请求

当处理请求的方式为同步时,直接使用XMLHttpRequest的实例对象的responseText属性用来接收服务端返回的消息。

xhr.responseText;

但是不推荐使用同步处理请求的方式。使用这种方式会导致 JavaScript 代码要一直等到服务器响应就绪后才继续执行,如果服务器繁忙或缓慢,应用程序就会挂起或停止。但是对于一些小型的请求,也是可以的。

异步处理请求

当处理请求的方式为异步时,需要通过XMLHttpRequest的实例对象提供的onreadystatechange事件来监听并处理服务器返回的消息。

xhr.onreadystatechange = function(){
    // ...
}

从“请求准备发送”到最后“请求发送成功”,中间执行了一系列的任务,分别为:

请求未初始化(0):即还没有调用 send() 方法;
服务器连接已建立(1):即已调用 send() 方法,正在发送请求;
请求已接收(2):即 send() 方法执行完成;
请求处理中(3):即正在解析响应内容;
请求已完成(4):且响应已就绪,即响应内容解析完成,可以在客户端进行调用了;

每执行一个任务,XMLHttpRequest对象的状态值readyState都会发生改变。只要readyState属性发生改变时,就会触发onreadystatechange事件。

按照任务顺序,状态值readyState依次从 0 到 4 发生改变。

只有当状态值为 4 时,才表示请求完成。请求完成后,判断请求状态,状态码status为 200 时表示请求成功。只有请求完成并且成功了,才能处理服务端返回的消息。

xhr.onreadystatechange = function(){
    if(xhr.readyState == 4 && xhr.status == 200){
        let text = xhr.responseText;
        console.log( text );
    }
}

5. 完整代码

我们将上面分解的每一步整合在一起,就是一个完整的 Ajax 请求代码了。

// 创建 XHR 对象
let xhr;
if (window.XMLHttpRequest) {
    //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
    xhr = new XMLHttpRequest();
} else {
    // IE6, IE5 浏览器执行代码
    xhr = new ActiveXObject('Microsoft.XMLHTTP');
}

// 2. 打开连接
xhr.open("get", "/users/isPhoneExist?phone=12313212311"); // 第三个参数默认 true,表示异步;get 请求的参数跟在路径后面。

// 3. 发送请求
xhr.send(); // send 方法默认没有参数。
//xhr.send("phone=123&pwd=123");  // 请求是 post,且想要传参,send 方法才有参数。

// 4. 通过事件监听并处理服务器返回的消息
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4 && xhr.status == 200) { // 服务器正常的响应数据回来
        let text = xhr.responseText;   // 得到的 text 是字符串
        // info.innerHTML = text;  // 处理响应数据
    }
}

最后,我将演示使用 Ajax 发送请求,然后使用 Mock 来拦截 Ajax 请求的示例,如下:

<body>
    <button id="btn">点击我从服务器获取数据</button>
    <span id="test"></span>
    <script>
        btn.onclick = function () {
            // 首先第 1 步 创建 xhr
            let xhr = null;
            if (window.XMLHttpRequest) {
                xhr = new XMLHttpRequest();
            } else {
                xhr = new ActiveXObject('Microsoft.XMLHTTP');
            }
            // 接下来来做第 2 步 发送请求
            xhr.open('GET', '/getStudent', true);
            xhr.send(null);
            // 第 3 步 监听 readyState 的值 当值为 4 的时候说明数据已经回来 
            // 可以将数据通过JS添加到页面上面
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    test.innerHTML = JSON.parse(xhr.responseText).name;
                }
            }
        }
        // 接下来我们使用 Mock 来截取 Ajax 请求
        Mock.mock(/getStudent/, 'get', function () {
            return Mock.mock({
                'name': '@cname'
            });
        });
    </script>
</body>

效果:点击按钮后会由 Mock 随机产生一条数据填入到 span 元素里面

再比如下面的例子:

<!DOCTYPE html>
<html lang="en">
<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>Document</title>
  <style>
    table{
      border:1px solid #ddd;
      border-collapse: collapse;
      border-spacing: 0;
    }
    td,th{
      border:1px solid #ddd;
      padding:20px;
      line-height: 40px;
    }
  </style>
</head>
<body>
  <button id="btn">点击访问数据</button>
  <br>
  <table>
    <thead>
      <tr>
        <th>姓名</th>
        <th>性别</th>
        <th>电话</th>
        <th>成绩</th>
      </tr>
    </thead>
    <tbody id="mybody">

    </tbody>
  </table>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>
<script>
let btn = document.getElementById("btn");
let mybody = document.getElementById("mybody");

Mock.mock(/users/,"get",function(){
  return Mock.mock({
    "users|10-15":[{
      "username":"@cname()",
      "sex|1":["男","女"],
      "tel":/^1[3-9]\d{9}$/,
      "score|1-100":1
    }]
  }).users
})

btn.onclick = function(){
  let xhr = new XMLHttpRequest();
  xhr.open("get","/users");
  xhr.send();
  xhr.onreadystatechange = function(){
    if(xhr.readyState === 4 && xhr.status === 200){
      let txt = xhr.responseText; //获取返回的json格式的字符串
      // console.log(txt,typeof txt);
      //将字符串转换为json格式
      let users = JSON.parse(txt);
      console.log(users);

      for (let i = 0; i < users.length; i++) {
        let user = users[i];
        let str = `<tr>
            <td>${user.username}</td>
            <td>${user.sex}</td>
            <td>${user.tel}</td>
            <td>${user.score}</td>
          </tr>`
        mybody.innerHTML += str
      }
    }
  }
}
</script>
</html>

对于异步的理解

上面的代码中,我们直接是在onreadystatechange回调函数中操作的代码,那思考一个问题,那能不能在回调函数中获取数据,然后再到外部操作这个数据呢?比如下的情况

<!DOCTYPE html>
<html lang="en">
<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>Document</title>
  <style>
    table{
      border:1px solid #ddd;
      border-collapse: collapse;
      border-spacing: 0;
    }
    td,th{
      border:1px solid #ddd;
      padding:20px;
      line-height: 40px;
    }
  </style>
</head>
<body>
  <button id="btn">点击访问数据</button>
  <br>
  <table>
    <thead>
      <tr>
        <th>姓名</th>
        <th>性别</th>
        <th>电话</th>
        <th>成绩</th>
      </tr>
    </thead>
    <tbody id="mybody">

    </tbody>
  </table>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>
<script>
let btn = document.getElementById("btn");
let mybody = document.getElementById("mybody");

let users = null;

Mock.mock(/users/,"get",function(){
  let data = Mock.mock({
    "users|10-15":[{
      "username":"@cname()",
      "sex|1":["男","女"],
      "tel":/^1[3-9]\d{9}$/,
      "score|1-100":1
    }]
  }).users

  return data;
})

btn.onclick = function(){
let xhr = new XMLHttpRequest();
xhr.open("get","/users");
xhr.send();
xhr.onreadystatechange = function(){
  if(xhr.readyState === 4 && xhr.status === 200){
    let txt = xhr.responseText; //获取返回的json格式的字符串
    console.log(txt);
    //转换为json对象格式
    users = JSON.parse(txt);
    //如果数据是ajax异步获取的
    //那么处理数据的操作,就必须放在ajax的异步操作中
    setHTML(users);
  }
}
}

function setHTML(users){
  for (let i = 0; i < users.length; i++) {
    let user = users[i];
    let str = `<tr>
        <td>${user.username}</td>
        <td>${user.sex}</td>
        <td>${user.tel}</td>
        <td>${user.score}</td>
      </tr>`
    mybody.innerHTML += str
  }
}

//这里直接调用会报错...
//因为数据是通过ajax异步获取的,这里是同步代码
//users在这个时候还没有值
// setHTML(users);


// let str = "hello";
// //setTimeout 指定毫秒数之后运行代码
// //这个函数本身就是异步的
// setTimeout(function(){
//   str = "hello 美女";
// },0);
// console.log(str);

</script>
</html>

注意看上面例子中,注释的部分

本文链接:http://www.yanhongzhi.com/post/js_ap_21.html

-- EOF --

Comments