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 });