当前位置: 美高梅棋牌 > 智能硬件 > 正文

jQuery源码分析系列(33) : AJAX中的前置过滤器和请求

时间:2019-09-02 11:32来源:智能硬件
jQuery1.5以后,AJAX模块提供了三个新的方法用于管理、扩展AJAX请求,分别是:   美高梅棋牌 ,  这里先分析前置过滤器和请求分发器,类型转换器下一节再讲。 15.4.1  前置过滤器和请

jQuery1.5以后,AJAX模块提供了三个新的方法用于管理、扩展AJAX请求,分别是:

 

美高梅棋牌, 

这里先分析前置过滤器和请求分发器,类型转换器下一节再讲。
15.4.1  前置过滤器和请求分发器的初始化
前置过滤器和请求分发器在执行时,分别遍历内部变量prefilters和transports,这两个变量在jQuery加载完毕后立即初始化,初始化的过程很有意思。
首先,prefilters和transports被置为空对象:
prefilters = {}, // 过滤器
transports = {}, // 分发器
然后,创建jQuery.ajaxPrefilter和jQuery.ajaxTransport,这两个方法都调用了内部函数addToPrefiltersOrTransports,addToPrefiltersOrTransports返回一个匿名闭包函数,这个匿名闭包函数负责将单一前置过滤和单一请求分发器分别放入prefilters和transports。我们知道闭包会保持对它所在环境变量的引用,而jQuery.ajaxPrefilter和jQuery.ajaxTransport的实现又完全一样,都是对Map结构的对象进行赋值操作,因此这里利用闭包的特性巧妙的将两个方法的实现合二为一。函数addToPrefiltersOrTransports可视为模板模式的一种实现。
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), // 通过闭包保持对prefilters的引用,将前置过滤器添加到prefilters
ajaxTransport: addToPrefiltersOrTransports( transports ), // 通过闭包保持对transports的引用,将请求分发器添加到transports
 
