先来看一看jQuery的ajax核心处理流程($.ajax)

a. ajax( [url,] options )执行流程


  第一步,为传递的参数做适配。url可以包含在options中

//传递的参数只是一个对象
if ( typeof url === "object" ) {
options = url;
url = undefined;
} //options强制转成对象
options = options || {};

  第二步,创建一些变量,比较重要的是:创建最终选项对象s、全局事件上下文是callbackContext、创建deferred和completeDeferred、创建jqXHR对象。

var //跨域检测变量
parts,
...
//创建最终选项对象
s = jQuery.ajaxSetup( {}, options ),
//回调上下文
callbackContext = s.context || s,
//全局事件上下文是callbackContext,如果他是一个DOM节点或jQuery集合(对象)
globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
jQuery( callbackContext ) :
jQuery.event,
// Deferreds
deferred = jQuery.Deferred(),
completeDeferred = jQuery.Callbacks("once memory"),
...
jqXHR = {
readyState: 0,
//建立请求头哈希表
getResponseHeader: function( key ) {...},
// Raw string
getAllResponseHeaders: function() {...},
//缓存请求头
setRequestHeader: function( name, value ) {...},
//重写响应content-type头
overrideMimeType: function( type ) {...},
//取决于状态的回调
statusCode: function( map ) {...},
//取消请求
abort: function( statusText ) {...}
}; //添加延时事件
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;

  第三步,检查是否跨域。其中需要注意的是ajaxLocParts在jQuery初始化的时候就定义了

//rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/
//需要注意的是本地文件一般形如"file:///C:/Users/Administrator/Desktop/jquery/test.html"
//最终结果为["file://", "file:", "", undefined]
//正常http请求如"http://www.baidu.com"
//的到结果为["http://www.baidu.com", "http:", "www.baidu.com", undefined]
//如果是"http://192.168.0.17:8080/baidu/com"
//则得到的结果["http://192.168.0.17:8080", "http:", "192.168.0.17", "8080"]
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
//跨域请求是为了当我们有一个协议:host:port不匹配的时候
if ( s.crossDomain == null ) {
parts = rurl.exec( s.url.toLowerCase() );
s.crossDomain = !!( parts &&
( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
);
}

  第四步,将传递数据data转化成一个查询字符串

//processData默认为true
//默认情况下,通过data属性传递进来的数据,如果是一个对象(技术上讲,只要不是字符串),
//都会处理转化成一个查询字符串,以配合默认内容类型 "application/x-www-form-urlencoded"
if ( s.data && s.processData && typeof s.data !== "string" ) {
s.data = jQuery.param( s.data, s.traditional );
}

  第五步,运行prefilters进行预处理

//运行prefilters
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

  预处理和分发器使用的是同一个函数inspectPrefiltersOrTransports,需要注意的是当dataType为jsonp的时候是以dataType为script的方式来处理的

  第六步,根据传递的选项设置默认参数处理(主要包括如果type是GET等类型,传递的数据将被附加到URL上;添加请求头如If-Modified-Since/If-None-Match、Content-Type、Accept等;)


//没有请求内容(type一般为GET的情况)
if ( !s.hasContent ) { //如果data可用,添加到url
if ( s.data ) {
cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
// #9682:删除data保证重试是不会被使用
delete s.data;
} //cache默认值:true(dataType为'script'或'jsonp'时,则默认为false)。
//指示是否缓存URL请求。如果设为false将强制浏览器不缓存当前URL请求。
//该参数只对HEAD、GET请求有效(POST请求本身就不会缓存)
if ( s.cache === false ) {
//rts = /([?&])_=[^&]*/
s.url = rts.test( cacheURL ) ? //如果已经有一个'_'参数,设置他的值
cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) : //否则添加到url后面
//ajax_rquery = /\?/
cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
}
} // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
//ifModified默认为false
//允许当前请求仅在服务器数据改变时获取新数据(如未更改,浏览器从缓存中获取数据)
//它使用HTTP头信息Last-Modified来判断。从jQuery 1.4开始,他也会检查服务器指定的'etag'来确定数据是否已被修改。
if ( s.ifModified ) {
if ( jQuery.lastModified[ cacheURL ] ) {
jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
}
if ( jQuery.etag[ cacheURL ] ) {
jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
}
} //contentType默认值:'application/x-www-form-urlencoded; charset=UTF-8'。
//使用指定的内容编码类型将数据发送给服务器。
//W3C的XMLHttpRequest规范规定charset始终是UTF-8,将其改也无法强制浏览器更改字符编码。
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
jqXHR.setRequestHeader( "Content-Type", s.contentType );
} //设置Accept头,依赖于dataType
jqXHR.setRequestHeader(
"Accept",
s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
s.accepts[ "*" ]
); // Check for headers option
//headers默认值:{}。
//以对象形式指定附加的请求头信息。请求头X-Requested-With: XMLHttpRequest将始终被添加,
//当然你也可以在此处修改默认的XMLHttpRequest值。
//headers中的值可以覆盖beforeSend回调函数中设置的请求头(意即beforeSend先被调用)。
for ( i in s.headers ) {
jqXHR.setRequestHeader( i, s.headers[ i ] );
}

