Ajax详解(手写jq和axios部分实现)

6,716 阅读4分钟

含义: async javascript and xml 异步的js和xml

一、Ajax原生JS操作

//一、创建Ajax实例
let xhr = new XMLHttpRequest();//IE下为ActiveObject对象
//二、打开请求: 发送请求之前的一些配置项
//1.HTTP METHOD:GET/POST/PUT/DELETE/HEAD/OPTIONS/TRACE/CONNECT/
//2.url:接口地址
//3.async:设置Ajax的同步异步,默认是异步
//4.user-name/user-pass用户名和密码,一般不用
xhr.open(method, url, async, [user-name], [user-pass])
//三、事件监听:一般监听的都是readystatechange事件(Ajax状态改变事件),基于这个事件可以获取服务器返回的响应头响应主体
xhr.onreadystatechange = () => {
	if(xhr.readyState === 4 && xhr.status === 200){
		console.log(xhr.responseText);
	}
};
//四、发送Ajax请求:从这步开始,当前Ajax任务开始,如果Ajax是同步的,后续代码不会执行,要等到Ajax状态成功后再执行
xhr.send([请求主体内容])

二、关于HTTP请求方式:

GET: 从服务器获取数据

POST: 向服务器推送数据

DELETE: 删除服务器端的某些内容

PUT: 向 服务器存放一些内容

HEAD: 只想获取服务器返回的响应头信息,不要响应主体的内容

OPTIONS: 一般使用它向服务器发送一个探测性请求,如果返回了信息,说明当前客户端和服务器端建立了连接,可以继续执行其他请求

TRACE: axios这个Ajax类库基于cross-domain进行跨域请求的时候,就是先发送OPTIONS进行探测尝试。如果能连通服务器,才会继续发送其它的请求。

GET 和 POST的区别:

【传递给服务器信息的方式不一样】

GET通过url字符串传参,POST通过请求主体

[GET]
xhr.open('GET', '/tmp/list?xxx=xxx&xxx=xxx')

[POST]
xhr.send('xxx=xxx')
(一般是url-encode格式)
【GET不安全 POST相对安全】

因为 GET是基于”问号传参“把信息传递给服务器的,容易被hack进行url劫持,post是基于请求主体传递的。

【GET会产生不可控制的缓存,POST不会】

不可控:是浏览器的自主记忆,无法通过JS控制。 解决方案

xhr.open('GET', `/temp/list?lx=1000&_=${Math.random()}`);
其他区别:
  • GET在浏览器回退时是无害的,而POST会再次提交请求
  • GET请求会被浏览器主动缓存,而POST不会,除非手动设置
  • GET请求参数会被完整保留在浏览器历史记录中,而POST不会
  • GET请求只能进行url编码,而POST支持多种编码方式

三、Ajax状态 ready--state

0 => UNSENT 刚开始创建xhr, 还没有发送

1 => OPENED 已经执行了open这个操作

2 => HEADERS_RESERVED 已经发送Ajax,响应头已经被客户端接受

3 => LOADING 响应主体内容正在返回

4 => DONE 响应主体已经被客户端接收

四、HTTP网络状态码 status

根据状态码能够清楚的反映出当前交互的结果和原因

1XX :指示信息-表示请求已接受、继续处理

2XX :成功 - 表示请求已被成功接收

3XX :成功,但是已经重定向

4XX : 客户端错误

5XX : 服务端错误

具体举例:

200 OK: 客户端请求成功

206 Partial Content: 客户发送了一个带有Range头的GET请求,服务器完成了它

301 Moved Permamently: 已经永久转至新的url

302 Found: 临时转至新的url,当一台服务器达到最大并发数,会转移服务器处理

304 Not Modified: 服务器告诉客户,原来的缓存可以继续使用,如CSS/JS/HTML/IMG,Ctrl+F5 304缓存失效

400 Bad Request: 客户端有语法错误,服务器不能理解

401 Unauthorized: 请求未经授权

403 Forbidden: 对被请求页面的访问被禁止

404 Not Found: 请求资源不存在

413 Request Entity Too Large 和服务器交互的内容资源超过最大大小

500 Interval Server Error 服务器错误,原来的缓存还能使用

503 Service Unavailable

五、关于XHR的属性和方法

xhr.response 响应主体内容

xhr.responseText 响应的内容是字符串(JSON或XML文档)

xhr.responseXML 响应的内容是xml

xhr.status 返回的HTTP状态码

xhr.statusText 状态码的描述

xhr.timeout 设置请求超时的时间

xhr.timeout = 1000
xhr.ontimeout = () => {
	console.log(‘请求超时’)
}

