[原创] jQuery源码分析-15AJAX-前置过滤器和请求分发器
?
边读边写,不正确的地方,还请各位告诉我,多多交流共同学习。15.4???????AJAX中的前置过滤器和请求分发器prefilters = {}, // 过滤器
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), // 通过闭包保持对prefilters的引用,将前置过滤器添加到prefilters
// 添加全局前置过滤器或请求分发器,过滤器的在发送之前调用,分发器用来区分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 );
?????????? }
?????? }
??? };
// 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 + "=" + 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;
??? }
// 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
???????????????????? // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
???????????????????? // 在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执行过程中能区分出是响应完成还是取消导致的调用
????????????????? }
????????????? }
?????????? };
?????? }
??? });
?
// Apply prefilters
// 应用前置过滤器,参数说明:
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
?
// If request was aborted inside a prefiler, stop there
// 如果请求已经结束,直接返回
if ( state === 2 ) {
??? return false;
// 注意:从这里开始要发送了
?
// Get transport
// 请求分发器
// 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;
属性
值
功能
*
[ function ]
返回xhr分发器,分发器带有send、abort方法
send方法依次调用XMLHTTPRequest的open、send方法,向服务端发送请求,并绑定onreadystatechange事件句柄
script
[ function ]
返回script分发器,分发器带有send、abort方法
send方法通过在header中创建script标签异步载入js,并在script元素上绑定onload、script.onreadystatechange事件句柄
?