自己动手写一个React的Ajax

478 阅读6分钟

前言

在日常工作中,我经常使用Jquery的Ajax来获取接口数据。这几天有一个的官网要制作,由于网站比较小,有一些与服务器通信的接口处理,并没有涉及到复杂的交互功能。为了可以减少加载Jquery库的时间,也不用负担Jquery复杂的逻辑处理带来的性能消耗。我决定不使用jquery,自己写了一个原生的Ajax函数。

需求整理

一般来说,前端与服务器的通信是使用XHR对象的。我做的官网是有几个异域的接口,然而XHR对象是没有跨域功能的。所以我把JSONP整合进来。
接下来,我们来看看整体功能图:

输入参数

首先,我们定义一个Ajax函数,并设置了一些输入参数。


function ajax(options){

    var url = options.url || "", //请求的链接
        type = (options.type || "get").toLowerCase(), //请求的方法,默认为get
        data = options.data || null, //请求的数据
        contentType = options.contentType || "", //请求头
        dataType = options.dataType || "", //请求的类型
        async = options.async === undefined && true, //是否异步,默认为true.
        timeOut = options.timeOut, //超时时间。 
        before = options.before || function(){}, //发送之前执行的函数
        error = options.error || function(){}, //错误执行的函数
        success = options.success || function() {}; //请求成功的回调函数
}


参数表:


编码

一般来说,发送到后端的数据,若是包括中文或某些标点符号时,就要对发送的数据进行编码了。

  • 如果data为字符串,通过&分割,对键名与键值分别编码
  • 如果data为对象,把键值转化为字符串,再进行编码
  • 由于encodeURIComponent不对+编码,所以我们用replace方法手动编码
  • 若是使用get方法或JSONP,则数据是通过URL参数的方法传到后台,所以我们手动添加数据到URL
//编码数据
function setData() {
    var name, value;
    if (data) {
        if (typeof data === "string") {
            data = data.split("&");
            for (var i = 0, len = data.length; i < len; i++) {
                name = data[i].split("=")[0];
                value = data[i].split("=")[1];
                data[i] =  encodeURIComponent(name) + "=" + encodeURIComponent(value);
            }
            data = data.replace("/%20/g", "+");
        } else if (typeof data === "object") {
            var arr = [];
            for (var name in data) {
                var value = data[name].toString();
                name = encodeURIComponent(name);
                value = encodeURIComponent(value);
                arr.push(name + "=" + value);
            }
            data = arr.join("&").replace("/%20/g", "+");
        }
        //若是使用get方法或JSONP,则手动添加到URL中
        if (type === "get" || dataType === "jsonp") {
            url += url.indexOf("?") > -1 ? data : "?" + data;
        }
    }
}

XMLHttpRequerst

  • 创建XHR对象,并针对IE进行兼容性处理
  • 调用XHR的open方法,设置请求的方法,请求的链接,是否异步
  • 设置请求头
  • 添加监听,如果成功则执行success函数,报错则执行error函数
  • 调用XHR的send方法,发送数据。如果是get方法,我们已经通过setData方法把数据添加到URL中了,所以这里data设置为null
function createXHR() {
    //由于IE6的XMLHttpRequest对象是通过MSXML库中的一个ActiveX对象实现的。
    //所以创建XHR对象,需要在这里做兼容处理。
    function getXHR() {
        if (window.XMLHttpRequest) {
            return new XMLHttpRequest();
        } else {
            //遍历IE中不同版本的ActiveX对象
            var versions = ["Microsoft", "msxm3", "msxml2", "msxml1"];
            for (var i = 0; i < versions.length; i++) {
                try {
                    var version = versions[i] + ".XMLHTTP";
                    return new ActiveXObject(version);
                } catch (e) {}
            }
        }
    }
    //创建对象。
    xhr = getXHR();
    xhr.open(type, url, async);
    //设置请求头
    if (type === "post" && !contentType) {
        //若是post提交,则设置content-Type 为application/x-www-four-urlencoded
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
    } else if (contentType) {
        xhr.setRequestHeader("Content-Type", contentType);
    }
    //添加监听
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

                success(xhr.responseText);
            } else {
                 error(xhr.status, xhr.statusText);
            }
        }
    };
    //发送请求
    xhr.send(type === "get" ? null : data);
    
}