// 添加全局前置过滤器或请求分发器,过滤器的在发送之前调用,分发器用来区分ajax请求和script标签请求
function addToPrefiltersOrTransports( structure ) {
    // 通过闭包访问structure
    // 之所以能同时支持Prefilters和Transports,关键在于structure引用的时哪个对象
    // dataTypeExpression is optional and defaults to "*"
    // dataTypeExpression是可选参数,默认为*
    return function( dataTypeExpression, func ) {
       // 修正参数
       if ( typeof dataTypeExpression !== "string" ) {
           func = dataTypeExpression;
           dataTypeExpression = "*";
       }
 
       if ( jQuery.isFunction( func ) ) {
           var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), // 用空格分割数据类型表达式dataTypeExpression
              i = 0,
              length = dataTypes.length,
              dataType,
              list,
              placeBefore;
 
           // For each dataType in the dataTypeExpression
           for(; i < length; i++ ) {
              dataType = dataTypes[ i ];
              // We control if we're asked to add before
              // any existing element
              // 如果以+开头,过滤+
              placeBefore = /^+/.test( dataType );
              if ( placeBefore ) {
                  dataType = dataType.substr( 1 ) || "*";
              }
              list = structure[ dataType ] = structure[ dataType ] || [];
              // then we add to the structure accordingly
              // 如果以+开头,则插入开始位置,否则添加到末尾
              // 实际上操作的是structure
              list[ placeBefore ? "unshift" : "push" ]( func );
           }
       }
    };
}
最后,分别调用jQuery.ajaxPrefilter和jQuery.ajaxTransport填充prefilters和transports.
填充prefilters:
// Detect, normalize options and install callbacks for jsonp requests
// 向前置过滤器对象中添加特定类型的过滤器
// 添加的过滤器将格式化参数,并且为jsonp请求增加callbacks
// MARK:AJAX模块初始化
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
 
    var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
       ( typeof s.data === "string" ); // 如果是表单提交,则需要检查数据
 
    // 这个方法只处理jsonp,如果json的url或data有jsonp的特征,会被当成jsonp处理
    // 触发jsonp的3种方式:
    if ( s.dataTypes[ 0 ] === "jsonp" || // 如果是jsonp
       s.jsonp !== false && ( jsre.test( s.url ) || // 未禁止jsonp,s.url中包含=?& =?$ ??
              inspectData && jsre.test( s.data ) ) ) { // s.data中包含=?& =?$ ??
 
       var responseContainer,
           jsonpCallback = s.jsonpCallback =
              jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, // s.jsonpCallback时函数,则执行函数用返回值做为回调函数名
           previous = window[ jsonpCallback ],
           url = s.url,
           data = s.data,
           // jsre = /(=)?(&|$)|??/i; // =?& =?$ ??
           replace = "$1" + jsonpCallback + "$2"; // $1 =, $2 &|$
 
       if ( s.jsonp !== false ) {
           url = url.replace( jsre, replace ); // 将回调函数名插入url
           if ( s.url === url ) { // 如果url没有变化,则尝试修改data
              if ( inspectData ) {
                  data = data.replace( jsre, replace ); // 将回调函数名插入data
              }
              if ( s.data === data ) { // 如果data也没有变化
                  // Add callback manually
                  url += (/?/.test( url ) ? "&" : "?") + s.jsonp + "="

1.前置过滤器 jQuery. ajaxPrefilter

  • jsonpCallback; // 自动再url后附加回调函数名
                  }
               }
           }
         
           // 存储可能改变过的url、data
           s.url = url;
           s.data = data;
     
           // Install callback
           window[ jsonpCallback ] = function( response ) { // 在window上注册回调函数
               responseContainer = [ response ];
           };
     
           // Clean-up function
           jqXHR.always(function() {
               // Set callback back to previous value
               // 将备份的previous函数恢复
               window[ jsonpCallback ] = previous;
               // Call if it was a function and we have a response
               // 响应完成时调用jsonp回调函数,问题是这个函数不是自动执行的么?
               if ( responseContainer && jQuery.isFunction( previous ) ) {
                  window[ jsonpCallback ]( responseContainer[ 0 ] ); // 为什么要再次执行previous呢?
               }
           });
     
           // Use data converter to retrieve json after script execution
           s.converters["script json"] = function() {
               if ( !responseContainer ) { // 如果
                  jQuery.error( jsonpCallback + " was not called" );
               }
               return responseContainer[ 0 ]; // 因为是作为方法的参数传入,本身就是一个json对象,不需要再做转换
           };
     
           // force json dataType
           s.dataTypes[ 0 ] = "json"; // 强制为json
     
           // Delegate to script
           return "script"; // jsonp > json
        }
    });
    // Handle cache's special case and global
    // 设置script的前置过滤器,script并不一定意思着跨域
    // MARK:AJAX模块初始化
    jQuery.ajaxPrefilter( "script", function( s ) {
        if ( s.cache === undefined ) { // 如果缓存未设置,则设置false
           s.cache = false;
        }
        if ( s.crossDomain ) { // 跨域未被禁用,强制类型为GET,不触发全局时间
           s.type = "GET";
           s.global = false;
        }
    });
    填充transports:
    // Bind script tag hack transport
    // 绑定script分发器,通过在header中创建script标签异步载入js,实现过程很简介
    // MARK:AJAX模块初始化
    jQuery.ajaxTransport( "script", function(s) {
     
        // This transport only deals with cross domain requests
        if ( s.crossDomain ) { // script可能时json或jsonp,jsonp需要跨域,ajax模块大约有1/3的代码时跨域的
           // 如果在本域中设置了跨域会怎么处理呢?
     
           var script,
               head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; // 充分利用布尔表达式的计算顺序
     
           return {
     
               send: function( _, callback ) { // 提供与同域请求一致的接口
     
                  script = document.createElement( "script" ); // 通过创script标签来实现
     
                  script.async = "async";
     
                  if ( s.scriptCharset ) {
                      script.charset = s.scriptCharset; // 字符集
                  }
     
                  script.src = s.url; // 动态载入
     
                  // Attach handlers for all browsers
                  script.onload = script.onreadystatechange = function( _, isAbort ) {
     
                      if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
     
                         // Handle memory leak in IE
                         script.onload = script.onreadystatechange = null; // onload事件触发后,销毁事件句柄,因为IE内存泄漏?
     
                         // Remove the script
                         if ( head && script.parentNode ) {
                             head.removeChild( script ); // onloda后,删除script节点
    美高梅游戏官网平台,                     }
     
                         // Dereference the script
                         script = undefined; // 注销script变量
     
                         // Callback if not abort
                         if ( !isAbort ) {
                             callback( 200, "success" ); // 执行回调函数,200为HTTP状态码
                         }
                      }
                  };
                  // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
                  // This arises when a base node is used (#2709 and #4378).
                  // 用insertBefore代替appendChild,如果IE6的bug
                  head.insertBefore( script, head.firstChild );
               },
     
               abort: function() {
                  if ( script ) {
                      script.onload( 0, 1 ); // 手动触发onload事件,jqXHR状态码为0,HTTP状态码为1xx
                  }
               }
           };
        }
    });
     
    // Create transport if the browser can provide an xhr
    if ( jQuery.support.ajax ) {
        // MARK:AJAX模块初始化
        // 普通AJAX请求分发器,dataType默认为*
        jQuery.ajaxTransport(function( s ) { // *
           // Cross domain only allowed if supported through XMLHttpRequest
           // 如果不是跨域请求,或支持身份验证
           if ( !s.crossDomain || jQuery.support.cors ) {
     
               var callback;
     
               return {
                  send: function( headers, complete ) {
     
                      // Get a new xhr
                      // 创建一个XHR
                      var xhr = s.xhr(),
                         handle,
                         i;
     
                      // Open the socket
                      // Passing null username, generates a login popup on Opera (#2865)
                      // 调用XHR的open方法
                      if ( s.username ) {
                         xhr.open( s.type, s.url, s.async, s.username, s.password ); // 如果需要身份验证
                      } else {
                         xhr.open( s.type, s.url, s.async );
                      }
     
                      // Apply custom fields if provided
                      // 在XHR上绑定自定义属性
                      if ( s.xhrFields ) {
                         for ( i in s.xhrFields ) {
                             xhr[ i ] = s.xhrFields[ i ];
                         }
                      }
     
                      // Override mime type if needed
                      // 如果有必要的话覆盖mineType,overrideMimeType并不是一个标准接口,因此需要做特性检测
                      if ( s.mimeType && xhr.overrideMimeType ) {
                         xhr.overrideMimeType( s.mimeType );
                      }
     
                      // X-Requested-With header
                      // For cross-domain requests, seeing as conditions for a preflight are
                      // akin to a jigsaw puzzle, we simply never set it to be sure.
                      // (it can always be set on a per-request basis or even using ajaxSetup)
                      // For same-domain requests, won't change header if already provided.
                      // X-Requested-With同样不是一个标注HTTP头,主要用于标识Ajax请求.大部分JavaScript框架将这个头设置为XMLHttpRequest
                      if ( !s.crossDomain && !headers["X-Requested-With"] ) {
                         headers[ "X-Requested-With" ] = "XMLHttpRequest";
                      }
     
                      // Need an extra try/catch for cross domain requests in Firefox 3
                      // 设置请求头
                      try {
                         for ( i in headers ) {
                             xhr.setRequestHeader( i, headers[ i ] );
                         }
                      } catch( _ ) {}
     
                      // Do send the request
                      // This may raise an exception which is actually
                      // handled in jQuery.ajax (so no try/catch here)
                      // 调用XHR的send方法
                      xhr.send( ( s.hasContent && s.data ) || null );
     
                      // Listener
                      // 封装回调函数
                      callback = function( _, isAbort ) {
     
                         var status,
                             statusText,
                             responseHeaders,
                             responses, // 响应内容,格式为text:text, xml:xml
                             xml;
     
                         // Firefox throws exceptions when accessing properties
                         // of an xhr when a network error occured
                         // )
                         // 在FF下当网络异常时,访问XHR的属性会抛出异常
                         try {
     
                             // Was never called and is aborted or complete
                             if ( callback && ( isAbort || xhr.readyState === 4 ) ) { // 4表示响应完成
     
                                // Only called once
                                callback = undefined; // callback只调用一次,注销callback
     
                                // Do not keep as active anymore
                                if ( handle ) {
                                    xhr.onreadystatechange = jQuery.noop; // 将onreadystatechange句柄重置为空函数
                                    if ( xhrOnUnloadAbort ) { // 如果是界面退出导致本次请求取消
                                       delete xhrCallbacks[ handle ]; // 注销句柄
                                    }
                                }
     
                                // If it's an abort
                                if ( isAbort ) { // 如果是取消本次请求
                                    // Abort it manually if needed
                                    if ( xhr.readyState !== 4 ) {
                                       xhr.abort(); // 调用xhr原生的abort方法
                                    }
                                } else {
                                    status = xhr.status;
                                    responseHeaders = xhr.getAllResponseHeaders();
                                    responses = {};
                                    xml = xhr.responseXML;
     
                                    // Construct response list
                                    if ( xml && xml.documentElement /* #4958 */ ) {
                                       responses.xml = xml; // 提取xml
                                    }
                                    responses.text = xhr.responseText; // 提取text
     
                                    // Firefox throws an exception when accessing
                                    // statusText for faulty cross-domain requests
                                    // FF在跨域请求中访问statusText会抛出异常
                                    try {
                                       statusText = xhr.statusText;
                                    } catch( e ) {
                                       // We normalize with Webkit giving an empty statusText
                                       statusText = ""; // 像WebKit一样将statusText置为空字符串
                                    }
     
                                    // Filter status for non standard behaviors
     
                                    // If the request is local and we have data: assume a success
                                    // (success with no data won't get notified, that's the best we
                                    // can do given current implementations)
                                    // 过滤不标准的服务器状态码
                                    if ( !status && s.isLocal && !s.crossDomain ) {
                                       status = responses.text ? 200 : 404; //
                                    // IE - #1450: sometimes returns 1223 when it should be 204
                                    // 204 No Content
                                    } else if ( status === 1223 ) {
                                       status = 204;
                                    }
                                }
                             }
                         } catch( firefoxAccessException ) {
                             if ( !isAbort ) {
                                complete( -1, firefoxAccessException ); // 手动调用回调函数
                             }
                         }
     
                         // Call complete if needed
                         // 在回调函数的最后,如果请求完成,立即调用回调函数
                         if ( responses ) {
                             complete( status, statusText, responses, responseHeaders );
                         }
                      };
     
                      // if we're in sync mode or it's in cache
                      // and has been retrieved directly (IE6 & IE7)
                      // we need to manually fire the callback
                      // 同步模式下:同步导致阻塞一致到服务器响应完成,所以这里可以立即调用callback
                      if ( !s.async || xhr.readyState === 4 ) {
                         callback();
                      } else {
                         handle = ++xhrId; // 请求计数
                         // 如果时页面退出导致本次请求取消,修正在IE下不断开连接的bug
                         if ( xhrOnUnloadAbort ) {
                             // Create the active xhrs callbacks list if needed
                             // and attach the unload handler
                             if ( !xhrCallbacks ) {
                                xhrCallbacks = {};
                                jQuery( window ).unload( xhrOnUnloadAbort ); // 手动触发页面销毁事件
                             }
                             // Add to list of active xhrs callbacks
                             // 将回调函数存储在全局变量中,以便在响应完成或页面退出时能注销回调函数
                             xhrCallbacks[ handle ] = callback;
                         }
                         xhr.onreadystatechange = callback; // 绑定句柄,这里和传统的ajax写法没什么区别
                      }
                  },
     
                  abort: function() {
                      if ( callback ) {
                         callback(0,1); // 1表示调用callback时,isAbort为true,在callback执行过程中能区分出是响应完成还是取消导致的调用
                      }
                  }
               };
           }
        });
    }
    15.4.2  前置过滤器和请求分发器的执行过程
    prefilters中的前置过滤器在请求发送之前、设置请求参数的过程中被调用,调用prefilters的是函数inspectPrefiltersOrTransports;巧妙的时,transports中的请求分发器在大部分参数设置完成后,也通过函数inspectPrefiltersOrTransports取到与请求类型匹配的请求分发器:
    // some code...
     
    // Apply prefilters
    // 应用前置过滤器,参数说明:
    inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
     
    // If request was aborted inside a prefiler, stop there
    // 如果请求已经结束,直接返回
    if ( state === 2 ) {
        return false;
    }
     
    // some code...
    // 注意:从这里开始要发送了
     
    // Get transport
    // 请求分发器
    transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
     
    // some code...
    函数inspectPrefiltersOrTransports从prefilters或transports中取到与数据类型匹配的函数数组,然后遍历执行,看看它的实现:
    // Base inspection function for prefilters and transports
    // 执行前置过滤器或获取请求分发器
    function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
           dataType /* internal */, inspected /* internal */ ) {
     
        dataType = dataType || options.dataTypes[ 0 ];
        inspected = inspected || {};
     
        inspected[ dataType ] = true;
     
        var list = structure[ dataType ],
           i = 0,
           length = list ? list.length : 0,
           executeOnly = ( structure === prefilters ),
           selection;
     
        for(; i < length && ( executeOnly || !selection ); i++ ) {
           selection = list[ i ]( options, originalOptions, jqXHR ); // 遍历执行
           // If we got redirected to another dataType
           // we try there if executing only and not done already
           if ( typeof selection === "string" ) {
               if ( !executeOnly || inspected[ selection ] ) {
                  selection = undefined;
               } else {
                  options.dataTypes.unshift( selection );
                  selection = inspectPrefiltersOrTransports(
                         structure, options, originalOptions, jqXHR, selection, inspected );
               }
           }
        }
        // If we're only executing or nothing was selected
        // we try the catchall dataType if not done already
        if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
           selection = inspectPrefiltersOrTransports(
                  structure, options, originalOptions, jqXHR, "*", inspected );
        }
        // unnecessary when only executing (prefilters)
        // but it'll be ignored by the caller in that case
        return selection;
    }
    15.4.3  总结
    通过前面的源码解析,可以将前置过滤器和请求分发器总结如下:
    前置过滤器 jQuery.ajaxPrefilter,prefilters

 