//安装回调到deferreds上
for ( i in { success: 1, error: 1, complete: 1 } ) {
jqXHR[ i ]( s[ i ] );
}

  第七步,执行请求分发

//执行请求分发
transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

  第八步,发送请求,附加上回调处理done,以及异常处理。done函数里面处理ajax请求完成(成功或失败)后的处理。

//如果没有transport,自动中止
if ( !transport ) {
done( -1, "No Transport" );
} else {
jqXHR.readyState = 1; //触发ajaxSend事件
...
//请求允许时长限时处理

//发送ajax请求
try {
state = 1;
transport.send( requestHeaders, done );
} catch ( e ) {
// 传播异常的错误,如果没有成功
if ( state < 2 ) {
done( -1, e );
//其他情况简单的处理
} else {
throw e;
}
}

  接下来是请求返回之后的处理,全部在done函数中完成:包括更新一些全局的状态、调用ajaxHandleResponses解析响应数据、针对响应返回的状态码自动判断执行那些Deferred延时以及触发哪些全局事件。把done函数的主要源码贴一下

//ajax完成后的回调
function done( status, nativeStatusText, responses, headers ) {
...
//状态改为"done"
state = 2; //清除timeout
if ( timeoutTimer ) {
clearTimeout( timeoutTimer );
}
...
//获取响应数据
if ( responses ) {
response = ajaxHandleResponses( s, jqXHR, responses );
}
...
//如果成功,处理之
if ( status >= 200 && status < 300 || status === 304 ) { //在ifModified模式下设置 If-Modified-Since and/or If-None-Match header
if ( s.ifModified ) {...} //没有新文档
if ( status === 204 ) {
... //客户端有缓冲的文档并发出了一个条件性的请求
//(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。
//服务器告诉客户,原来缓冲的文档还可以继续使用。
} else if ( status === 304 ) {
... //如果有数据,我们转换他
} else {
isSuccess = ajaxConvert( s, response );
statusText = isSuccess.state;
success = isSuccess.data;
error = isSuccess.error;
isSuccess = !error;
} //如果出错
} else {
//我们从状态文本提取错误,然后正常化状态文本和状态给没有中止的请求
error = statusText;
if ( status || !statusText ) {
statusText = "error";
if ( status < 0 ) {
status = 0;
}
}
} //为jqXHR对象设置数据
jqXHR.status = status;
jqXHR.statusText = ( nativeStatusText || statusText ) + ""; //Deferred执行和全局事件触发处理
...
}

  到此,整个流程完结。

b. 预处理prefilters和请求分发trasports结构


  预处理prefilters和请求分发trasports的初始化都是调用addToPrefiltersOrTransports返回一个包装函数,然后调用这个包装函数给prefilters和transports添加属性。

jQuery.extend({
  ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
  ajaxTransport: addToPrefiltersOrTransports( transports ),
} // jQuery.ajaxPrefilter和jQuery.ajaxTransport基础构造函数构造函数
function addToPrefiltersOrTransports( structure ) { // dataTypeExpression is optional and defaults to "*"
return function( dataTypeExpression, func ) {...};
}

  举一个初始化预处理prefilters的例子

    jQuery.ajaxPrefilter( "script", function( s ) {
if ( s.cache === undefined ) {
s.cache = false;
}
if ( s.crossDomain ) {
s.type = "GET";
s.global = false;
}
});

  其他的初始化都是类似,最终预处理prefilters初始化完成以后的结果是

  

  而分发器初始化完成后的结果是

  

  预处理和分发器使用的是同一个函数inspectPrefiltersOrTransports来触发,需要注意的是当dataType为jsonp的时候是以dataType为script的方式来处理的。

// 基本功能用于预处理过滤器和分发器
//structure对应的是prefilters和transports
/*
prefilters = {
script: [function(s){...}],
json: [function(s, originalSettings, jqXHR){...}],
jsonp: [function(s, originalSettings, jqXHR){...}]
} transports = {
*: [function(s){...}],
script: [function(s){...}]
}
*/
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
  var inspected = {},
  seekingTransport = ( structure === transports );   function inspect( dataType ) {
    var selected;
    inspected[ dataType ] = true;
    //structure[ dataType ]获取置顶的处理函数数组(目前数组长度都是1)
    jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
      //执行预处理或分发函数
      var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );       //“jsonp”的预处理进入该分支(dataTypeOrTransport为“script”),jsonp最终以datatype为“script”的方式来处理
      if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
        options.dataTypes.unshift( dataTypeOrTransport );
        
//dataTypeOrTransport为"script",执行script的预处理
        inspect( dataTypeOrTransport );
        return false
;
      } else if ( seekingTransport ) {
        return !( selected = dataTypeOrTransport );
      }
    });
    return selected;
  }
  return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