JSONP

  • 创建script标签
  • 设置回调函数名称
  • 监听回调函数
  • 设置URL,并添加到文档中
// 创建JSONP
function createJsonp() {
    var script = document.createElement("script"),
        timeName = new Date().getTime() + Math.round(Math.random() * 1000),
        callback = "JSONP_" + timeName;

    window[callback] = function(data) {
        document.body.removeChild(script);
        success(data);
    }
      script.src = url +  (url.indexOf("?") > -1 ? "" : "?") + "callback=" + callback;
    script.type = "text/javascript";
    document.body.appendChild(script);
}

超时设置

  • 设置一个全局的定时器标识,用来在回调函数中清除定时器
  • JSONP
    • 传入两个参数,一个是回调函数名,一个是script标签
    • 超时之后,移除监听函数,移除script标签
  • XHR
    • 超时之后,调用XHR的abort方法,停止请求
    • 由于执行abort()方法后,有可能触发onreadystatechange事件,所以设置一个timeout_bool标识,来忽略中止触发的事件。
var timeout_flag = null, //定时器标识
    timeout_bool = false;//是否请求超时
//设置请求超时
function setTime(callback, script) {
    if (timeOut !== undefined) {
        timeout_flag = setTimeout(function() {
            if (dataType === "jsonp") {
                delete window[callback];
                document.body.removeChild(script);

            } else {
                timeout_bool = true;
                xhr && xhr.abort();
            }
            console.log("timeout");

        }, timeOut);
    }
}

添加超时函数,并设置在回调成功后移除定时器

JSONP

// 创建JSONP
function createJsonp() {
    ……
    window[callback] = function(data) {
        clearTimeout(timeout_flag);
        document.body.removeChild(script);
        success(data);
    }
    ……
    document.body.appendChild(script);
    setTime(callback, script);
}

XHR

function createXHR() {
    ……    
    //添加监听
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (timeOut !== undefined) {
                //由于执行abort()方法后,有可能触发onreadystatechange事件,
                //所以设置一个timeout_bool标识,来忽略中止触发的事件。
                if (timeout_bool) {
                    return;
                }
                clearTimeout(timeout_flag);
            }
           ……
        }
    };
    //发送请求
    xhr.send(type === "get" ? null : data);
    setTime(); //请求超时
}

全部代码:

