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,貌似差别不大。