属性

功能

*

undefined

不做任何处理,事实上也没有*属性

json

[ function ]

被当作*处理

jsonp

[ function ]

修正url或data,增加回调函数名

在window上注册回调函数

注册script>json数据转换器

(被当作script处理)

script

[ function ]

设置设置以下参数:

是否缓存 cache、(如果跨域)请求类型 、(如果跨域)是否触发AJAX全局事件

2.请求分发器 jQuery. ajaxTransport,

请求分发器 jQuery.ajaxTransport,transports

 

 

3.类型转换器 ajaxConvert

属性

功能

*

[ function ]

返回xhr分发器,分发器带有send、abort方法

send方法依次调用XMLHTTPRequest的open、send方法,向服务端发送请求,并绑定onreadystatechange事件句柄

script

[ function ]

返回script分发器,分发器带有send、abort方法

send方法通过在header中创建script标签异步载入js,并在script元素上绑定onload、script.onreadystatechange事件句柄

 

 作者“知行合一”  

源码结构:

 

 

15.4.1 前置过滤器和请求分发器的初始化 前置过滤器和请求分发器在执行时...

复制代码

jQuery.extend({

 

    /**

     * 前置过滤器

     * @type {[type]}

     */

    ajaxPrefilter: addToPrefiltersOrTransports(prefilters),

 

    /**

     * 请求分发器

     * @type {[type]}

     */

    ajaxTransport: addToPrefiltersOrTransports(transports),

 

    

     ...........................

});