xhr.withCredentials 是否允许跨域(false)

xhr.abort() 强制中断Ajax请求

xhr.abort();
xhr.onabort = () => {}

xhr.getAllResponseHeaders() 获取所有响应头信息

xhr.getResponseHeader([key])例如:xhr.getResponseHeader('date')就是获取响应头中的服务器时间

xhr.open() 打开url请求

xhr.overrideMimeType() 重写MIME类型

xhr.send()发送Ajax请求,参数为请求主体对象

xhr.setRequestHeader() 设置自定义请求头信息(不能出现中文),必须在open之后设置

//小例子
xhr.onreadystatechange = () => {
	if(!/^(2|3)\d{2}$/.test(xhr.status))return;//证明服务器已经返回内容了
	if(xhr.readyState === 2){
		let time = xhr.getResponseHeader('date');
	}
	if(xhr.readyState === 4 && xhr.status === 200){
		JSON.parse(xhr.responseText);
	}
}

六、异步和同步的区别

异步:

let xhr = new XMLHttpRequest();
xhr.open('GET', 'xxx', true);
xhr.onreadystatechange = () => {
	if(xhr.readyState === 2) {
		console.log(1);
	}
	if(xhr.readyState === 4) {
		console.log(2)
	} 
}
xhr.send(); 
console.log(3)
//3 1 2 

同步:

let xhr = new XMLHttpRequest();
xhr.open('GET', 'xxx', false);
xhr.onreadystatechange = () => {
	if(xhr.readyState === 2) {
		console.log(1);
	}
	if(xhr.readyState === 4) {
		console.log(2)
	} 
}
xhr.send(); //任务开始,只要当前Ajax请求这件事没完成(readyState没到4),什么都不能做
console.log(3)
//2 3 为什么呢?
//由于是同步编程,主任务队列在状态没有变成4之前一直被Ajax请求占用,其他事件做不了。
//所以,只有readyState变成4才能执行方法。

let xhr = new XMLHttpRequest();
xhr.open('GET', 'xxx', false);
xhr.send(); //任务开始,只要当前Ajax请求这件事没完成(readyState没到4),什么都不能做
//现在状态已经为4
xhr.onreadystatechange = () => {
	if(xhr.readyState === 2) {
		console.log(1);
	}
	if(xhr.readyState === 4) {
		console.log(2)
	} 
}

console.log(3)
//3
//因此采用异步Ajax

七、jQuery中Ajax

/**
 * DATA:
 *	如果是GET请求是基于问号传参过去的
 *	如果是POST请求是基于请求主体传递过去的
 *	data的值可以是对象也可以是字符串(一般常用对象):
 *		如果是对象,jq会把对象转换为 xxx=xxx 的模式(x-www-form-urlencoded)
 * DATA-TYPE:预设置获取结果的数据格式 TEXT/JSON/JSONP/HTML/XML/SCRIPT(服务器返回给客户端的响应主体中的内容一般是字符串,
 *	而设置DATA-TYPE='json',jq会内部把获取的字符串转化为JSON格式的对象 => 它不影响服务器返回的结果,只是二次处理结果)
 * ASYNC:设置是否异步
 * CACHE:设置是否缓存,当设置FALSE,并且get请求,JQ会在请求的url地址末尾加随机数
 * SUCCESS:回调函数,当Ajax请求成功执行,JQ执行回调函数时把响应主体中获取的结果(二次处理)当做参数
 * ERROR: 请求失败后执行的回调函数
 */
$.ajax({
	url: 'xxx',
	method: 'GET',
	data: null,
	dataType: 'json',
	async: true,
	cache: true,
	success: (result, textStatus, xhr) => {},
	error: () => {}
})

八、无敌手写

原生JS封装 ajax(jQ版本)

