浅析AJAX

57 阅读3分钟

什么是 AJAX

async javascript and xml:异步的 JS 和 XML

服务器渲染

在没有AJAX之前,我们一般是服务器渲染,将服务器中的数据整体渲染完后再返回给客户端,数据变化后,采用全局刷新的方式,将数据整体渲染后在传给服务器。

早起我们看到的内容都是在服务器端渲染完的 (JSP、PHPSASPsASP.NETSNODE...〕,客户端只是把所有渲染好的内容呈现在页面中而已,然而我们第一次渲染完,页面中的某部分数据要更新了,我们要让服务器整体重新的渲染一次,把最新的页面〔包含最新的数据〕返回给客户端,客户端只能整体刷新页面展示最新的内容,全局刷新性能和体验等都非常的差,而目服务器压力也很大。

当然服务器渲染也有其优点:

  1. 如果服务器性能比较高,页面呈现出来的速度 会快一些,因为只要从服务器拿到内容,一切信 息都已经准备好了;
  2. 由于内容在服务器端就已经宣染好了,所以页 面渲染完成后,在页面的源代码中都可以看到内 容,有利于SEO索引擎优化。

客户端渲染

后来,谷歌发明了AJAX技术,用在Gmail上,可以实现页面中内容局部刷新,而且渲染的操作交给客户端来做,体验好,减轻了服务器的压力,而且开始可以只把部分区域的数据获取到,滚动到某个区域后,在请求对应的数据也可以,实现数据的分批异步加载,提高性

客户端渲染缺点:由客户端渲染的内容没有出现在页面的原代码中,不利于SEO优化。

答疑

综上所述,AJAX的异步指的是局部刷新。

XML是可扩展的标记语言,用自己自定义的标签来存储数据的在很早以前,我们基于 AJAX 和服务器进行交互的数据格式一般都已 XML 格式为主,因为它能清晰展示出对应的数据和结构层级;但是到后面,流行了一种新的数据格式 JSON,它不仅比XML更清晰展示数据的结构,而且同样的数据存储,JSON更加轻量,也方便解析和相关的操作,所以现在前后端的数据交互都已 JSON 格式为主。所以AJAX应该叫做AJAJ(async javascript and json)

AJAX的核心操作

  1. 创建AJAX实例;
    let xhr=new XMLHttpRequest;
    //  IE低版本浏览器中用的是 new ActiveXObject() 高程三中JS惰性     编程思想,关于XHR的兼容处理
    
  2. 打开URL(配置发送请求的信息) ;
    xhr.open('GET','./json/xxx.json',true);
    
    open方法的五个参数:
    • METHOD:HTTP请求方式;
    • URL:请求地址(API接口地址);
    • ASYNC:设置同步或者异步,默认是TRUE异步,FALSE同步;
    • USER-NAME:传递给服务器的用户名;
    • USER-PASS:传递给服务器的密码。
  3. 监听AJAX状态,获取服务器响应的内容,AJAX状态码:0、1、2、3 、4;
    xhr.onreadystatechange=function(){
        if(xhr.readyState===4 && /^(2|3)\d{2}$/.test(xhr.status)){
           let result = xhr.responseText;
        }
     }
    
  4. 发送请求。
    xhr.send(null);
    // SEND中放的是请求主体的内容
    

AJAX任务,即发送一个请求给服务器,从服务器获取到对应的内容。是从send方法后开始,到“xhr.readyState===4”的时候算任务结束。

HTTP的请求方式

GET系列一般用于从服务器获取信息,POST系列一般用于给服务器推送信息,但是不论GET和POST都可以把信息传递给服务器,也能从服务器获取到结果,只不过是谁多谁少的问题。

  • GET:给的少,拿的多;
  • POST:给的多,拿的少。

GET系列请求

  1. GET;
  2. DELETE:一般应用于告诉服务器,从服务器上删除点东西;
  3. HEAD:只想获取响应头内容,告诉服务器响应主体内容不要了;
  4. OPTIONS:试探性请求,发个请求给服务器,看看服务器能不能接收到,能不能返回。

POST系列请求

  1. POST;
  2. PUT:和DELETE对应,一般是想让服务器把我传递的信息存储到服务器上(一般应用于文件和大型数据内容);

真实项目中用对应的请求方式,会使请求变的更加明确(语义化),不遵循这些方式也可以,最起码浏览器在语法上是允许的;但是这些是开发者们相互间约定俗成的规范;

GET系列和POST系列的本质区别:

  1. GET系列传递给服务器信息的方式一般采用问号传参,POST系列传递给服务器信息的方式一般采用设置请求主体;
  2. GET传递给服务器的内容比POST少,因为URL有最长大小限制(IE浏览器一般限制2KB,谷歌浏览器一般限制4~8KB,超过长度的部分自动被浏览器截取了)。
    // get请求
    xhr.open('GET','/list?name=zhufeng&year=10&xxx=xxx...');
    // post请求
    xhr.send('....') 
    
    post请求主体中传递的内容理论上没有大小限制,但是真实项目中,为了保证传输的速度,我们会自己限制一些;
  3. GET会产生缓存(缓存不是自己可控制的):因为请求的地址(尤其是问号传递的信息一样),浏览器有时候会认为你要和上次请求的数据一样,拿的是上一次信息;这种缓存我们不期望有,我们期望的缓存是自己可控制的;所以真实项目中,如果一个地址,GET请求多次,我们要去除这个缓存;
    解决办法设置随机数:
    xhr.open('GET','/list?name=zhufeng&_='+Math.random());
    
  4. GET相比较POST来说不安全:GET是基于问号传参传递给服务器内容,有一种技术叫做URL劫持,这样别人可以获取或者篡改传递的信息;而POST基于请求主体传递信息,不容易被劫持。

使用AJAX把信息传递给服务器

  1. 问号传参:
    xhr.open('GET','/getdata?xxx=xxx&xxx=xxx')
    
  2. 设置请求头:
    xhr.setRequestHeader([key],[value])
    
  3. 设置请求主体
    xhr.send(请求主体信息)
    

使用AJAX获取响应信息

  1. 通过响应头
    // getAllResponseHeaders和getResonseHeader
    xhr.getAllResponseHeaders();
    xhr.getResonseHeader([key])
    
  2. 通过响应主体(大部分信息都是基于响应主体返回的)
    let result = xhr.response;
    

AJAX的状态码

xhr.readyState // 获取状态码
  1. UNSEND 0 :未发送(创建一个XHR,初始状态是0);
  2. OPENED 1 :已经打开(执行了xhr.open);
  3. HEADERS_RECEIVED 2 :响应头信息已经返回给客户端(发送请求后,服务器会依次返回响应头和响应主体的信息);
  4. LOADING 3 : 等待服务器返回响应内容;
  5. DONE 4 : 响应主体信息已经返回给客户端。

xhr的属性和方法及事件

  1. 响应信息:
    xhr.response/xhr.responseText/xhr.responseXML
    
  2. 网络状态码及其描述:
    xhr.status/xhr.statusText
    
  3. AJAX超时时间:
    xhr.timeout
    
  4. 在在跨域请求中是否允许携带证书(携带cookie)
    xhr.withCredentials
    
  5. 中断请求
    xhr.abort();
    
  6. 获取所有的响应头信息
    xhr.getAllResponseHeaders();
    
  7. 根据键值对获取相应的响应头信息
    xhr.getResonseHeader([key]);
    
  8. 打开URL(配置发送请求的信息)
    xhr.open();
    
  9. 重写mime类型
    xhr.overriderMimeType();
    
  10. 发送请求
    xhr.send();
    
  11. 设置请求头信息
    xhr.setRequestHeader();
    

AJAX同步异步

异步执行

send放在最后

xhr的onreadystatechange事件由于是异步的,状态改变就会执行,所以在readyState等于2、3、4的时候,分别执行一次。

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
//=>设置事件绑定之前状态1
xhr.onreadystatechange = function () {
    console.log(xhr.readyState); //=>2 3 4
}
xhr.send(null);

onreadystatechange放在最后

由于是异步的,onreadystatechange放置在最后也可以,xhr的绑定事件是在状态改变的时候从事件队列里面拿出来执行,所以在readyState等于2、3、4的时候,分别执行一次。

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
xhr.send(null);
xhr.onreadystatechange = function () {
    console.log(xhr.readyState); //=>2 3 4
}

同步执行

send放在最后

使用AJAX同步编程,只有在onreadystatechange为4时,才能处理send下面的代码并且在onreadystatechange返回4时才执行绑定的事件,所以不能在状态码变为2和3的时候获取到响应头的信息,但是状态码变为4的时候也是可以获取到头和主体信息

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json',false);
xhr.onreadystatechange = function () {
    console.log(xhr.readyState); //=>4
}
xhr.send(null);

onreadystatechange放在最后

执行后,只有状态码变为4才会继续处理send下面的代码,onreadystatechange只有监听到状态变为4时才能执行事件,但此时send下面的代码发现状态已经成为了4,所以不再执行。没有监听到变化的状态。

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json',false);
xhr.send(null);
xhr.onreadystatechange = function () {
    console.log(xhr.readyState); //=>4
}

jQuery中的ajax

$.ajax() 是基于原生JS的AJAX四步操作进行封装的一个方法,主要是:

$.ajax([URL],[OPTIONS])
$.ajax([OPTIONS]) URL在配置项中(推荐)
$.get/post/getJSON/getScript()

配置项信息

  1. url:请求的API接口地址;
  2. method:HTTP请求方式,默认GET;
  3. data:传递给服务器的信息,默认null(可以是字符串,可以是对象,而且如果GET系列请求,JQ会自动把信息拼接到URL的末尾,基于问号传参传递给服务器;如果是POST请求,JQ会基于请求主体,把信息传递给服务器);
  4. dataType:预设服务器返回的结果格式(服务器返回的一般都是JSON格式的字符串,如果我们设置了DATA-TYPE,JQ会根据设置的类型,把服务器返回的结果处理为对应的格式),支持的内容text / json / xml / html / script / jsonp(跨域) =>不影响服务器返回的结果,只是把服务器返回的结果进行二次处理;
  5. async:是否为异步操作,默认是TRUE,代表异步操作;
  6. cache:缓存处理,只对GET系列请求有作用,默认是TRUE不处理缓存,当我们设置FALSE后,JQ帮我们在URL的末尾设置一个随机数;
  7. contentType:设置传递给服务器内容的格式类型 默认是"application/x-www-form-urlencoded"。客户端传递给服务器信息的格式(类型一般都是字符串),常用的:
    • form-data表单数据:JSON格式 '{"name":"xxx","lx":1}';
    • x-www-form-urlencoded:name=xxx&lx=1;
    • raw:纯文本格式;
  8. headers:设置请求头信息,他是一个对象;
  9. timeout:设置超时的时间;
  10. success:回调函数,当数据请求成功执行,方法中的参数就是从服务器获取的结果;
  11. error:回调函数,数据请求失败执行,方法中的参数是错误信息。

示例:

$.ajax({
  url"http://yapi.demo.qunar.com/mock/95100/project/list",
  method"POST",
  data: {
    name"zhufeng",
    lx"teacher",
  },
  dataType"json",
  asynctrue,
  cachefalse,
  headers: {},
  success(result, status, xhr) => {
    //=>xhr:是JQ帮我们处理过的AJAX实例
    console.log(result, status, xhr);
  },
});

自己尝试封装一个jQuery ajax库

*
 * 支持的参数配置项
 *    url
 *    method:'GET'
 *    data:null
 *    dataType:'json'
 *    async:true
 *    cache:true
 *    success:null
 *    error:null
 *    headers:null
 *    timeout:null
 */
(function (win) {
  function ajax(options = {}) {
    return new ajax.prototype.init(options);
  }

  let regGet = /^(GET|DELETE|HEAD|OPTIONS)$/i;
  let defaults = {
    url: "", //请求的API接口
    method: null, // 请求方式
    data: null, // 传递给服务器的信息,支持string和object,如果是object,我们需要把其处理为x-www-form-urlencoded格式,get请求是把信息作为问号参数传递给服务器
    dataType: "JSON", //把服务器返回结果处理成为对应的格式 JSON/TEXT/XML
    async: true, //是否异步
    cache: true, // 只对get请求有作用,设置为false在URL的末尾加随机数来清除缓存
    timeout: null, //超时时间
    headers: null, //设置请求头信息
    success: null, //从服务器获取成功后执行,把获取的结果、状态信息、XHR传递给它
    error: null, //获取失败后执行,把错误信息传递给它
  };

  ajax.fn = ajax.prototype = {
    constructor: ajax,
  };

  ajax.fn.init = function init(options) {
    this.options = Object.assign(defaults, options);
    this.xhr = null;
    this.send();
  };

  ajax.fn.send = function send() {
    let {
      url,
      method,
      async,
      data,
      cache,
      headers,
      timeout,
      dataType,
      success,
      error,
    } = this.options;
    let xhr = new XMLHttpRequest();
    this.xhr = xhr;

    // 处理DATA:如果是get请求,把处理后的data放在URL的末尾
    data = this.handleData();
    if (data !== null && regGet.test(method)) {
      url += `${this.checkAsk(url)}${data}`;
      data = null;
    }

    // 缓存处理,如果cache为false,请求为get请求,设置缓存
    if (cache === false && regGet.test(method)) {
      url += `${this.checkAsk(url)}_=${Math.random()}`;
    }

    xhr.open(method, url, async);
    // 超时处理
    timeout !== null ? (xhr.timeout = timeout) : null;
    // 设置请求头信息
    if (Object.prototype.toString.call(headers) === "[object Object]") {
      for (const key in headers) {
        if (headers.hasOwnProperty(key)) {
          xhr.setRequestHeader(key, encodeURIComponent(headers[key]));
        }
      }
    }

    xhr.onreadystatechange = () => {
      let { status, statusText, readyState, responseText, responseXML } = xhr;
      if (/^(2|3)\d{2}$/.test(status)) {
        if (readyState === 4) {
          switch (dataType.toUpperCase()) {
            case "JSON":
              responseText = JSON.parse(responseText ? responseText : null);
              break;
            case "XML":
              responseText = responseXML;
              break;
          }
          success && success(responseText, statusText, xhr);
        }
        return;
      }
      typeof error === "function" ? error(statusText, xhr) : null;
    };

    xhr.send(data);
  };

  ajax.fn.handleData = function handleData() {
    let { data } = this.options;
    if (data === null || typeof data === "string") return data;

    let str = ``;
    for (const key in data) {
      if (data.hasOwnProperty(key)) {
        str += `${key}=${data[key]}&`;
      }
    }
    str = str.substring(0, str.length - 1);
    return str;
  };

  ajax.fn.checkAsk = function checkAsk(url) {
    return url.indexOf("?") === -1 ? "?" : "&";
  };

  ajax.fn.init.prototype = ajax.fn;

  win.$ajax = ajax;
})(window);