• 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 });
  • 相关阅读:
    数组排序去重
    js打印页面添加分页
    使用navicate可视化工具连接mysql数据库错误
    php_smarty模板引擎与.NET_VTemplate模板引擎对比
    JoshChen判断是否微信内置浏览器访问【转载】
    JoshChen毕业设计分享之班级网站-ASP.NET
    JoshChen防止前台恶意修改数据
    JoshChen安卓开发学习,从零开始(2)
    JoshChen安卓开发学习,从零开始(1)
    JoshChen模式笔记之php单例模式
  • 原文地址:https://www.cnblogs.com/wingzw/p/7365167.html
Copyright © 2020-2023  润新知