复制代码

可见这2个方法是通过私有方法addToPrefiltersOrTransports通过curry手段构造的,分别是保持了prefilters与transports的引用

 

 

 

来个简单的模拟这个结构

 

复制代码

var prefilters = 2;

 

var addToPrefiltersOrTransports = function(prefilters) {

    return function(b) {

        return prefilters + b;

    }

}

 

var ajaxPrefilter = addToPrefiltersOrTransports(prefilters)

 

ajaxPrefilter(1) //3

复制代码

可见ajaxPrefilter就维持了addToPrefiltersOrTransports返回函数的引用了,这种就是闭包的手法了,这也是JS的开发人员都需要掌握的

 

好处就是合并多个参数,当然因为维持引用代价就是一点点性能消耗

 

 

 

当然jQuery不是传递的简单类型处理,还可以传递的一个引用类型的回调函数,所以针对ajaxPrefilter方法放闭包构件就需要做一些处理了

 

填充prefilters处理器

 

复制代码

var prefilters = {};

 

var addToPrefiltersOrTransports = function(structure) {

 

    return function(func) {

        structure['*'] = func;

    }

}

 

var ajaxPrefilter = addToPrefiltersOrTransports(prefilters)

 

 

ajaxPrefilter(function(options){

    return {

        send:function(){

 

        },

        callback:function(){

 

        }

    }

})

复制代码

其实说白了就是把对应的方法制作能函数的形式填充到prefilters或者transports对应的处理包装对象中

 

要用的时候直接执行,每个函数都保持着各自的引用

 

这种写法的好处自然是灵活,易维护,减少代码量

 

还有我们经常的使用的,jQuery的代码很简练,比如合并多个方法的创建等等

 

复制代码

jQuery.each([

        "tabIndex",

        "readOnly",

        "maxLength",

        "contentEditable"

    ], function() {

        jQuery.propFix[this.toLowerCase()] = this;

    });

复制代码

 

 

所以此时的prefilters中的结构就是

 

复制代码

prefilters = {

        '*': function() {

            return {

编辑:智能硬件 本文来源:jQuery源码分析系列(33) : AJAX中的前置过滤器和请求

关键词: