AJAX入门

AJAX(Async Javascript and Xml):在AJAX中的异步不是异步编程中的异步,而是泛指“局部刷新”,但是在AJAX请求中尽可能使用异步获取数据(因为异步数据获取不会阻塞下面代码的执行)

AJAX的操作

//=>创建AJAX实例:IE6中是不兼容的,使用的是new ActiveXObject来实现的
let xhr = new XMLHttpRequest();

//=>打开请求:发送请求之前的一些配置项
//1.HTTP METHOD 请求方式
//2.URL 向服务器端发送请求的API接口地址
//3.ASYNC 设置AJAX请求的同步异步,默认是异步(写TRUE也是异步),FALSE是同步,项目中都使用异步编程,防止阻塞后续代码执行
//4.USER-NAME/USER-PASS:用户名密码,一般不用
xhr.open([HTTP METHOD],[URL],[ASYNC],[USER-NAME],[USER-PASS]);

//=>事件监听:一般监听的都是 READY-STATE-CHANGE 事件(AJAX状态改变事件),基于这个事件可以获取服务器返回的响应头/响应主体内容
xhr.onreadystatechange= () => {
    if(xhr.readyState === 4 && xhr.status === 200){
       xhr.responseText;
    }
};

//=>发送AJAX请求:从这步开始,当前AJAX任务开始,如果AJAX是同步的,后续代码不会执行,要等到AJAX状态成功后在执行,反之异步不会
xhr.send([body]);

AJAX状态(READY-STATE)

0 => UNSENT  刚开始创建XHR,还没有发送
1 => OPENED  已经执行了OPEN操作
2 => HEADERS_RECEIVED 已经发送AJAX请求(AJAX任务开始),响应头信息已经被客户端接收了(响应头中包含了:服务器的时间、返回的HTTP状态码...)
3 => LOADING 响应主体内容正在返回
4 => DONE 响应主体内容已经被客户端接收

关于XHR的属性和方法

  • xhr.response 响应主体内容
  • xhr.responseText 响应主体的内容是字符串(JSON或者XML格式字符串都可以)
  • xhr.responseXML 响应主体的内容是XML文档
  • xhr.status 返回的HTTP状态码
  • xhr.statusText 状态码的描述
  • xhr.timeout 设置请求超时的时间
  • xhr.withCredentials 是否允许跨域
  • xhr.abort() 强制中断AJAX请求
  • xhr.getAllResponseHeaders() 获取所有响应头信息
  • xhr.getResponseHeader([key]) 获取KEY对应的响应头信息,例如:xhr.getResponseHeader('date')就是在获取响应有中的服务器时间
  • xhr.open() 打开URL请求
  • xhr.overrideMimeType() 重写MIME类型
  • xhr.send() 发送AJAX请求
  • xhr.setRequestHeader() 设置请求头
let xhr = new XMLHttpRequest();
xhr.open('GET', 'temp.json');
xhr.setRequestHeader('leonard', '@@@');//=>设置的请求头信息不能出现中文而且必须在OPEND之后才可以设置成功

/* xhr.timeout = 200;
   xhr.ontimeout = () => {
     console.log('请求超时,请稍后重试');
  };
*/

xhr.onreadystatechange = () => {
      if (!/^(2|3)\d{2}$/.test(xhr.status)) return; 
      if (xhr.readyState === 2) {
          let time = xhr.getResponseHeader('date');
          console.log(time, new Date(time));
      }

      if (xhr.readyState === 4) {
            console.log(xhr.responseText);
      }
  };
xhr.send(null); 

AJAX中的同步异步

  • 异步情况
    let xhr=new XMLHttpRequest();       // readyState 0
    xhr.open('GET','/temp/list',true);  // readyState 1
    xhr.onreadystatechange=()=>{
       if(xhr.readyState===2){console.log(1);}
       if(xhr.readyState===4){console.log(2);}
    };
    xhr.send();
    console.log(3);
    //=> 3 1 2
    
  • 同步情况:同步状态下主任务队列在readyState未变成4之前一直被占用,所以即使触发了onreadystatechange,也无法执行
    //=>注意send位置不同结果是不一样的
    let xhr=new XMLHttpRequest();
    xhr.open('GET','/temp/list',true);
    xhr.onreadystatechange=()=>{
       if(xhr.readyState===2){console.log(1);}
       if(xhr.readyState===4){console.log(2);}
    };
    xhr.send();
    console.log(3);
    //=> 2 3
    
    let xhr=new XMLHttpRequest();
    xhr.open('GET','/temp/list',true);
    xhr.send();
    xhr.onreadystatechange=()=>{
       if(xhr.readyState===2){console.log(1);}
       if(xhr.readyState===4){console.log(2);}
    };
    console.log(3);
    //=> 3
    

JQUERY中的AJAX

       我们发现,每次写AJAX请求都要经历“经典四步”,写起来特别麻烦,所以需要借助一些已经封装好了的AJAX(当然,你也可以自己封装2333)来进行数据请求,这样显得方便快捷

  • $.ajax([URL],[OPTIONS]) / $.ajax([OPTIONS])
  • $.get / $.post / $.getJSON / $.getScript 这些方法都是基于$.ajax构建出来的快捷方法
//=>$.ajax中OPTIONS常用的参数
 /*
  * URL:请求的API接口地址
  * METHOD:请求的方式
  * DATA:传递给服务器的信息可以放到DATA中
  *   如果是GET请求是基于问号传参传递过去的
  *   如果是POST请求是基于请求主体传递过去的
  *
  *   DATA的值可以是对象也可以是字符串(一般常用对象)
  *     如果是对象类型,JQ会把对象转换为 xxx=xxx&xxx=xxx 的模式(x-www-form-urlencoded)
  *     如果是字符串,我们写的是什么就传递什么
  *
  * DATA-TYPE:预设置获取结果的数据格式 TEXT/JSON/JSONP/HTML/SCRIPT/XML...(服务器返回给客户端的响应主体中的内容一般都是字符串[JSON格式居多]),而设置DATA-TYPE='JSON',JQ会内部把获取的字符串转为JSON格式的对象 =>“他不会影响服务返回的结果,只是把返回的结果进行了二次处理”
  * ASYNC:设置同步或者异步(TRUE->异步 FALSE->同步)
  * CACHE:设置GET请求下是否建立缓存(默认TRUE->建立缓存 FALSE->不建立缓存),当我们设置FALSE,并且当前请求是GET请求,JQ会在请求的URL地址末尾追加随机数(时间辍)
  *
  * SUCCESS:回调函数,当AJAX请求成功执行,JQ执行回调函数的时候会把从响应主体中获取的结果(可能二次处理了)当做参数传递给回调函数
  * ERROR:请求失败后执行的回调函数
  */
$.ajax({
   url: '/temp/list',
   method: 'GET',
   data: {
       name: 'Leonard',
       age: 18
   },
   dataType: 'json',
   async: true,
   cache: false,
   success: (result, textStatus, xhr) => {
       console.log(result);
       console.log(textStatus);
       console.log(xhr.getResponseHeader('date'));//=>jq重构的XHR
   },
   error: () => {}
});

       心动不如行动,接下来模仿JQ封装一个简易版的AJAX