~ function (window) {
  function AJAX(options) {
    return new AJAX.prototype.init(options);
  }
  function init(options = {}){
    let {
      url,
      method = 'GET',
      data = null,
      dataType = 'JSON',
      async = true,
      cache = true,
      success,
      error
    } = options;
    //=>MOUNT 把配置项挂载到实例上
    ['url', 'method', 'data', 'dataType', 'async', 'cache', 'success', 
    'error'].forEach(item => {
      this[item] = eval(item);
    });
  }
  
  AJAX.prototype = {
    constructor: AJAX,
    init,
    sendAjax(){
      this.handleCache();
      this.handleData();
      //send
      let {method, url, async, error, success} = this;
      //SEND发送请求
      let xhr = new XMLHttpRequest();
      xhr.open(method, url, async);
      xhr.onreadystatechange = () => {
        if(xhr.readyState === 4){
          if(!/^(2|3)\d{2}$/.test(xhr.status)){
            error && error(xhr.statusText, xhr)
          }
          //处理DATA-TYPE
          let result = this.handleDataType(xhr);
          success && success(result, xhr);
        }
      };
      xhr.send();
    },
    handleDataType(xhr) { 
      let dataType = this.dataType.toUpperCase(),
          result = xhr.responseText;
      switch (dataType) {
        case 'TEXT':
          break;
        case 'JSON':
          result = JSON.parse(result);
          break;
        case 'XML':
          result = xhr.responseXML;
          break;
      }  
      return result;    
    },
    handleCache() {
      let {url, method, cache} = this;
      if(/^GET$/i.test(method) && cache==false){
        url += `${this.check()}=${+(new Date())}`;
      }
    },
    handleData() {
      let {data, method} = this;
      if(!data) return;
      if(typeof data === 'object'){
        //如果是一个对象,我们把它转换为x-www-form-urlencoeded模式
        for(let key in data){
          if(data.hasOwnProperty(key)){
            str += `${key}=${data[key]}`;
          }
        }
        data=str.substring(0,str.length);
      }
      if(/^(GET|DELETE|HEAD|TRACE|OPTIONS)$/i.test(method)){
        this.url += `${this.check()}${data}`;
        this.data = null;
        return;
      }
      this.data = data; //POST处理方式
    },
    check() {
      return this.url.indexOf('?')>-1?'&':'?';
    }
  }
  init.prototype = AJAX.prototype;

  window.ajax = AJAX;
}(window)

基于Promise用原生JS手撸Ajax(axios版本)

~ function (window) {
  //设置默认的参数配置项  
  let _default = {
    method: 'GET',
    url: '',
    baseURL: '',
    headers: {},
    dataType: 'JSON',
    data: null, //POST系列
    params: null, //GET系列
    cache: true
  };
  //基于Promise设计模式管理Ajax
  let ajaxPromise = function axios() {
    let {
      url,
      baseURL,
      data,
      dataType,
      headers,
      cache,
      params
    } = options;
    //=>把传递的参数进一步进行处理
    if(/^(GET|DELETE|HEAD|OPTIONS)$/.test(method)){
      //GET参数
      if(params) {
        url += `${ajaxPromise.check(url)}${ajaxPromise.formatData(params)}`
      }
      if(cache === false){
        url += `${ajaxPromise.check(url)}_=${+(new Date())}`
      }
      data= null;//GET系列请求主体为空
    }else{
      //POST系列
      if(data){
        data = ajaxPromise.formatData(data);
      }
    }
    //=>基于Promise发送Ajax
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest();
      xhr.open(method, `${baseURL}${url}`);
      if(headers != null && typeof headers === 'object'){
        for(let attr in headers){
          if(headers.hasOwnProperty(attr)){
            let val = headers[attr];
            if(/[\u4e00-\u9fa5]/.test(val)){
              val = encodeURIComponent(val);
            }
            xhr.setRequestHeader(attr, headers[attr]);
          }
        }
      }
      //=>如果headers存在,我们需要设置请求头
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4){
          if(/^(2|3)\d{2}$/.test(xhr.status)){
            let result = xhr.responseText;
            dataType = dataType.toUpperCase();
            dataType === 'JSON'?result = JSON.parse(result):(dataType === 'XML'?result = xhr.responseXML : null);
            resolve(result, xhr);
            return;
          }
          reject(xhr.statusText, xhr);
        }
      }
      xhr.send(data);
    })
  }

  ajaxPromise.defaults = _default;

  ajaxPromise.formatData = function formatData(){
    let str = ``;
    for(let attr in obj) {
      if(obj.hasOwnProperty(attr)){
        str += `${attr}=${obj[attr]}&`;
      }
      return str.substring(0, str.length-1)
    }
  }

  ajaxPromise.check = function check(url){
    return url.indexOf('?')>-1?'&':'?';
  }

  //GET系列 
  ['get', 'delete', 'head', 'options'].forEach(item => {
    ajaxPromise[item] = (url, options = {}) => {
      options = {
        ..._default,
        ...options, 
        url, 
        method: item.toUpperCase()
      };
      return ajaxPromise(options);
    }
  })
  //POST系列
  ['post', 'put', 'patch'].forEach(item => {
    ajaxPromise[item] = (url, data = {}, options = {}) => {
      options = {
        ..._default,
        ...options, 
        url, 
        method: item.toUpperCase(),
        data
      };
      return ajaxPromise(options);
    }
  })

  window.ajaxPromise = ajaxPromise;
}(window)

关于Ajax一点小小的总结,希望对大家有帮助!