c. 预处理prefilters详细分析


  预处理有三种:json/jsonp/script

  

  首先我们需要明白为什么需要进行预处理。

  在dataType为"script"的情况下,我们需要强制处理缓存的特殊情况;如果是跨域则需要强制类型为GET,并禁止触发全局事件。这个处理的源码如下

jQuery.ajaxPrefilter( "script", function( s ) {
if ( s.cache === undefined ) {
s.cache = false;
}
//注意:在远程请求时(不在同一个域下),所有POST请求都将转为GET请求。(因为将使用DOM的script标签来加载)
if ( s.crossDomain ) {
s.type = "GET";
s.global = false;
}
});

  在dataType为"json"的情况下实际上是什么都没有做。

  另一个需要预处理的是当dataType为jsonp的情况。 jsonp下情况比较特殊,jsonp的原理详见

  jsonp原理页面也有jQuery处理的分析。这里就简单介绍了。处理步骤如下(jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR )的源码

  首先我们需要设置回调函数名称,可以自己定义也可以让jQuery自动设置。

//获取回调函数名称(这个名称可以在ajax的jsonpCallback选项上设置,
//否则通过jQuery默认的方式jsonpCallback()来设置)
//这个回调函数名称是用来告诉后台需要将返回数据包裹到该函数中,返回前端后执行
callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
s.jsonpCallback() :
s.jsonpCallback;

  然后,将回调插入到url或者data选项中(一般来说是URL

if ( jsonProp ) {
s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
} else if ( s.jsonp !== false ) {
s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
}

  再然后,安装回调。

//安装回调
overwritten = window[ callbackName ];
window[ callbackName ] = function() {
responseContainer = arguments;
};

  很明显,本来后台应该执行overwritten这个回调的,但是现在换成了执行后面重写的这个回调。别急overwritten后面有掉用到。

  在安装回调之前还有一个步骤:添加"script json"转换器

//使用数据转换器,脚本执行后取回JSON
s.converters["script json"] = function() {
if ( !responseContainer ) {
jQuery.error( callbackName + " was not called" );
}
return responseContainer[ 0 ];
};

  我们可能疑惑responseContainer是怎么来的?看到了安装回调了么,responseContainer就是后台调用了window[ callbackName ]这个回调以后获取到的。

  最后,添加延时对象的always响应执行overwritten。

//清除函数(转换完成后执行)
jqXHR.always(function() {
//保存先前存的值
window[ callbackName ] = overwritten; //将jsonpCallback设置回原始值
if ( s[ callbackName ] ) {
//确保重新使用jsonpCallback选项没有杂质
s.jsonpCallback = originalSettings.jsonpCallback; //将callbackName压入oldCallbacks以备将来使用
oldCallbacks.push( callbackName );
} //在请求响应后,如果jsonpCallback指定的回调是一个函数则调用它
if ( responseContainer && jQuery.isFunction( overwritten ) ) {
overwritten( responseContainer[ 0 ] );
} responseContainer = overwritten = undefined;
});

  所以overwritten是在这里面执行的,前面源码中重载过的那个回调在后台调用后获取到了responseContainer值也被用到了。可见我们在设置的jsonpCallback选项指定的回调名(例如chuaRemote)对应的回调先被保存到overwritten中,而这个原始的chuaRemote被赋值为function() {  responseContainer = arguments;};

  在响应处理中chuaRemote会被调用让responseContainer获取到响应值。最后会执行到jqXHR.always添加的函数处理。将chuaRemote恢复到原来的函数overwritten,并执行overwritten(jsonpCallback指定的回调)。主要是always这个监听处理中还清除callbackName指定的函数,以及添加回到历史等等处理。

d. 分发器ajaxTransport


  分发器干啥的?前面不是说了jQuery的ajax处理方式有两种么,一种直接使用浏览器的ajax接口处理,另一种是使用script的src来处理。分发器就是将这两中情况分发给他们两者的专用处理器来处理。

  分发器ajaxTransport在jQuery初始化完成后得到了分发处理的两种类型

  

  两种类型中除开跨域使用script指定的方式外,都使用*指定的方式。

  我们先看一下script方式的分发器如下

jQuery.ajaxTransport( "script", function(s) {
  // 仅用于跨域
  if ( s.crossDomain ) {
    var script,
    head = document.head || jQuery("head")[0] || document.documentElement;
    return {
      send: function( _, callback ) {
        script = document.createElement("script");
        script.async = true;
        if ( s.scriptCharset ) {
          script.charset = s.scriptCharset;
        }
        script.src = s.url;
        //添加事件处理
        script.onload = script.onreadystatechange = function( _, isAbort ) {…};
        //使用本地DOM操作,以避免我们的domManip AJAX挂羊头卖狗肉
        head.insertBefore( script, head.firstChild );
      },       abort: function() {
        if ( script ) {
          script.onload( undefined, true );
        }
      }
    };
  }
});

  可见跨域请求使用动态加载script标签的方式来完成,所有的参数都附加到url上。dataType为jsonp也是使用该方式。

  需要注意一点的是判断script标签加载完成的回调处理

script.onload = script.onreadystatechange = function( _, isAbort ) {
//这种写法的取巧之处在于onload和onreadystatechage都用同一个函数,
//Firefox/Safari/Chrome/Opera中不支持onreadystatechage事件,也没有readyState属性,
//所以 !this.readyState 是针对这些浏览器。readyState是针对IE浏览器,载入完毕的情况是loaded,
//缓存的情况下可能会出现readyState为complete。所以两个不能少。
//但由于IE9/10也已经支持onload事件了,会造成callback执行2次。
//所以执行一次以后设置了script.onload = script.onreadystatechange = null;
if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
//处理IE内存
script.onload = script.onreadystatechange = null; //移除script节点
if ( script.parentNode ) {
script.parentNode.removeChild( script );
} //script内存清空
script = null; // Callback if not abort
if ( !isAbort ) {
callback( 200, "success" );
}
}
};

  

  接下来我们看一下普通类型的ajax请求分发是如何处理的

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
var handle, i,
xhr = s.xhr();
//打开socket
//传递空username,Opera产生一个登陆弹出框(#2865)
if ( s.username ) {
xhr.open( s.type, s.url, s.async, s.username, s.password );
} else {
xhr.open( s.type, s.url, s.async );
} //如果提供应用自定义字段
if ( s.xhrFields ) {
for ( i in s.xhrFields ) {
xhr[ i ] = s.xhrFields[ i ];
}
} // 重写mime类型,如果需要的话
if ( s.mimeType && xhr.overrideMimeType ) {
xhr.overrideMimeType( s.mimeType );
} // X-Requested-With头
if ( !s.crossDomain && !headers["X-Requested-With"] ) {
headers["X-Requested-With"] = "XMLHttpRequest";
} // 需要extra try/catch对跨域请求(Firefox 3中)
try {
for ( i in headers ) {
xhr.setRequestHeader( i, headers[ i ] );
}
} catch( err ) {} //发送请求
//在jQuery.ajax中有try/catch处理
xhr.send( ( s.hasContent && s.data ) || null ); // Listener
callback = function( _, isAbort ) {...}; if ( !s.async ) {
// if we're in sync mode we fire the callback
callback();
} else if ( xhr.readyState === 4 ) {
// (IE6 & IE7) if it's in cache and has been
// retrieved directly we need to fire the callback
setTimeout( callback );
} else {
handle = ++xhrId;
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;
}
}, abort: function() {
if ( callback ) {
callback( undefined, true );
}
}
};
}
});

  逻辑是比较简单的,就不详细分析了。可见普通情况下使用XHR方式来处理ajax。

  如果觉得本文不错,请点击右下方【推荐】!

  