~(function(window) {
    function AJAX(options) {
        return new init(options);
    }

    let init = function init(options = {}) {
        //=>初始化参数
        let {
            url,
            method = 'GET',
            data = null,
            dataType = 'JSON',
            async = true,
            cache = true,
            success,
            error
        } = options;

        //=>把配置项挂载到实例上
        ['url', 'method', 'data', 'dataType', 'async', 'cache', 'success', 'error'].forEach(item => {
            this[item] = eval(item);
        });

        //=>发送AJAX请求
        this.sendAjax();
    };

    AJAX.prototype = {
        constructor: AJAX,
        init,
        //=>发送AJAX请求
        sendAjax() {
            this.handleData();
            this.handleCache();

            //=>SEND
            let {method, url, async, error, success, data} = this,
                xhr = new XMLHttpRequest;
            xhr.open(method, url, async);
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4) {
                    //=>ERROR
                    if (!/^(2|3)\d{2}$/.test(xhr.status)) {
                        error && error(xhr.statusText, xhr);
                        return;
                    }
                    //=>SUCCESS
                    let result = this.handlDataType(xhr);
                    success && success(result, xhr);
                }
            };
            xhr.send(data);
        },
        //=>处理DATA-TYPE
        handlDataType(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;
        },
        //=>处理CACHE
        handleCache() {
            let {url, method, cache} = this;
            if (/^GET$/i.test(method) && cache === false) {
                //=>URL末尾追加时间辍
                url += `${this.check()}_=${+(new Date())}`;
                this.url = url;
            }
        },
        //=>处理DATA
        handleData() {
            let {data, method} = this;
            if (!data) return;
            //=>如果是个OBJECT对象,我们把它转换为x-www-form-urlencoded这种模式,方便后期传递给服务器
            if (typeof data === 'object') {
                let str = ``;
                for (let key in data) {
                    if (data.hasOwnProperty(key)) {
                        str += `${key}=${data[key]}&`;
                    }
                }
                data = str.substring(0, str.length - 1);
            }

            //=>根据请求方式不一样,传递给服务器的方式也不同
            if (/^(GET|DELETE|HEAD|TRACE|OPTIONS)$/i.test(method)) {
                this.url += `${this.check()}${data}`;
                this.data = null;
                return;
            }
            this.data = data;
        },
        //=>检测URL中是否存在问号
        check() {
            return this.url.indexOf('?') > -1 ? '&' : '?';
        }
    };

    init.prototype = AJAX.prototype;
    window.ajax = AJAX;
})(window);

       不过问题又来了,我们发现JQ中的AJAX在处理多个请求中会出现“回调地狱”的尴尬情形,那不行,虽然我的代码没那么美观,但也不能容忍它这么丑吧,得想想法子

//=>回调地狱出现
$.ajax({
     url: '/temp/list',
     success: result => {
         $.ajax({
             url: '/temp/info',
             success: result => {
                 $.ajax({
                     url: '/temp/add',
                     method:'POST',
                     success: result => {
                              ...
                     }
                 });
             }
         });
     }
});

//=>解决方案1:发布订阅
let $planA = $.Callbacks(),
    $planB = $.Callbacks();
$.ajax({
    url: '/temp/list',
    success: result => {
        $planA.fire(result);
    }
});

$planA.add(result => {
    $.ajax({
        url: '/temp/list',
        success: result => {
            $planB.fire(result);
        }
    });
});

$planB.add(result => {
    ...
});

//=>解决方案2:Promise
let queryA = function queryA() {
  return new Promise(resolve => {
     $.ajax({
         url: '/temp/list',
         success: resolve
     });
  });
};

let queryB = function queryB() {
    return new Promise(resolve => {
        $.ajax({
           url: '/temp/info',
           success: resolve
        });
    });
};

let queryC = function queryC() {
     return new Promise(resolve => {
         $.ajax({
             url: '/temp/add',
             method: 'POST',
             success: resolve
         });
     });
};

let promise = queryA();
promise.then(result => {
    console.log('A', result);
    return queryB();
}).then(result => {
    console.log('B', result);
    return queryC();
}).then(result => {
    console.log('C', result);
});

AXIOS

       那么既然promise这么香,为何不用一个基于promise来管理的AJAX库呢?AXIOS就是这样一个宝藏,它是一个基于promise管理的类库

  • 发送请求
// axios.get(url[,config])
axios.get('/temp/info', {
    params: {
        name: 'Leonard',
        age: 18
    }
});

//=>配置项中传递的内容都相当于基于请求主体专递给服务器
//=>但是传递给服务器的内容格式是RAW(JSON格式的字符串)
//=>不是X-WWW-FORM-URLENCODED
// axios.post(url[,data[,config]])
axios.post('/temp/add', {
     name: 'Leonard',
     age: 18
});

