jQuery-1.9.1源码分析系列(十六)ajax——ajax处理流程以及核心函数
先来看一看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处理流程以及核心函数的更多相关文章
- MyCat源码分析系列之——配置信息和启动流程
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...
- ABP源码分析二十六:核心框架中的一些其他功能
本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSess ...
- ABP源码分析三十六:ABP.Web.Api
这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...
- ABP源码分析四十六:ABP ZERO中的Ldap模块
通过AD作为用户认证的数据源.整个管理用户认证逻辑就在LdapAuthenticationSource类中实现. LdapSettingProvider:定义LDAP的setting和提供Defaut ...
- Android源码分析(十六)----adb shell 命令进行OTA升级
一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...
- spark 源码分析之十六 -- Spark内存存储剖析
上篇spark 源码分析之十五 -- Spark内存管理剖析 讲解了Spark的内存管理机制,主要是MemoryManager的内容.跟Spark的内存管理机制最密切相关的就是内存存储,本篇文章主要介 ...
- jQuery-1.9.1源码分析系列(六) 延时对象应用——jQuery.ready
还记不记得jQuery初始化函数jQuery.fn.init中有这样是一个分支 //document ready简便写法$(function(){…}) } else if ( jQuery.isFu ...
- Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解
普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的. 有时候作用域插槽很有用,比如使用Element-ui表格自定义模板时就用到了作用域插槽,Element-ui定义了每个单 ...
- jQuery-1.9.1源码分析系列(六) 延时对象续——辅助函数jQuery.when
$.when的说明 描述: 提供一种方法来执行一个或多个对象的回调函数,返回这些对象的延时(Deferred)对象. 说明(结合实例和源码): 如果你不传递任何参数, jQuery.when()将返 ...
- jQuery-1.9.1源码分析系列(六) 延时对象
首先我们需要明白延时对象有什么用? 第一个作用,解决时序以及动态添加执行函数的问题. function a(){alert(1)}; function b(){alert(2)}; function ...
随机推荐
- viso
- 四则运算安卓客户端UI截图(部分)
1.我们组安卓手机客户端UI设计主要由林培文同学负责,界面中用到的素材全部由他一人用PS制作,所以在素材来源上当属原创啦.正因为UI由一个人设计,同时他还得分担少量后台代码的编写,颇多的工作量与人才短 ...
- 利用sql注入
INSERT查询中实现注入攻击 1. 思路就是在含有insert语句的页面插入目标值信息.经常包含的是一个子查询. 2. 注意在insert过程中,左边的注入点和右边的注入点会有不同 3. 在mysq ...
- crontab 案例
#MIN HOUR DAY MONTH DAYOFWEEK COMMAND #每天早上6点10分 10 6 * * * date #每两个小时 0 */ ...
- C#集合类型大盘点
C#集体类型( Collections in C#) 集合是.NET FCL(Framework Class Library)中很重要的一部分,也是我们开发当中最常用到的功能之一,几乎是无处不在.俗话 ...
- .NET开发笔记(二十三) 谷歌地图下载
关于如何将地球经纬度坐标系统转换成程序中常用到的平面2D坐标系统,网上的文章很多,参考http://www.cnblogs.com/beniao/archive/2010/04/18/1714544. ...
- dubbo 配置文件详解
一.dubbo常用配置 <dubbo:service/> 服务配置,用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心. eg.<dub ...
- 《Entity Framework 6 Recipes》中文翻译系列 (41) ------ 第七章 使用对象服务之标识关系中使用依赖实体与异步查询保存
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 7-7 标识关系中使用依赖实体 问题 你想在标识关系中插入,更新和删除一个依赖实体 ...
- C# 用原生JS进行文件的上传
1.此文章是用原生JS来进行文件的上传,有两个版本,一个不用ajax,一个用ajax. 1)非AJAX <!DOCTYPE html> <html> <head> ...
- 《Qt Quick 4小时入门》学习笔记3
http://edu.csdn.net/course/detail/1042/14807?auto_start=1 Qt Quick 4小时入门 第八章:Qt Quick中的锚(anchors)布局 ...