Skip to content
快速预览

ajax基础概念

✍️ w 🕒 2023-10-13 00:51:20(2 minutes ago) 🔗 C.前端工程化

要对接口请求有个理解之前要先知道关于前后端分离的产生过程有个了解,早期时候

服务器端渲染(SSR,server side render):客户端发出请求 -> 服务端接收请求并返回相应HTML文档 -> 页面刷新,客户端加载新的HTML文档,页面是通过后端渲染来完成的

但这种情况产生弊端当用户点击页面中的某个按钮向服务器发送请求时,页面本质上只是一些数据发生了变化,而此时服务器却要将重绘的整个页面再返回给浏览器加载,这显然有悖于程序员的“DRY( Don‘t repeat yourself )”原则;只是一些数据的变化却迫使服务器要返回整个HTML文档,这本身也会给网络带宽带来不必要的开销

后来出现了先后端分离

客户端渲染:只向服务器请求新的数据,并且在阻止页面刷新的情况下,动态的替换页面中展示的数据ajax 出现

ajax 全称 Asynchronous JavaScript + XML(异步JavaScript和XML),2005年被Jesse James Garrett提出的新术语,用来描述一种使用现有技术集合的新方法,包括: HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要XMLHttpRequest。当使用结合了这些技术的AJAX模型以后, 网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。这使得程序能够更快地回应用户的操作。尽管X在Ajax中代表XML, 但由于JSON的许多优势,比如更加轻量以及作为Javascript的一部分,目前JSON的使用比XML更加普遍。JSON和XML都被用于在Ajax模型中打包信息。

XMLHttpRequest

  1. XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest在[AJAX]编程中被大量使用 参考资料MDN # XMLHttpRequest

  2. 使用四步骤

  • 创建网络请求的AJAX对象(使用XMLHttpRequest)
  • 监听XMLHttpRequest对象状态的变化,或者监听onload事件(请求完成时触发)
  • 配置网络请求(通过open方法)
  • 发送send网络请求
js
let xhr = new XMLHttpRequest; // 无参时候可以不加括号
xhr.open('GET', '/userInfo?id=1'); //=>router Query
// xhr.open('GET', '/userInfo/1'); //=>这种router Params后端处理解析url 获取参数

xhr.onreadystatechange = function () {
    if (xhr.status !== 200) return;
     /**监听请求的过程,在不同的阶段做不同的处理「包含获取服务器的响应信息」
     + ajax状态  xhr.readyState
       + 0 UNSENT  代理被创建,但尚未调用 open() 方法
       + 1 OPENED    方法已经被调用
       -----
       + 2 HEADERS_RECEIVED 响应头信息已经返回
       + 3 LOADING 响应主体信息正在处理
       + 4 DONE 响应主体信息已经返回**/
    if (xhr.readyState === 4) { // 要等到数据全部返回因此判断状态为4
        console.log(xhr.response);
    }
};
xhr.send();

XMLHttpRequest其他事件监听

  1. onloadstart:请求开始。
  2. onprogress: 一个响应数据包到达,此时整个 response body 都在 response 中。
  3. onabort:调用 xhr.abort() 取消了请求。
  4. onerror:发生连接错误,例如,域错误。不会发生诸如 404 这类的 HTTP 错误。
  5. onload:请求成功完成。
  6. ontimeout:由于请求超时而取消了该请求(仅发生在设置了 timeout 的情况下)。
  7. onloadend:在 load,error,timeout 或 abort 之后触发。
  • : onreadystatechange 和 onload。区别只要进入onload请求中,一定是已经到4这个状态了

  • 下面案例中设置了 xhr.responseType 类型决定 content-type 返回的类型,获取HTTP响应的网络状态,可以通过status和statusText来获取,不同的是,status 属性保存的状态码是以数字表示的,而 statusText 属性保存的状态码是以字符串表示