//=>一次并发多个请求
let sendAry = [
  axios.get('/temp/list'),
  axios.get('/temp/info'),
  axios.get('/temp/add')
]
axios.all(sendAry).then(result => {
  console.log(result);  //=>是一个数组,分别存储着每一个请求的结果
})
//=> 也可以写成
axios.all(sendAry).then(axios.spread((resA,resB,resC) => {...})
  • 请求返回信息
let promise = axios.get('/package.json', {
        params: {
            lx: 12
        }
    });
promise.then(result => {
        console.log(result);  //=>获取的结果是一个对象
        /*
         * data:从服务器获取的响应主体内容
         * headers:从服务器获取的响应的头信息
         * request:创建的AJAX实例
         * status:状态码
         * statusText:状态码的描述
         * config:基于AXIOS发送请求的时候做的配置项
         */
}).catch(msg => {
    console.log(msg);  //=>请求失败的原因
});
  • 初始化一些常用的配置项
//=>设置公共地址
axios.defaults.baseURL = 'https://www.easy-mock.com/mock/5b0412beda8a195fb0978627/temp';
//=>响应拦截处理,可以直接取出响应主体内容以便后续使用
axios.interceptors.response.use(result => result.data);
//=>自定义校验规则
axios.defaults.validateStatus = status => /^(2|3)\d{2}$/.test(status);
//=>设置在POST请求中基于请求主体向服务器发送内容的格式,默认是RAW,项目中常用的是URL-ENCODEED格式
axios.defaults.headers['Content-Type'] = 'appliction/x-www-form-urlencoded';
//=>对请求内容进行处理
axios.defaults.transformRequest = data => {
//=>DATA:就是请求主体中需要传递给服务器的内容(对象)
  let str = ``;
  for (let attr in data) {
      if (data.hasOwnProperty(attr)) {
           str += `${attr}=${data[attr]}&`;
      }
  }
  return str.substring(0, str.length - 1);
};
  • 更详细地进行学习可以参考axios
  • 最后,我们基于PROMISE再封装一款简易版AJAX库
~(function(window) {
    //=>设置默认的参数配置项
    let _default = {
        method: 'GET',
        url: '',
        baseURL: '',
        headers: {},
        dataType: 'JSON',
        data: null,
        params: null,
        cache: true
    };

    //=>基于PROMISE设计模式管理AJAX请求
    let ajaxPromise = function ajaxPromise(options) {
        let {url, baseURL, method, data, dataType, headers, cache, params} = options;

        //=>把传递的参数进一步进行处理
        if (/^(GET|DELETE|HEAD|OPTIONS)$/i.test(method)) {
            //=>GET系列
            if (params) {
                url += `${ajaxPromise.check(url)}${ajaxPromise.formatData(params)}`;
            }
            if (cache === false) {
                url += `${ajaxPromise.check(url)}_=${+(new Date())}`;
            }
            data = null;//=>请求主体清空
        } else {
            //=>POST系列
            if (data) {
                data = ajaxPromise.formatData(data);
            }
        }

        //=>基于PROMISE发送AJAX
        return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest;
            xhr.open(method, `${baseURL}${url}`);
            //=>如果HEADERS存在,我们需要设置请求头
            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中包含中文:我们把它进行编码
                            val = encodeURIComponent(val);
                        }
                        xhr.setRequestHeader(attr, val);
                    }
                }
            }
            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);
                        return;
                    }
                    reject(xhr.statusText);
                }
            };
            xhr.send(data);
        });
    };

    //=>把默认配置暴露出去,后期用户在使用的时候可以自己设置一些基础的默认值(发送AJAX请求的时候按照用户配置的信息进行处理)
    ajaxPromise.defaults = _default;

    //=>把对象转换为URLENCODED格式的字符串
    ajaxPromise.formatData = function formatData(obj) {
        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', 'delete', 'head', 'options'].forEach(item => {
        ajaxPromise[item] = function(url, options = {}) {
            options = {
                ..._default,//=>默认值或者基于defaults修改的值
                ...options,//=>用户调取方法传递的配置项
                url: url,//=>请求的URL地址(第一个参数:默认配置项和传递的配置项中都不会出现URL,只能这样获取)
                method: item.toUpperCase()
            };
            return ajaxPromise(options);
        };
    });

    ['post', 'put', 'patch'].forEach(item => {
        ajaxPromise[item] = function anonymous(url, data = {}, options = {}) {
            options = {
                ..._default,
                ...options,
                url: url,
                method: item.toUpperCase(),
                data: data
            };
            return ajaxPromise(options);
        };
    });

    window.ajaxPromise = ajaxPromise;
})(window);

你可能感兴趣的