AJAX 就是 XHR 的应用,无需多说。请看看小弟我第 N 次的封装工作。
首先声明命名空间,先占一个位:
// base goes here...
ajaxjs = {}; // Setup a top namespace
然后依赖一些方法:
/*
* --------------------------------------------------------
* 函数委托 参见
* http://blog.csdn.net/zhangxin09/article/details/8508128
* @return {Function}
* --------------------------------------------------------
*/
Function.prototype.delegate = function() {
var self = this, scope = this.scope, args = arguments, aLength = arguments.length, fnToken = 'function';
return function() {
var bLength = arguments.length, Length = (aLength > bLength) ? aLength
: bLength;
// mission one:
for (var i = 0; i < Length; i++)
if (arguments[i])
args[i] = arguments[i]; // 拷贝参数
args.length = Length; // 在 MS
// jscript下面,arguments作为数字来使用还是有问题,就是length不能自动更新。修正如左:
// mission two:
for (var i = 0, j = args.length; i < j; i++) {
var _arg = args[i];
if (_arg && typeof _arg == fnToken && _arg.late == true)
args[i] = _arg.apply(scope || this, args);
}
return self.apply(scope || this, args);
};
};
接着主角登场,XHR:
/*
* --------------------------------------------------------
* 封装 XHR,支持 GET/POST/PUT/DELETE/JSONP/FormData
* --------------------------------------------------------
*/
ajaxjs.xhr = {
json2url : ajaxjs.params.json2url,
// 注意 url 部分带有 # 的话则不能传参数过来
request : function(url, cb, args, cfg, method) {
var params = this.json2url(args), xhr = new XMLHttpRequest();
method = method.toUpperCase();
if (method == 'POST' || method == 'PUT') {
xhr.open(method, url);
} else
xhr.open(method, url + (params ? '?' + params : ''));
cb.url = url; // 保存 url 以便记录请求路径,可用于调试
xhr.onreadystatechange = this.callback.delegate(null, cb, cfg && cfg.parseContentType);
if (method == 'POST' || method == 'PUT') {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
} else {
xhr.send(null);
}
},
callback : function(event, cb, parseContentType) {
if (this.readyState === 4 && this.status === 200) {
var responseText = this.responseText.trim();
try {
if (!responseText)
throw '服务端返回空的字符串!';
var data = null;
switch (parseContentType) {
case 'text':
data = this.responseText;
break;
case 'xml':
data = this.responseXML;
break;
case 'json':
default:
data = JSON.parse(responseText);
}
} catch (e) {
alert('AJAX 错误:\n' + e + '\nThe url is:' + cb.url); // 提示用户 异常
}
if (!cb)
throw '你未提供回调函数';
cb(data, this);
}
},
/**
* e.g XMLHttpRequest.jsonp(
* 'http://u1.3gtv.net:2080/pms-service/section/content_list', { start:0,
* limit:10, sort:0, portalId:45, id:3290 }, function(json){
* console.log(json); } );
*
* @param url
* 请求远程路径
* @param params
* 参数
* @param cb
* 回调函数
* @param cfg
* 该次请求的配置
*/
jsonp : function(url, params, cb, cfg) {
var globalMethod_Token = 'globalMethod_'
+ parseInt(Math.random() * (200000 - 10000 + 1) + 10000);
if (!window.$$_jsonp)
window.$$_jsonp = {};
// Map<String, Function>
window.$$_jsonp[globalMethod_Token] = cb;
params = params || {};
params[cfg && cfg.callBackField || 'callBack'] = '$$_jsonp.'+ globalMethod_Token;
var scriptTag = document.createElement('script');
scriptTag.src = this.json2url(params, url);
document.getElementsByTagName('head')[0].appendChild(scriptTag);
},
form : function(form, cb, cfg) {
if (!window.FormData) {
var msg = 'The version of your browser is too old, please upgrade it.';
throw msg;
}
if (typeof form == 'string')
form = document.querySelector(form);
if (!form.action)
throw 'Please fill the url in ACTION attribute.';
// form.method always GET, so form.getAttribute('method') instead
var method = form.getAttribute('method').toLowerCase();
if (!method)
method = 'post';
form.addEventListener('submit', function(e, cb, cfg) {
if (cfg && cfg.beforeSubmit && cfg.beforeSubmit(form, formData) === false)
return;
e.preventDefault();// 禁止 form 默认提交
var form = e.target;
var json = {};
var formData = new FormData(form);
formData.forEach(function(value, key){
json[key] = value;
});
ajaxjs.xhr.post(form.action, cb, json);
}.delegate(null, cb, cfg));
}
};
所谓 GET/POST/PUT/DELETE,也就是 Open 时候参数不同,故适合 delegate 预先指定参数。
// GET 请求
ajaxjs.xhr.get = ajaxjs.xhr.request.delegate(null, null, null, null, 'GET');
ajaxjs.xhr.post = ajaxjs.xhr.request.delegate(null, null, null, null, 'POST');
ajaxjs.xhr.put = ajaxjs.xhr.request.delegate(null, null, null, null, 'PUT');
ajaxjs.xhr.dele = ajaxjs.xhr.request.delegate(null, null, null, null, 'DELETE');
表单 AJAX 请求使用了 H5 的 FormData 新特性,不需要自己去遍历表单了。
// 不需要了……
function serializeForm(formEl, isStringOutput, isIgnroEmpty /* 是否忽略空字符串的字段 */) {
var formData = {};
eachChild4form(
formEl,
function(el) {
var elType = el.type, key = el.name;
if (elType == "text" || elType == "hidden"
|| elType == "password" || elType == "textarea") {
formData[key] = getPrimitives(el.value);
} else if (elType == "radio" || elType == "checkbox") {
if (el.checked)// 选中才会加入数据
formData[key] = getPrimitives(el.value);
} else if (elType == "select-one"
|| elType == "select-multiple") {
for (var opt, optValue, p = 0, q = el.options.length; p < q; p++) {
opt = el.options[p];
if (opt.selected) {
optValue = opt.hasAttribute ? opt
.hasAttribute('value') : opt
.getAttribute('value') !== null
optValue = optValue ? opt.value : opt.text;
formData[key] = getPrimitives(optValue);
}
}
}
if (typeof formData[key] == 'string') { // url 编码
formData[key] = encodeURIComponent(formData[el.name]);
}
});
if (isIgnroEmpty) {
for ( var i in formData) {
if (formData[i] === "")
delete formData[i];
}
}
return isStringOutput ? utils.json2url(formData) : formData;
}
// 遍历表单
function eachChild4form(formEl, fn) {
// 豁免:没有 name 属性的不要;禁止了的不要;特定的字段不要
// !el.checked // 未选择的要来干嘛??
var ignore = /file|undefined|reset|button|submit/i;
for (var el, i = 0, j = formEl.elements.length; i < j; i++) {
el = formEl.elements[i];
if (!el.name || el.disabled || ignore.test(el.type))
continue;
fn(el, i);
}
}
// 判断form的内容是否有改变
function isFormChanged(formEl) {
var i = 0;
eachChild4form(formEl, function(el) {
switch (el.type) {
case "text":
case "hidden":
case "password":
case "textarea":
if (el.defaultValue != el.value)
return true;
break;
case "radio":
case "checkbox":
if (el.defaultChecked != el.checked)
return true;
break;
case "select-one":
i = 1;
case "select-multiple":
opts = el.options;
for (; i < opts.length; i++)
if (opts[i].defaultSelected != opts[i].selected)
return true;
break;
}
});
return false;
}
上面的代码留个纪念……
注意 AJAX 表单很多新手都会犯个错误,就是没有禁止 form 默认提交事件。你可以去掉 form 标签,或者:
e.preventDefault();// 禁止 form 默认提交
// or
return false;
AJAX 还有另外一种选择
Fetch API,貌似差别不大。