13.1 Ajax概览javascript
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更为方便。php
13.2 优雅地取得XMLHttpRequest对象html
13.3 XMLHttpRequest对象的事件绑定与状态维护java
13.4 发生请求与数据node
13.5 接收数据jquery
13.6 上传文件git
13.7 一个完整的Ajax实现github
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 = /\r?\n/g, 10 encode = encodeURIComponent, 11 decode = decodeURIComponent, 12 rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, 13 // IE的换行符不包含 \r 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 //设置首部 一、Content-Type首部 102 if (opts.contentType) { 103 dummyXHR.setRequestHeader("Content-Type", opts.contentType); 104 } 105 //二、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, "\r\n"); 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 });