jQuery-1.9.1源码分析系列(十六)ajax——ajax处理流程以及核心函数的更多相关文章

  1. MyCat源码分析系列之——配置信息和启动流程

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...

  2. ABP源码分析二十六:核心框架中的一些其他功能

    本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSess ...

  3. ABP源码分析三十六:ABP.Web.Api

    这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...

  4. ABP源码分析四十六:ABP ZERO中的Ldap模块

    通过AD作为用户认证的数据源.整个管理用户认证逻辑就在LdapAuthenticationSource类中实现. LdapSettingProvider:定义LDAP的setting和提供Defaut ...

  5. Android源码分析(十六)----adb shell 命令进行OTA升级

    一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...

  6. spark 源码分析之十六 -- Spark内存存储剖析

    上篇spark 源码分析之十五 -- Spark内存管理剖析 讲解了Spark的内存管理机制,主要是MemoryManager的内容.跟Spark的内存管理机制最密切相关的就是内存存储,本篇文章主要介 ...

  7. jQuery-1.9.1源码分析系列(六) 延时对象应用——jQuery.ready

    还记不记得jQuery初始化函数jQuery.fn.init中有这样是一个分支 //document ready简便写法$(function(){…}) } else if ( jQuery.isFu ...

  8. Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解

    普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的. 有时候作用域插槽很有用,比如使用Element-ui表格自定义模板时就用到了作用域插槽,Element-ui定义了每个单 ...

  9. jQuery-1.9.1源码分析系列(六) 延时对象续——辅助函数jQuery.when

    $.when的说明 描述: 提供一种方法来执行一个或多个对象的回调函数,返回这些对象的延时(Deferred)对象. 说明(结合实例和源码): 如果你不传递任何参数,  jQuery.when()将返 ...

  10. jQuery-1.9.1源码分析系列(六) 延时对象

    首先我们需要明白延时对象有什么用? 第一个作用,解决时序以及动态添加执行函数的问题. function a(){alert(1)}; function b(){alert(2)}; function ...