js
var formData = new FormData();
  formData.append('username', 'johndoe');
  formData.append('id', 123456);
  //创建xhr对象 
  var xhr = new XMLHttpRequest();
  //设置xhr请求的超时时间
  xhr.timeout = 3000;
  //设置响应返回的数据格式
  xhr.responseType = "text";
  //创建一个 post 请求,采用异步
  xhr.open('POST', '/server', true);
  //注册相关事件回调处理函数
  xhr.onload = function(e) { 
    if(this.status == 200||this.status == 304){
        alert(this.responseText);
    }
  };
  xhr.ontimeout = function(e) { ... };
  xhr.onerror = function(e) { ... };
  xhr.upload.onprogress = function(e) { ... };
  
  //发送数据
  xhr.send(formData);

Content-Type 两者form 格式说明

  1. 请求头和响应头中'Content-Type' ,用于定义用户的浏览器或相关设备如何显示将要加载的数据,或者如何处理将要加载的数据
  2. 发送请求设置 'Content-Type' 举例: 2.1.格式为urlencoded,设置为 'Content-Type:x-www-form-urlencoded'格式的字符串( 将键值对的参数用&连接起来,如果有空格,将空格转换为+加号;有特殊符号,将特殊符号转换为ASCII HEX),是浏览器默认的编码格式。对于Get请求,是将参数转换?key=value&key=value格式,连接到url后,举个例子参数在请求体中 格式 'lx=1&name=xxx' 我们在开发过程中使用'qs' 库Qs.stringify/parse:实现对象和urlencoded格式字符串之间的转换
js
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send(Qs.stringify({
        lx: 0,
        name: 'xxx'
    }))
// 不用qs 自己拼接字符串
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send("name=why&age=18&address=广州市")

当然如果不想引入qs 库也可以使用'URLSearchParams'但是URLSearchParams 不支持所有的浏览器

js
let param = new URLSearchParams()
param.append('username', 'admin')
param.append('pwd', 'admin')

param.toString() //  'username=admin&pwd=admin'

2.2. formdata 'Content-Type:form-data' 主要应用于文件的上传或者表单数据提交使用FormData,form-data格式一般是用来进行文件上传的。使用表单上传文件时,必须让表单的 enctype 等于multipart/form-data,因为该值默认值为application/x-www-form-urlencodedmultipart/form-data是基于post方法来传递数据的,另外,该格式会生成一个boundary字符串来分割请求头与请求体的

  • 分割格式是 boundary=${boundary}之后就是请求体内容了,请求体内容各字段之间以--${boundary}来进行分割,以--${boundary}--来结束请求体内容
// 开始分割
boundary=----WebKitFormBoundaryyb1zYhTI38xpQxBK

------WebKitFormBoundaryyb1zYhTI38xpQxBK
Content-Disposition: form-data; name="city_id"

1

------WebKitFormBoundaryyb1zYhTI38xpQxBK  // 请求体内容各字段之间 分割
Content-Disposition: form-data; name="company_id"

2
------WebKitFormBoundaryyb1zYhTI38xpQxBK
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryyb1zYhTI38xpQxBK-- // 结束分割
xhr.setRequestHeader('Content-Type', 'multipart/form-data');
   let fd = new FormData;
   fd.append('lx', 0);
   fd.append('name', 'xxx');
   xhr.send(fd);

2.3. raw字符串格式

普通字符串  -> text/plain
       JSON字符串 -> application/json  => JSON.stringify/parse  「常用」
       XML格式字符串 -> application/xml

2.4. binary进制数据文件「buffer/二进制...」

一般也应用于文件上传
   图片 -> image/jpeg
   EXCEL -> application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
  1. 响应主体信息 xhr.response/responseText/responseXML, 服务器返回的响应主体信息的格式 3.1. 字符串「一般是JSON字符串」 「最常用」 3.2. XML格式数据 3.3. 文件流格式数据「buffer/二进制...」

可参考链接谈谈axios配置请求头content-type

封装

  1. 下面代码将xhr 挂载到promise 上为了可以使用xhr 一些方法
