13.数据交换模块

13.1  Ajax概览

1             var xhr = new(self.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")
2             xhr.onreadystatechange = function() { //先绑定事件后open
3                 if(this.readyState === 4 && this.status === 200) {
4                     var div = document.createElement("div");
5                     div.innerHTML = this.responseText;
6                     document.body.appendChild(div);
7                 }
8             }
9             xhr.open("POST", "/ajax", true);

这是一个完整的Ajax程序,包括跨平台取得XMLHTTPRequest对象,绑定事件回调,判定处理状态,发出请求,设置首部,以及在POST请求时,通过send方法发送数据。上面七个步骤每一步都有兼容性问题或易用性处理。如果是跨域请求,IE8可能为XDomainRequest更为方便。

 13.2  优雅地取得XMLHttpRequest对象

13.3  XMLHttpRequest对象的事件绑定与状态维护

13.4  发生请求与数据

13.5  接收数据

13.6  上传文件

13.7  一个完整的Ajax实现

  1 //=========================================
  2 //  数据交互模块
  3 //==========================================
  4 //var reg = /^[^u4E00-u9FA5]*$/;
  5 define("ajax", this.FormData ? ["flow"] : ["ajax_fix"], function($) {
  6     var global = this,
  7             DOC = global.document,
  8             r20 = /%20/g,
  9             rCRLF = /
?
/g,
 10             encode = encodeURIComponent,
 11             decode = decodeURIComponent,
 12             rheaders = /^(.*?):[ 	]*([^
]*)
?$/mg,
 13             // IE的换行符不包含 

 14             rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
 15             rnoContent = /^(?:GET|HEAD)$/,
 16             rquery = /?/,
 17             rurl = /^([w.+-]+:)(?://([^/?#:]*)(?::(d+)|)|)/,
 18             //在IE下如果重置了document.domain,直接访问window.location会抛错,但用document.URL就ok了
 19             //http://www.cnblogs.com/WuQiang/archive/2012/09/21/2697474.html
 20             curl = DOC.URL,
 21             segments = rurl.exec(curl.toLowerCase()) || [],
 22             isLocal = rlocalProtocol.test(segments[1]),
 23             //http://www.cnblogs.com/rubylouvre/archive/2010/04/20/1716486.html
 24             s = ["XMLHttpRequest", "ActiveXObject('Msxml2.XMLHTTP.6.0')",
 25         "ActiveXObject('Msxml2.XMLHTTP.3.0')", "ActiveXObject('Msxml2.XMLHTTP')"];
 26     if (!"1" [0]) { //判定IE67
 27         s[0] = location.protocol === "file:" ? "!" : s[0];
 28     }
 29     for (var i = 0, axo; axo = s[i++]; ) {
 30         try {
 31             if (eval("new " + axo)) {
 32                 $.xhr = new Function("return new " + axo);
 33                 break;
 34             }
 35         } catch (e) {
 36         }
 37     }
 38 
 39     var accepts = {
 40         xml: "application/xml, text/xml",
 41         html: "text/html",
 42         text: "text/plain",
 43         json: "application/json, text/javascript",
 44         script: "text/javascript, application/javascript",
 45         "*": ["*/"] + ["*"] //避免被压缩掉
 46     },
 47     defaults = {
 48         type: "GET",
 49         contentType: "application/x-www-form-urlencoded; charset=UTF-8",
 50         async: true,
 51         jsonp: "callback"
 52     };
 53     //将data转换为字符串,type转换为大写,添加hasContent,crossDomain属性,如果是GET,将参数绑在URL后面
 54 
 55     function setOptions(opts) {
 56         opts = $.Object.merge({}, defaults, opts);
 57         if (typeof opts.crossDomain !== "boolean") { //判定是否跨域
 58             var parts = rurl.exec(opts.url.toLowerCase());
 59             opts.crossDomain = !!(parts && (parts[1] !== segments[1] || parts[2] !== segments[2] || (parts[3] || (parts[1] === "http:" ? 80 : 443)) !== (segments[3] || (segments[1] === "http:" ? 80 : 443))));
 60         }
 61         if (opts.data && typeof opts.data !== "object") {
 62             $.error("data必须为对象");
 63         }
 64         var querystring = $.param(opts.data);
 65         opts.querystring = querystring || "";
 66         opts.url = opts.url.replace(/#.*$/, "").replace(/^///, segments[1] + "//");
 67         opts.type = opts.type.toUpperCase();
 68         opts.hasContent = !rnoContent.test(opts.type); //是否为post请求
 69         if (!opts.hasContent) {
 70             if (querystring) { //如果为GET请求,则参数依附于url上
 71                 opts.url += (rquery.test(opts.url) ? "&" : "?") + querystring;
 72             }
 73             if (opts.cache === false) { //添加时间截
 74                 opts.url += (rquery.test(opts.url) ? "&" : "?") + "_time=" + Date.now();
 75             }
 76         }
 77         return opts;
 78     }
 79     //ajax主函数
 80     $.ajax = function(opts) {
 81         if (!opts || !opts.url) {
 82             $.error("参数必须为Object并且拥有url属性");
 83         }
 84         opts = setOptions(opts); //处理用户参数,比如生成querystring, type大写化
 85         //创建一个伪XMLHttpRequest,能处理complete,success,error等多投事件
 86         var dummyXHR = new $.XMLHttpRequest(opts);
 87         "complete success error".replace($.rword, function(name) { //绑定回调
 88             if (typeof opts[name] === "function") {
 89                 dummyXHR.bind(name, opts[name]);
 90                 delete opts[name];
 91             }
 92         });
 93         var dataType = opts.dataType; //目标返回数据类型
 94         var transports = $.ajaxTransports;
 95         var name = opts.form ? "upload" : dataType;
 96         var transport = transports[name] || transports.xhr;
 97         $.mix(dummyXHR, transport );//取得传送器的request, respond, preproccess
 98         if (dummyXHR.preproccess) { //这用于jsonp upload传送器
 99             dataType = dummyXHR.preproccess() || dataType;
100         }
101         //设置首部 1、Content-Type首部
102         if (opts.contentType) {
103             dummyXHR.setRequestHeader("Content-Type", opts.contentType);
104         }
105         //2、Accept首部
106         dummyXHR.setRequestHeader("Accept", accepts[dataType] ? accepts[dataType] + ", */*; q=0.01" : accepts["*"]);
107         for (var i in opts.headers) { //3 haders里面的首部
108             dummyXHR.setRequestHeader(i, opts.headers[i]);
109         }
110         // 处理超时
111         if (opts.async && opts.timeout > 0) {
112             dummyXHR.timeoutID = setTimeout(function() {
113                 dummyXHR.abort("timeout");
114             }, opts.timeout);
115         }
116         dummyXHR.request();
117         return dummyXHR;
118     };
119     "get,post".replace($.rword, function(method) {
120         $[method] = function(url, data, callback, type) {
121             if ($.isFunction(data)) {
122                 type = type || callback;
123                 callback = data;
124                 data = undefined;
125             }
126             return $.ajax({
127                 type: method,
128                 url: url,
129                 data: data,
130                 success: callback,
131                 dataType: type
132             });
133         };
134     });
135     function isValidParamValue(val) {
136         var t = typeof val; // If the type of val is null, undefined, number, string, boolean, return true.
137         return val == null || (t !== 'object' && t !== 'function');
138     }
139 
140     $.mix({
141         ajaxTransports: {
142             xhr: {
143                 //发送请求
144                 request: function() {
145                     var self = this;
146                     var opts = this.options;
147                     $.log("XhrTransport.request.....");
148                     var transport = this.transport = new $.xhr;
149                     if (opts.crossDomain && !("withCredentials" in transport)) {
150                         $.error("本浏览器不支持crossdomain xhr");
151                     }
152                     if (opts.username) {
153                         transport.open(opts.type, opts.url, opts.async, opts.username, opts.password);
154                     } else {
155                         transport.open(opts.type, opts.url, opts.async);
156                     }
157                     if (this.mimeType && transport.overrideMimeType) {
158                         transport.overrideMimeType(this.mimeType);
159                     }
160                     this.requestHeaders["X-Requested-With"] = "XMLHTTPRequest";
161                     for (var i in this.requestHeaders) {
162                         transport.setRequestHeader(i, this.requestHeaders[i]);
163                     }
164                     var dataType = this.options.dataType;
165                     if ("responseType" in transport && /^(blob|arraybuffer|text)$/.test(dataType)) {
166                         transport.responseType = dataType;
167                         this.useResponseType = true;
168                     }
169                     transport.send(opts.hasContent && (this.formdata || this.querystring) || null);
170                     //在同步模式中,IE6,7可能会直接从缓存中读取数据而不会发出请求,因此我们需要手动发出请求
171                     if (!opts.async || transport.readyState === 4) {
172                         this.respond();
173                     } else {
174                         if (transport.onerror === null) { //如果支持onerror, onload新API
175                             transport.onload = transport.onerror = function(e) {
176                                 this.readyState = 4; //IE9+ 
177                                 this.status = e.type === "load" ? 200 : 500;
178                                 self.respond();
179                             };
180                         } else {
181                             transport.onreadystatechange = function() {
182                                 self.respond();
183                             };
184                         }
185                     }
186                 },
187                 //用于获取原始的responseXMLresponseText 修正status statusText
188                 //第二个参数为1时中止清求
189                 respond: function(event, forceAbort) {
190                     var transport = this.transport;
191                     if (!transport) {
192                         return;
193                     }
194                     try {
195                         var completed = transport.readyState === 4;
196                         if (forceAbort || completed) {
197                             transport.onerror = transport.onload = transport.onreadystatechange = $.noop;
198                             if (forceAbort) {
199                                 if (!completed && typeof transport.abort === "function") { // 完成以后 abort 不要调用
200                                     transport.abort();
201                                 }
202                             } else {
203                                 var status = transport.status;
204                                 this.responseText = transport.responseText;
205                                 try {
206                                     //当responseXML为[Exception: DOMException]时,
207                                     //访问它会抛“An attempt was made to use an object that is not, or is no longer, usable”异常
208                                     var xml = transport.responseXML
209                                 } catch (e) {
210                                 }
211                                 if (this.useResponseType) {
212                                     this.response = transport.response;
213                                 }
214                                 if (xml && xml.documentElement) {
215                                     this.responseXML = xml;
216                                 }
217                                 this.responseHeadersString = transport.getAllResponseHeaders();
218                                 //火狐在跨城请求时访问statusText值会抛出异常
219                                 try {
220                                     var statusText = transport.statusText;
221                                 } catch (e) {
222                                     statusText = "firefoxAccessError";
223                                 }
224                                 //用于处理特殊情况,如果是一个本地请求,只要我们能获取数据就假当它是成功的
225                                 if (!status && isLocal && !this.options.crossDomain) {
226                                     status = this.responseText ? 200 : 404;
227                                     //IE有时会把204当作为1223
228                                     //returning a 204 from a PUT request - IE seems to be handling the 204 from a DELETE request okay.
229                                 } else if (status === 1223) {
230                                     status = 204;
231                                 }
232                                 this.dispatch(status, statusText);
233                             }
234                         }
235                     } catch (e) {
236                         // 如果网络问题时访问XHR的属性,在FF会抛异常
237                         // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
238                         if (!forceAbort) {
239                             this.dispatch(500, e + "");
240                         }
241                     }
242                 }
243             },
244             jsonp: {
245                 preproccess: function() {
246                     var namespace = DOC.URL.replace(/(#.+|W)/g, ''); //得到框架的命名空间
247                     var opts = this.options;
248                     var name = this.jsonpCallback = opts.jsonpCallback || "jsonp" + setTimeout("1");
249                     opts.url = opts.url + (rquery.test(opts.url) ? "&" : "?") + opts.jsonp + "=" + namespace + "." + name;
250                     //将后台返回的json保存在惰性函数中
251                     global[namespace][name] = function(json) {
252                         $[name] = json;
253                     };
254                     return "script"
255                 }
256             },
257             script: {
258                 request: function() {
259                     var opts = this.options;
260                     var node = this.transport = DOC.createElement("script");
261                     $.log("ScriptTransport.sending.....");
262                     if (opts.charset) {
263                         node.charset = opts.charset;
264                     }
265                     var load = node.onerror === null; //判定是否支持onerror
266                     var self = this;
267                     node.onerror = node[load ? "onload" : "onreadystatechange"] = function() {
268                         self.respond();
269                     };
270                     node.src = opts.url;
271                     $.head.insertBefore(node, $.head.firstChild);
272                 },
273                 respond: function(event, forceAbort) {
274                     var node = this.transport;
275                     if (!node) {
276                         return;
277                     }
278                     var execute = /loaded|complete|undefined/i.test(node.readyState);
279                     if (forceAbort || execute) {
280                         node.onerror = node.onload = node.onreadystatechange = null;
281                         var parent = node.parentNode;
282                         if (parent) {
283                             parent.removeChild(node);
284                         }
285                         if (!forceAbort) {
286                             var args = typeof $[this.jsonpCallback] === "function" ? [500, "error"] : [200, "success"];
287                             this.dispatch.apply(this, args);
288                         }
289                     }
290                 }
291             },
292             upload: {
293                 preproccess: function() {
294                     var opts = this.options;
295                     var formdata = new FormData(opts.form); //将二进制什么一下子打包到formdata
296                     $.each(opts.data, function(key, val) {
297                         formdata.append(key, val); //添加客外数据
298                     });
299                     this.formdata = formdata;
300                 }
301             }
302         },
303         ajaxConverters: {//转换器,返回用户想要做的数据
304             text: function(text) {
305                 return text || "";
306             },
307             xml: function(text, xml) {
308                 return xml !== void 0 ? xml : $.parseXML(text);
309             },
310             html: function(text) {
311                 return $.parseHTML(text);//一个文档碎片,方便直接插入DOM树
312             },
313             json: function(text) {
314                 return $.parseJSON(text);
315             },
316             script: function(text) {
317                 $.parseJS(text);
318             },
319             jsonp: function() {
320                 var json = $[this.jsonpCallback];
321                 delete $[this.jsonpCallback];
322                 return json;
323             }
324         },
325         getScript: function(url, callback) {
326             return $.get(url, null, callback, "script");
327         },
328         getJSON: function(url, data, callback) {
329             return $.get(url, data, callback, "jsonp");
330         },
331         upload: function(url, form, data, callback, dataType) {
332             if ($.isFunction(data)) {
333                 dataType = callback;
334                 callback = data;
335                 data = undefined;
336             }
337             return $.ajax({
338                 url: url,
339                 type: 'post',
340                 dataType: dataType,
341                 form: form,
342                 data: data,
343                 success: callback
344             });
345         },
346         //将一个对象转换为字符串
347         param: function(json, bracket) {
348             if (!$.isPlainObject(json)) {
349                 return "";
350             }
351             bracket = typeof bracket === "boolean" ? bracket : !0;
352             var buf = [],
353                     key, val;
354             for (key in json) {
355                 if (json.hasOwnProperty(key)) {
356                     val = json[key];
357                     key = encode(key);
358                     if (isValidParamValue(val)) { //只处理基本数据类型,忽略空数组,函数,正则,日期,节点等
359                         buf.push(key, "=", encode(val + ""), "&");
360                     } else if (Array.isArray(val) && val.length) { //不能为空数组
361                         for (var i = 0, n = val.length; i < n; i++) {
362                             if (isValidParamValue(val[i])) {
363                                 buf.push(key, (bracket ? encode("[]") : ""), "=", encode(val[i] + ""), "&");
364                             }
365                         }
366                     }
367                 }
368             }
369             buf.pop();
370             return buf.join("").replace(r20, "+");
371         },
372         //将一个字符串转换为对象
373         //$.deparam = jq_deparam = function( params, coerce ) {
374         //https://github.com/cowboy/jquery-bbq/blob/master/jquery.ba-bbq.js
375         unparam: function(url, query) {
376             var json = {};
377             if (!url || !$.type(url, "String")) {
378                 return json;
379             }
380             url = url.replace(/^[^?=]*?/ig, '').split('#')[0]; //去除网址与hash信息
381             //考虑到key中可能有特殊符号如“[].”等,而[]却有是否被编码的可能,所以,牺牲效率以求严谨,就算传了key参数,也是全部解析url。
382             var pairs = url.split("&"),
383                     pair, key, val, i = 0,
384                     len = pairs.length;
385             for (; i < len; ++i) {
386                 pair = pairs[i].split("=");
387                 key = decode(pair[0]);
388                 try {
389                     val = decode(pair[1] || "");
390                 } catch (e) {
391                     $.log(e + "decodeURIComponent error : " + pair[1], 3);
392                     val = pair[1] || "";
393                 }
394                 key = key.replace(/[]$/, ""); //如果参数名以[]结尾,则当作数组
395                 var item = json[key];
396                 if (item === void 0) {
397                     json[key] = val; //第一次
398                 } else if (Array.isArray(item)) {
399                     item.push(val); //第三次或三次以上
400                 } else {
401                     json[key] = [item, val]; //第二次,将它转换为数组
402                 }
403             }
404             return query ? json[query] : json;
405         },
406         serialize: function(form) { //表单元素变字符串
407             var json = {};
408             // 不直接转换form.elements,防止以下情况:   <form > <input name="elements"/><input name="test"/></form>
409             $.filter(form || [], function(el) {
410                 return el.name && !el.disabled && (el.checked === true || /radio|checkbox/.test(el.type));
411             }).forEach(function(el) {
412                 var val = $(el).val(),
413                         vs;
414                 val = Array.isArray(val) ? val : [val];
415                 val = val.map(function(v) {
416                     return v.replace(rCRLF, "
");
417                 });
418                 // 全部搞成数组,防止同名
419                 vs = json[el.name] || (json[el.name] = []);
420                 vs.push.apply(vs, val);
421             });
422             return $.param(json, false); // 名值键值对序列化,数组元素名字前不加 []
423         }
424     });
425     var transports = $.ajaxTransports;
426     $.mix(transports.jsonp, transports.script);
427     $.mix(transports.upload, transports.xhr);
428     /**
429      * 伪XMLHttpRequest类,用于屏蔽浏览器差异性
430      * var ajax = new(self.XMLHttpRequest||ActiveXObject)("Microsoft.XMLHTTP")
431      * ajax.onreadystatechange = function(){
432      *   if (ajax.readyState==4 && ajax.status==200){
433      *        alert(ajax.responseText)
434      *   }
435      * }
436      * ajax.open("POST", url, true);
437      * ajax.send("key=val&key1=val2");
438      */
439     $.XMLHttpRequest = $.factory($.Observer, {
440         init: function(opts) {
441             $.mix(this, {
442                 responseHeadersString: "",
443                 responseHeaders: {},
444                 requestHeaders: {},
445                 querystring: opts.querystring,
446                 readyState: 0,
447                 uniqueID: setTimeout("1"),
448                 status: 0
449             });
450             this.addEventListener = this.bind;
451             this.removeEventListener = this.unbind;
452             this.setOptions("options", opts); //创建一个options保存原始参数
453         },
454         setRequestHeader: function(name, value) {
455             this.requestHeaders[name] = value;
456             return this;
457         },
458         getAllResponseHeaders: function() {
459             return this.readyState === 4 ? this.responseHeadersString : null;
460         },
461         getResponseHeader: function(name, match) {
462             if (this.readyState === 4) {
463                 while ((match = rheaders.exec(this.responseHeadersString))) {
464                     this.responseHeaders[match[1]] = match[2];
465                 }
466                 match = this.responseHeaders[name];
467             }
468             return match === undefined ? null : match;
469         },
470         overrideMimeType: function(type) {
471             this.mimeType = type;
472             return this;
473         },
474         toString: function() {
475             return "[object XMLHttpRequest]";
476         },
477         // 中止请求
478         abort: function(statusText) {
479             statusText = statusText || "abort";
480             if (this.transport) {
481                 this.respond(0, statusText);
482             }
483             return this;
484         },
485         /**
486          * 用于派发success,error,complete等回调
487          * http://www.cnblogs.com/rubylouvre/archive/2011/05/18/2049989.html
488          * @param {Number} status 状态码
489          * @param {String} statusText 对应的扼要描述
490          */
491         dispatch: function(status, statusText) {
492             // 只能执行一次,防止重复执行
493             if (!this.transport) { //2:已执行回调
494                 return;
495             }
496             this.readyState = 4;
497             var eventType = "error";
498             if (status >= 200 && status < 300 || status === 304) {
499                 eventType = "success";
500                 if (status === 204) {
501                     statusText = "nocontent";
502                 } else if (status === 304) {
503                     statusText = "notmodified";
504                 } else {
505                     //如果浏览器能直接返回转换好的数据就最好不过,否则需要手动转换
506                     if (typeof this.response === "undefined") {
507                         var dataType = this.options.dataType || this.options.mimeType;
508                         if (!dataType) { //如果没有指定dataType,则根据mimeType或Content-Type进行揣测
509                             dataType = this.getResponseHeader("Content-Type") || "";
510                             dataType = dataType.match(/json|xml|script|html/) || ["text"];
511                             dataType = dataType[0];
512                         }
513                         try {
514                             this.response = $.ajaxConverters[dataType].call(this, this.responseText, this.responseXML);
515                         } catch (e) {
516                             eventType = "error";
517                             statusText = "parsererror : " + e;
518                         }
519                     }
520                 }
521             }
522             this.status = status;
523             this.statusText = statusText;
524             if (this.timeoutID) {
525                 clearTimeout(this.timeoutID);
526                 delete this.timeoutID;
527             }
528             this.rawFire = true;
529             this._transport = this.transport;
530             // 到这要么成功,调用success, 要么失败,调用 error, 最终都会调用 complete
531             if (eventType === "success") {
532                 this.fire(eventType, this.response, statusText, this);
533             } else {
534                 this.fire(eventType, this, statusText);
535             }
536             this.fire("complete", this, statusText);
537             delete this.transport;
538         }
539     });
540     if (typeof $.fixAjax === "function") {
541         $.fixAjax();
542     }
543     return $;
544 });
原文地址:https://www.cnblogs.com/wingzw/p/7365167.html