随机推荐

  1. Windows Server 2012 支持的逻辑盘容量最大是多少?

    这个问题似乎看起来是问系统支持最大硬盘参数?其实不然,这和文件系统有着很大关系. 磁盘在系统应用之前,要先初始化,然后创建卷,再进行格式化后完成在系统的挂载.完成这些操作之后,磁盘空间可以被系统使用. ...

  2. windows或mac上对iOS设备截图

    1.需要在设备上安装插件“设置”--“开发者”的选项 2.启动"com.apple.mobile.screenshotr"服务 3.和设备scoket通讯,拿到截图.

  3. 装逼名词-ABA CAS SpinLock

    今天看wiki,看到一个提到什么什么会陷入 race condition & ABA problem.丫的我没听过ABA呀,那么我去搜了一下,如下: http://www.bubuko.com ...

  4. RazorEngine 3.3 在Mono 3.2上正常运行

    RazorEngine 是一个简化的模板引擎基于微软新的Razor 解析引擎, Razor是在 ASP.NET MVC3 和 Web Pages中引入的.RazorEngine 提供了一个外包装和额外 ...

  5. WPF - 属性系统 (1 of 4)

    本来我希望这一系列文章能够深入讲解WPF属性系统的实现以及XAML编译器是如何使用这些依赖项属性的,并在最后分析WPF属性系统的实际实现代码.但是在编写的过程中发现对WPF属性系统代码的讲解要求之前的 ...

  6. Lesson 12 Goodby and good luck

    Text Our neighbour, Captain Charles Alison, will sail from Portsmouth tomorrow. We'll meet him at th ...

  7. Python黑帽编程 3.3 MAC洪水攻击

    Python灰帽编程 3.3 MAC洪水 传统的交换机(我只对我目前使用的交互机做过测试,按照常识只能这样表述)在数据转发过程中依靠对CAM表的查询来确定正确的转发接口,一旦在查询过程中无法找到相关目 ...

  8. 剑指Offer面试题:14.链表的倒数第k个节点

    PS:这是一道出境率极高的题目,记得去年参加校园招聘时我看到了3次,但是每次写的都不完善. 一.题目:链表的倒数第k个节点 题目:输入一个链表,输出该链表中倒数第k个结点.为了符合大多数人的习惯,本题 ...

  9. [nRF51822] 9、基础实验代码解析大全 · 实验12 - ADC

    一.本实验ADC 配置 分辨率:10 位. 输入通道:5,即使用输入通道AIN5 检测电位器的电压. ADC 基准电压:1.2V. 二.NRF51822 ADC 管脚分布 NRF51822 的ADC ...

  10. Android-webview和js互相调用

    Android-webview和js互相调用 Android 和 H5 都是移动开发应用的非常广泛.市面上很多App都是使用Android开发的,但使用Android来开发一些比较复杂附属类,提示性的 ...