js
function myAjax({
  url,
  method = "get",
  data = {},
  timeout = 10000,
  headers = {}, // token
} = {}) {
  // 1.创建对象
  const xhr = new XMLHttpRequest()

  // 2.创建Promise
  const promise = new Promise((resolve, reject) => {

    // 2.监听数据
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response)
      } else {
        reject({ status: xhr.status, message: xhr.statusText })
      }
    }

    // 3.设置类型
    xhr.responseType = "json"
    xhr.timeout = timeout

    // 4.open方法
    if (method.toUpperCase() === "GET") {
      const queryStrings = []
      for (const key in data) {
        queryStrings.push(`${key}=${data[key]}`)
      }
      url = url + "?" + queryStrings.join("&")
      xhr.open(method, url)
      xhr.send()
    } else {
      xhr.open(method, url)
      xhr.setRequestHeader("Content-type", "application/json")
      xhr.send(JSON.stringify(data))
    }
  })

  promise.xhr = xhr

  return promise
}
  • 使用
js
const promise = myAjax({
	url: "",
	data: {
	username: "",
	password: ""
	}
})

promise.then(res => {
	console.log("res:", res)
}).catch(err => {
	console.log("err:", err)
})
promise.xhr.abort() // 取消请求

倒计时抢购案例

  1. 获取服务器时间可以从任意接口或者资源中的响应头Date 获取如图

js
/* 
 * 两个时间:
 *   + 目标时间 18:00:00
 *   + 当前时间 
 *   目标时间-当前时间=时间差 「毫秒差:计算时间差中包含多少小时,多少分钟,多少秒」 
 *   每间隔一秒中都需要重新获取当前时间「定时器 setInterval」,重算时间差等
 * 
 * 核心的问题:
 *   当前时间是不可以获取客户端本地的(因为本地的时间客户自己可以肆意的修改),需要统一获取服务器的时间「响应头->Date」
 *   + 获取服务器时间会存在时间偏差问题  --> HEAD  AJAX状态码为2
 * 
 *   在页面不刷新的情况下,每间隔1秒,不是再次从服务器获取(如果这样:服务器会崩溃,用户得到的时间误差也会越大...),而是基于第一次获取的结果之上,手动给其累加1000ms即可
 */
let countdownModule = (function () {
    let textBox = document.querySelector('.text'),
        serverTime = 0,
        targetTime = +new Date('2020/12/05 16:00:00'),
        timer = null;

    // 获取服务器时间
    const queryServerTime = function queryServerTime() {
        return new Promise(resolve => {
            let xhr = new XMLHttpRequest;
            xhr.open('HEAD', '/');
            xhr.onreadystatechange = () => {
                if ((xhr.status >= 200 && xhr.status < 300) && xhr.readyState === 2) {
                    let time = xhr.getResponseHeader('Date');
                    // 获取的时间是格林尼治时间 -> 变为北京时间
                    resolve(+new Date(time));
                }
            };
            xhr.send(null);
        });
    };
    
    // 倒计时计算
    const supplyZero = function supplyZero(val) {
        val = +val || 0;
        return val < 10 ? `0${val}` : val;
    };
    const computed = function computed() {
        let diff = targetTime - serverTime,
            hours = 0,
            minutes = 0,
            seconds = 0;
        if (diff <= 0) {
            // 到达抢购时间了
            textBox.innerHTML = '00:00:00';
            clearInterval(timer);
            return;
        }
        // 没到时间则计算即可
        hours = Math.floor(diff / (1000 * 60 * 60));
        diff = diff - hours * 1000 * 60 * 60;
        minutes = Math.floor(diff / (1000 * 60));
        diff = diff - minutes * 1000 * 60;
        seconds = Math.floor(diff / 1000);
        textBox.innerHTML = `${supplyZero(hours)}:${supplyZero(minutes)}:${supplyZero(seconds)}`;
    };

    return {
        async init() {
            serverTime = await queryServerTime();
            computed();

            // 设置定时器   
            timer = setInterval(() => {
                serverTime += 1000;
                computed();
            }, 1000);
        }
    };
})();
countdownModule.init();

Released under the MIT License.