function Ajax(options) {
	//编码数据
	function setData() {
		//设置对象的遍码
		function setObjData(data, parentName) {
			function encodeData(name, value, parentName) {
				var items = [];
				name = parentName === undefined ? name : parentName + "[" + name + "]";
				if(typeof value === "object" && value !== null) {
					items = items.concat(setObjData(value, name));
				} else {
					name = encodeURIComponent(name);
					value = encodeURIComponent(value);
					items.push(name + "=" + value);
				}
				return items;
			}
			var arr = [],
				value;
			if(Object.prototype.toString.call(data) == '[object Array]') {
				for(var i = 0, len = data.length; i < len; i++) {
					value = data[i];
					arr = arr.concat(encodeData(typeof value == "object" ? i : "", value, parentName));
				}
			} else if(Object.prototype.toString.call(data) == '[object Object]') {
				for(var key in data) {
					value = data[key];
					arr = arr.concat(encodeData(key, value, parentName));
				}
			}
			return arr;
		};
		//设置字符串的遍码,字符串的格式为:a=1&b=2;
		function setStrData(data) {
			var arr = data.split("&");
			for(var i = 0, len = arr.length; i < len; i++) {
				name = encodeURIComponent(arr[i].split("=")[0]);
				value = encodeURIComponent(arr[i].split("=")[1]);
				arr[i] = name + "=" + value;
			}
			return arr;
		}

		if(data) {
			if(typeof data === "string") {
				data = setStrData(data);
			} else if(typeof data === "object") {
				data = setObjData(data);
			}
			data = data.join("&").replace("/%20/g", "+");
			//若是使用get方法或JSONP,则手动添加到URL中
			if(type === "get" || dataType === "jsonp") {
				url += url.indexOf("?") > -1 ? (url.indexOf("=") > -1 ? "&" + data : data) : "?" + data;
			}
		}
	}
	// JSONP
	function createJsonp() {
		var script = document.createElement("script"),
			timeName = new Date().getTime() + Math.round(Math.random() * 1000),
			callback = "JSONP_" + timeName;

		window[callback] = function(data) {
			clearTimeout(timeout_flag);
			document.body.removeChild(script);
			success(data);
		}
		script.src = url + (url.indexOf("?") > -1 ? "&" : "?") + "callback=" + callback;
		script.type = "text/javascript";
		document.body.appendChild(script);
		setTime(callback, script);
	}
	//设置请求超时
	function setTime(callback, script) {
		if(timeOut !== undefined) {
			timeout_flag = setTimeout(function() {
				if(dataType === "jsonp") {
					delete window[callback];
					document.body.removeChild(script);

				} else {
					timeout_bool = true;
					xhr && xhr.abort();
				}
				console.log("timeout");

			}, timeOut);
		}
	}

	// XHR
	function createXHR() {
		//由于IE6的XMLHttpRequest对象是通过MSXML库中的一个ActiveX对象实现的。
		//所以创建XHR对象,需要在这里做兼容处理。
		function getXHR() {
			if(window.XMLHttpRequest) {
				return new XMLHttpRequest();
			} else {
				//遍历IE中不同版本的ActiveX对象
				var versions = ["Microsoft", "msxm3", "msxml2", "msxml1"];
				for(var i = 0; i < versions.length; i++) {
					try {
						var version = versions[i] + ".XMLHTTP";
						return new ActiveXObject(version);
					} catch(e) {}
				}
			}
		}
		//创建对象。
		xhr = getXHR();
		xhr.open(type, url, async);
		//设置请求头
		if(type === "post" && !contentType) {
			//若是post提交,则设置content-Type 为application/x-www-four-urlencoded
			xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		} else if(contentType) {
			xhr.setRequestHeader("Content-Type", contentType);
		}
		//添加监听
		xhr.onreadystatechange = function() {
			if(xhr.readyState === 4) {
				if(timeOut !== undefined) {
					//由于执行abort()方法后,有可能触发onreadystatechange事件,
					//所以设置一个timeout_bool标识,来忽略中止触发的事件。
					if(timeout_bool) {
						return;
					}
					clearTimeout(timeout_flag);
				}
				if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {

					success(xhr.responseText);
				} else {
					error(xhr.status, xhr.statusText);
				}
			}
		};
		//发送请求
		xhr.send(type === "get" ? null : data);
		setTime(); //请求超时
	}

	var url = options.url || "", //请求的链接
		type = (options.type || "get").toLowerCase(), //请求的方法,默认为get
		data = options.data || null, //请求的数据
		contentType = options.contentType || "", //请求头
		dataType = options.dataType || "", //请求的类型
		async = options.async === undefined ? true : options.async, //是否异步,默认为true.
		timeOut = options.timeOut, //超时时间。 
		before = options.before || function() {}, //发送之前执行的函数
		error = options.error || function() {}, //错误执行的函数
		success = options.success || function() {}; //请求成功的回调函数
	var timeout_bool = false, //是否请求超时
		timeout_flag = null, //超时标识
		xhr = null; //xhr对角
	setData();
	before();
	if(dataType === "jsonp") {
		createJsonp();
	} else {
		createXHR();
	}
}

export default Ajax; //https://github.com/littleBlack520/ajax
export function Ajax_get(url, fun) {
	Ajax({
		type: "get",
		url: url, //添加自己的接口链接
		timeOut: 5000,
		before: function() {

		},
		success: function(str) {

			fun(str)
		},
		error: function() {

		}
	});
}
export function Ajax_post(url, data, fun) {
	Ajax({
		type: "post",
		url: url,
		data: JSON.stringify(data),
		timeOut: 5000,
		before: function() {

		},
		success: function(str) {
			fun(str);
		},
		error: function() {

		}
	});
}


ES6 使用:

import Ajax, { Ajax_get, Ajax_post } from './Ajax';
 Ajax_get("./test.json", function(str) {
			alert("ajax is OK");
			console.log(str);
		});