JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源码剖析
一、前言
jQuery.Deferred作为1.5的新特性出现在jQuery上,而jQuery.ajax函数也做了相应的调整。因此我们能如下的使用xhr请求调用,并实现事件处理函数晚绑定。
var promise = $.getJSON('dummy.js')
// 其他逻辑处理
promise.then(function(){
alert('late binding')
})
我还一度以为这就是Promises/A+规范的实现,但其实jQuery.Deferred应该与jsDeferred归为一类,我称之为Before Promises/A。虽然jQuery.Deferred的出现会导致初接触Promise的朋友产生不少的误解,但同时证明了Promises/A+规范的实现已成为开发过程中必不可少的利器了。
接下来我们会踏上从1.5到2.1版本的jQuery.Deferred实现的剖析之旅,有兴趣的朋友们请坐稳扶好哦!!!
由于篇幅较长,特设目录一坨!
二、启程——1.5
jQuery.Deferred 中主要包含三个对象类型Deferred、EnhancedDeferred和Promise,Deferred作为基础类型用于构建更复杂的EnhancedDeferred类型,EnhancedDeferred实例则是用户直接操作的对象,而Promise则是EnhancedDeferred的功能子集,仅提供成功/失败回调函数的订阅、关联的EnhancedDeferred实例的状态查询功能。
Deferred实例的状态:initialized 、fired和cancelled。而状态间的转换关系如下:
initialized -> fired
initalized -> cancelled
EnhancedDeferred实例的状态:initialized、resolved、rejected。而状态间的转换关系如下:
initialized -> resolved
initialized -> rejected
(注意:上述类型和类型状态均根据源码分析得出,源码中并没有明确注明)
1.5的jQuery.Deferred实现位于core.js文件中的,下面我将相关代码抽取并分组来分析。
1. Deferred实例工厂
/**
* Deferred实例工厂
* Deferred实例实际上就是对一堆回调函数的管理
*/
$._Deferred = function(){
// Deferred实例私有属性
var callbacks = [] // 回调函数队列
/**
* 状态标识
* fired和firing,均用于标识状态"fired"
* fired还用于保存调用回调函数队列元素时的this指针和入参,内容格式为:[ctx, args]
* firing表示是否正在执行回调函数, 防止并发执行resovleWith函数(主页面和同域或子域iframe子页面可并发调用)
* cancelled,用于标识状态"cancelled"
*/
var fired
,firing,
,cancelled // Deferred实例
var deferred = {
// 添加回调函数到队列
done: function(/* args{0,} */) {
if ( !cancelled ) {
var args = arguments
,length
,elem
,type
,_fired
// 若当前Deferred实例状态为"fired"(曾经调用过resolveWith或resolve方法)
// 则使用第一次调用resolveWith或resolve的参数作为入参执行新添加的回调函数
if (fired) {
_fired = fired;
// 将Deferred实例的状态重置为"initialized",后面通过resolveWith函数实现"initialized"->"fired"的状态转换
fired = ;
}
for (var i = , length = args.length; i < length; i++) {
elem = args[i]
type = $.type(elem)
if (type === "array") {
// 若该入参为数组则递归添加回调函数
deferred.done.apply(deferred, elem)
} else if (type === "function") {
// 添加回调函数到队列
callbacks.push(elem)
}
}
if (_fired) {
// 实现"initialized"->"fired"的状态转换
// 注意:递归添加回调函数时并不会执行该代码
deferred.resolveWith(_fired[], _fired[])
}
}
// 返回当前Deferred对象,形成链式操作
return this
},
/**
* 发起实现"initialized"->"fired"的状态转换请求
*/
resolveWith: function(context, /* args{0,} */) {
if (!cancelled && !fired && !firing) {
firing = // 状态转换"initialized"->"fired"
try {
while(callbacks[]) {
// 以resolveWith的参数作为入参同步调用所有回调函数
callbacks.shift().apply(context, args)
}
}
finally {
fired = [context, args] // 状态转换"initialized"->"fired"
firing =
}
}
return this
},
resolve: function() {
// 当this为deferred时采用Promise实例
// 当this为failDeferred时采用Deferred实例
deferred.resolveWith($.isFunction(this.promise) ? this.promise() : this, arguments)
return this
},
isResolved: function() {
return !!( firing || fired );
},
/**
* 私有方法
* 将当前Deferred对象的状态设置为"cancelled",并清空回调函数队列
*/
cancel: function() {
cancelled = ;
callbacks = [];
return this;
}} return deferred
}
Deferred实例内部维护着名为callbacks的回调函数队列(而不是Promises/A+规范中的成功/失败事件处理函数和Deferred单向链表)。然后将目光移到done方法,透过其实现可知jQuery.Deferred是支持回调函数晚绑定的(jsDeferred不支持,Promises/A+规范支持),但均以resovleWith的参数作为回调函数的入参,而不是上一个回调函数的返回值作为下一个回调函数的入参来处理,无法形成责任链模式(Promises/A+规范支持)。
2. 对外API——jQuery.Deferred
/**
* 用户使用的jQuery.Deferred API
* 返回EnhancedDeferred类型实例(加工后的Deferred实例)
*/
$.Deferred = function(func) {
/**
* EnhancedDeferred实例有两个Deferred实例构成
* 其中deferred代表成功回调函数,failDeferred代表失败回调函数
* 好玩之处:EnhancedDeferred实例并不是由新类型构建而成,
* 而是以deferred实例为基础,并将failDeferred融入deferred的扩展方法中构建所得
*/
var deferred = jQuery._Deferred(), failDeferred = jQuery._Deferred(), promise; // 将failDeferred融入deferred的扩展方法中
deferred.fail = failDeferred.done
deferred.rejectWith = failDeferred.resolveWith
deferred.reject = failDeferred.resolve
deferred.isRejected = failDeferred.isResolved // 辅助方法,一次性添加成功/失败处理函数到各自的Deferred实例的回调函数队列中
deferred.then = function(doneCallbacks, failCallbacks) {
deferred.done(doneCallbacks).fail(failCallbacks)
return this
} // 向入参obj添加Deferred实例的方法,使其成为Promise实例
// 精妙之处:由于这些方法内部均通过闭包特性操作EnhancedDeferred实例的私有属性和方法(而不是通过this指针)
// 因此即使this指针改变为其他对象依然有效。
// 也就是promise函数不会产生新的Deferred对象,而是作为另一个操作原EnhancedDeferred实例的视图。
deferred.promise = function(obj, i /* internal */) {
if (obj == null) {
if (promise) return promise
promise = obj = {}
}
i = promiseMethods.length
while (i--) {
obj[promiseMethods[i]] = deferred[promiseMethods[i]]
}
return obj
} // 当调用resolve后,failDeferred的状态从"initialized"转换为"cancelled"
// 当调用reject后,deferred的状态从"initialized"转换为"cancelled"
// 因此resolve和reject仅能调用其中一个,同时调用和重复调用均无效
deferred.then(failDeferred.cancel, deferred.cancel)
// 将cancel函数转换为私有函数
delete deferred.cancel
// 调用工厂方法
if (func) {
func.call(deferred, deferred)
}
return deferred
}
jQuery.Deferred函数返回一个EnhancedDeferred实例,而EnhancedDeferred是以一个管理成功回调函数队列的Deferred实例为基础,并将另一个用于管理失败回调函数队列的Deferred实例作为EnhancedDeferred实例扩展功能的实现提供者,很明显成功、失败回调函数队列是独立管理和执行。
3. 辅助方法——jQuery.when
功能就是等待所有入参均返回值后,以这些返回值为入参调用回调队列的函数
$.when = function(object) {
var args = arguments, length = args.length, deferred = length <=
&& object && $.isFunction(object.promise) ? object
: jQuery.Deferred(), promise = deferred.promise(), resolveArray;
if (length > ) {
resolveArray = new Array(length);
$.each(args, function(index, element) {
// 递归产生多个EnhancedDeferred实例
$.when(element).then(
function() {
resolveArray[index] = arguments.length >
? slice.call(arguments, )
: arguments[]
if (!--length) {
// 当入参均有返回值时,则修改顶层EnhancedDeferred实例状态为"resolved"
deferred.resolveWith(promise, resolveArray);
}
}
// 修改顶层EnhancedDeferred实例状态为"rejected"
, deferred.reject);
});
} else if (deferred !== object) {
// 当object不是Deferred实例或Promise实例时,将当前的EnhancedDeferred实例状态设置为"resolved"
deferred.resolve(object);
}
// 将设置当前EnhancedDeferred实例状态的操作,交还给object自身
return promise;
};
jQuery.Deferred中的Deferred实例和EnhancedDeferred实例均设计了隐式的状态标识,因此支持回调函数晚绑定的功能,但由于其采用两个Deferred实例分类管理所有成功/失败回调函数,而不是采用Deferred实例单向链表的结构,因此无法实现成功和失败回调函数之间的数据传递,并且没有对回调函数的抛异常的情况作处理。并且resolveWith的遍历调用回调函数队列中没有采用责任链模式,与Promises/A+规范截然不同。另外回调函数均为同步调用,而不是Promises/A+中的异步调用。因此我们只能将其列入Before Promises/A的队列中了!
jQuery1.5除了新增jQuery.Deferred特性,还以jQuery.Deferred为基础对ajax模块进行增强,相关代码如下:
function done( status, statusText, responses, headers) {
......................
// Success/Error
if ( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] );
}
......................
// Complete
completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] );
......................
// Attach deferreds
deferred.promise( jXHR );
jXHR.success = jXHR.done;
jXHR.error = jXHR.fail;
jXHR.complete = completeDeferred.done
...................
}
三、觉醒——1.6
可能是jQuery的开发团队意识到jQuery.Deferred的实现与Promises/A+规范相距甚远,于是在1.6版本上补丁式地为EnhancedDeferred增加了一个 pipe方法 ,从而实现回调函数的责任链。另外jQueyr.Deferred已经成为一个独立的模块deferred.js了(《JavaScript框架设计》中的示例就是1.6的)。
/**
* fnDone和fnFail作为当前EnhancedDeferred实例的回调函数,
* 而不是pipe函数中新创建的EnhancedDeferred实例的回调函数。
*/
pipe = function(fnDone, fnFail) {
// 创建一个新的EnhancedDeferred实例
return jQuery.Deferred(function(newDefer) {
jQuery.each({
done: [fnDone, "resolve"]
,fail: [fnFail, "reject"]
}, function(handler, data) {
var fn = data[]
,action = data[]
,returned
if ($.isFunction(fn)) {
deferred[handler](function() {
// fnDone, fnFail作为原有EnhancedDeferred实例的回调函数被执行
returned = fn.apply(this, arguments)
if (jQuery.isFunction(returned.promise)) {
// 若返回值为EnhancedDeferred或Promise实例,由它们来修改新EnhancedDeferred实例的状态
returned.promise().then(newDefer.resolve, newDefer.reject)
} else {
// 将原有EnhancedDeferred实例的回调函数的执行结果作为新EnhancedDeferred实例回调函数的入参,
// 并将新EnhancedDeferred实例的状态设置为"resolved"
newDefer[action](returned)
}
})
} else {
// 将新EnhancedDeferred实例的resolve/reject添加到旧EnhancedDeferred相应的回调函数队列中
deferred[handler](newDefer[action])
}
})
}).promise()
}
除了pipe函数外,1.6还为EnhancedDeferred实例新增了 always函数 ,通过它添加的回调函数,无论EnhancedDeferred实例状态为"resolved"还是"rejected"均会被执行。
always = function() {
return deferred.done.apply(deferred, arguments).fail.apply(this, arguments)
}
另外1.6对$.when进行了重构使代码更容易理解。并且effectes和queue模块可以开始以jQuery.Deferred作为基础提供then方法等API了。
四、全局重构,但本质不变——1.7
由于VS2012新建Asp.Net项目时默认自带jQuery1.7,我想Asp.Net的攻城狮们对它应该不陌生了。而1.7版本的jQuery.Deferred相对于以前的版本新增了 progress 、 notify 和 notifyWith 的API,但到底有什么用呢?1.7版本的jQuery.Deferred是否更接近Promises/A+规范呢?答案是否定的。
新版的jQuery.Deferred内部新增一个回调函数队列,该队列不像1.6版本中的deferred和failDeferred那样只能触发一次"initialized"->"fired"的状态转换,而是可以进行多次并且与deferred和failDeferred一样支持回调函数晚绑定。而 progress 、 notify 和 notifyWith 则与这个新的回调函数队列相关。
另外1.7版本中对jQuery.Deferred进行全局重构,不再由原来的 $._Deferred 来构建Deferred实例,而是通过 jQuery.Callbacks函数 来生成回调函数队列管理器来代替(作用是一样的,但回调函数队列管理器更具有通用性),而上文提到的EnhancedDeferred则由三个回调函数队列管理器组成。
在陷入源码前再次强调一点——1.7与1.6版本在本质上是一点都没变!!
1. 首先我们一起来看看重构的重心—— jQuery.Callbacks函数 (位于callbacks.js文件中)
作用:创建回调函数队列管理器实例。
回调函数队列管理器存在以下状态:
initialized: 管理器实例初始状态;
firing: 正在遍历回调函数队列并按FIFO顺序调用回调函数;
fired: 遍历完回调函数队列,等待接受下一次遍历请求;
locked: 锁定管理器,无法再接受遍历回调函数的请求;
dying: 管理器进入临死状态,只要此时状态转换为fired或locked,则会直接跳转为disabled状态;
disabled: 管理器将被废弃,无法再使用了。
状态间的转换关系如下:
①. initialized -> firing <-> fired [-> disabled|locked]
②. initialized <-> firing <-> fired [-> disabled|locked]
③. initialized -> locked -> disabled
④. initialized -> dying -> locked -> disabled
⑤. initialized -> dying -> fired -> disabled
⑥. initialized -> dying -> fired -> firing
在调用jQuery.Callbacks时可以通过可选入参来配置管理器的一些特性,分别为:
unique,是否确保队列中的回调函数的唯一性。
stopOnFalse,是否当某个回调函数返回值为false时,将配置管理器的状态设置为dying。
once,是否仅能执行一次队列遍历操作。若不限制仅能执行一次队列遍历(默认值),则状态转换关系为②、③和⑥。
memory,是否支持函数晚绑定。若不支持晚绑定且仅能执行一次队列遍历操作,则状态转换关系为③、④和⑤。若支持晚绑定则为①和③。
(function( jQuery ) { // String to Object flags format cache
var flagsCache = {}; // Convert String-formatted flags into Object-formatted ones and store in cache
function createFlags( flags ) {
var object = flagsCache[ flags ] = {},
i, length;
flags = flags.split( /\s+/ );
for ( i = , length = flags.length; i < length; i++ ) {
object[ flags[i] ] = true;
}
return object;
} /** 特性说明
* once: 启动仅遍历执行回调函数队列一次特性,遍历结束后废弃该管理器
* memory: 启动回调函数晚绑定特性
* unique: 启动回调函数唯一性特性
* stopOnFalse: 启动回调函数返回false,则废弃该管理器
*/
jQuery.Callbacks = function( flags ) {
// 特性标识
flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; var // 回调函数队列
list = [],
// 请求队列(不要被变量名欺骗,不是栈结构而是队列结构),用于暂存发起遍历执行回调函数队列的请求,元素数据结构为[ctx, args]
stack = [],
// 标识是否支持回调函数晚绑定, 不支持则为true,支持则为[ctx, args]
memory,
// 表示是否正在遍历回调函数队列
firing,
// 初始化回调函数队列遍历的起始索引
firingStart,
// 回调函数队列遍历的上限
firingLength,
// 回调函数队列遍历的起始索引
firingIndex,
// 私有方法:添加回调函数到队列
add = function( args ) {
var i,
length,
elem,
type;
for (i = , length = args.length; i < length; i++) {
elem = args[i];
type = jQuery.type(elem);
if (type === "array") {
// 递归添加到回调函数队列
add(elem);
} else if (type === "function") {
// 开启唯一性特性,且队列中已经有相同的函数则不入队
if (!flags.unique || !self.has( elem )) {
list.push(elem);
}
}
}
},
// 私有方法:遍历队列执行队列中的函数
fire = function(context, args) {
args = args || [];
// 标识是否支持回调函数晚绑定, 不支持则为true,支持则为[ctx, args]
memory = !flags.memory || [context, args];
firing = true;
firingIndex = firingStart || ;
firingStart = ;
firingLength = list.length;
// 由于在循环期间有可能管理器会被废弃,因此需要在循环条件中检查list的有效性
for (;list && firingIndex < firingLength; firingIndex++) {
if (list[firingIndex].apply(context, args) === false && flags.stopOnFalse) {
memory = true; // 标识中止遍历队列操作,效果和不支持回调函数晚绑定一致
break;
}
}
firing = false;
if (list) {
if (!flags.once) {
if (stack && stack.length) {
// 关闭仅遍历一次回调函数队列特性时
// 请求队列首元素出队,再次遍历执行回调函数队列
memory = stack.shift();
self.fireWith(memory[], memory[]);
}
} else if (memory === true) {
// 当开启仅遍历一次回调函数队列特性,且发生了中止遍历队列操作或不支持回调函数晚绑定,
// 则废弃当前回调函数队列管理器
self.disable();
} else {
list = [];
}
}
},
// 回调函数队列管理器
self = {
// 添加回调函数到队列中
add: function() {
if (list) {
var length = list.length;
// 如果正在遍历执行回调函数队列,那么添加函数到队列后马上更新遍历上限,从而执行新加入的回调函数
add(arguments);
if (firing) {
firingLength = list.length;
} else if (memory && memory !== true) {
// 遍历执行回调函数已结束,并且支持函数晚绑定则从上次遍历结束时的索引位开始继续遍历回调函数队列
firingStart = length;
fire(memory[], memory[]);
}
}
return this;
},
// 从队列中删除回调函数
remove: function() {
if (list) {
var args = arguments,
argIndex = ,
argLength = args.length;
for (; argIndex < argLength; argIndex++) {
for ( var i = ; i < list.length; i++) {
if (args[argIndex] === list[i]) {
// 由于删除队列的一个元素,因此若此时正在遍历执行回调函数队列,
// 则需要调整当前遍历索引和遍历上限
if (firing) {
if (i <= firingLength) {
firingLength--;
if (i <= firingIndex) {
firingIndex--;
}
}
}
// 删除回调函数
list.splice(i--, );
// 如果开启了回调函数唯一性的特性,则只需删除一次就够了
if (flags.unique) {
break;
}
}
}
}
}
return this;
},
// 对回调函数作唯一性检查
has: function( fn ) {
if ( list ) {
var i = ,
length = list.length;
for ( ; i < length; i++ ) {
if ( fn === list[ i ] ) {
return true;
}
}
}
return false;
},
// 清空回调函数队列
empty: function() {
list = [];
return this;
},
// 废除该回调函数队列
disable: function() {
list = stack = memory = undefined;
return this;
},
// 状态:是否已废弃
disabled: function() {
return !list;
},
// 不在处理遍历执行回调函数队列的请求
lock: function() {
// 不再处理遍历执行回调函数队列的请求
stack = undefined;
if (!memory || memory === true) {
// 当未遍历过回调函数队列
// 或关闭晚绑定特性则马上废弃该管理器
self.disable();
}
return this;
},
// 状态:是否已被锁定
locked: function() {
return !stack;
},
// 发起遍历队列执行队列函数的请求
fireWith: function(context,args) {
if (stack) {
if (firing) {
if (!flags.once) {
// 若正在遍历队列,并且关闭仅遍历一次队列的特性时,将此请求入队
stack.push([context, args]);
}
} else if (!flags.once || !memory) {
// 关闭仅遍历一次队列的特性
// 或从未遍历过回调函数队列时,执行遍历过回调函数队列操作
fire(context, args);
}
}
return this;
},
// 发起遍历队列执行队列函数的请求
fire: function() {
self.fireWith(this, arguments);
return this;
},
// 状态:是否已遍历过回调函数队列
fired: function() {
return !!memory;
}
}; return self;
};
})( jQuery );
2. 然后就是jQuery.Deferred的改造
$.Deferred = function(){
// 对原来的Deferred实例改造为两个不可重复遍历函数队列的回调函数队列管理器
var doneList = jQuery.Callbacks("once memory"),
failList = jQuery.Callbacks("once memory");
// 新增的回调函数队列管理器,可多次遍历其函数队列
var progressList = jQuery.Callbacks("memory");
...........................................
}
1.7中通过 私有属性state 明确标识Deferred实例的状态(pending、resolved和rejected),但可惜的是这些属性对Deferred实例的行为没有任何作用,感觉有没有这些状态都没有所谓。
经过这样一改,就更明确Deferred实例其实对三个回调函数队列的统一管理入口而已了。
五、又一次靠近Promise/A+规范——1.8
jQuery1.8的jQuery.Deferred依然依靠jQuery.Callbacks函数生成的三个回调函数队列管理器作为Deferred的构建基础,该版本大部分均为对jQuery.Deferred和jQuery.Callbacks代码结构、语义层面的局部重构,使得更容易理解和维护,尤其是对jQuery.Callbacks代码重构后,回调函数队列管理器实例的状态关系转换清晰不少。
而比较大的局部功能重构是jQuery.Deferred的then方法被重构成为pipe方法的别名,而pipe函数的实现为Promise/A规范中的then方法,因此1.8的then方法与旧版本的then方法不完全兼容。
六、 保持现状——1.9&2.1
jQuery1.9和2.1并没重构或为jQuery.Deferred添加新功能,可以直接跳过。
七、总结
通过上述内容大家已经清楚jQuery.Deferred并不是Promise/A+规范的完整实现(甚至可以说是相距甚远),且jQuery1.8中then函数的实现方式与旧版本的不同,埋下了兼容陷阱,但由于jQuery.Deferred受众面少(直接使用Ajax、effects和queue模块的Promise形式的API较多),因此影响范围不大,庆幸庆幸啊!
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4158939.html ^_^肥子John
八、参考
《JavaScript架构设计》
JS魔法堂:jQuery.Deferred(jQuery1.5-2.1)源码剖析的更多相关文章
- jQuery deferred应用之ajax详细源码分析(二)
在上一节中,我只贴出了$.Deferred的源码分析,并没用讲解怎么使用它,现在我们先看看$.ajax是如何使用它,让我们进行异步任务的处理. 如果没看上节的代码,请先稍微了解一下jQuery Def ...
- JS魔法堂:那些困扰你的DOM集合类型
一.前言 大家先看看下面的js,猜猜结果会怎样吧! 可选答案: ①. 获取id属性值为id的节点元素 ②. 抛namedItem is undefined的异常 var nodes = documen ...
- JS魔法堂:判断节点位置关系
一.前言 在polyfill querySelectorAll 和写弹出窗时都需要判断两个节点间的位置关系,通过jQuery我们可以轻松搞定,但原生JS呢?下面我将整理各种判断方法,以供日后查阅. 二 ...
- JS魔法堂:jsDeferred源码剖析
一.前言 最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(<JavaScript框架设计& ...
- JS魔法堂:属性、特性,傻傻分不清楚
一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...
- JS魔法堂:不完全国际化&本地化手册 之 理論篇
前言 最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...
- JS魔法堂:不完全国际化&本地化手册 之 实战篇
前言 最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...
- JS魔法堂:剖析源码理解Promises/A规范
一.前言 Promises/A是由CommonJS组织制定的异步模式编程规范,有不少库已根据该规范及后来经改进的Promises/A+规范提供了实现 如Q, Bluebird, when, rsvp. ...
- JS魔法堂:mmDeferred源码剖析
一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...
- JS魔法堂:LINK元素深入详解
一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...
随机推荐
- JS写的排序算法演示
看到网上有老外写的,就拿起自已之前完成的jmgraph画图组件也写了一个.想了解jmgraph的请移步:https://github.com/jiamao/jmgraph 当前演示请查看:http:/ ...
- 使用Ant自动化我们的java项目生成
现在我们已经了解如何定义属性.依赖关系以及如何运行ant,接下来我们将学习怎样使用ant编译java源代码并生成jar文件. 编译源代码 由于Ant的主要目标就是生成java应用程序,它内置了java ...
- C#入门基础三
封装:简化用户接口,隐藏实现细节. get{return 属性值:} set{属性值 = value:} 继承:子类继承父类所有非私有成员.继承具有传递性,单根性. 隐式继承:用引号(:)实现. 显示 ...
- IOS 推送-客户端处理推送消息
IOS 推送-客户端处理推送消息 1.推送调用顺序 APN push的消息到达后,UIApplicationDelegate有两个方法和处理消息有关: 1)application:didReceive ...
- 说说设计模式~策略模式(Strategy)
返回目录 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.而对于客户端(UI)来说,可以通过IOC再配合工厂模块,实现动态策略的切换,策略模块通常于一个抽象策略对象(in ...
- python多线程网络编程
背景 使用过flask框架后,我对request这个全局实例非常感兴趣.它在客户端发起请求后会保存着所有的客户端数据,例如用户上传的表单或者文件等.那么在很多客户端发起请求时,服务器是怎么去区分不同的 ...
- Atitit 如何利用先有索引项进行查询性能优化
Atitit 如何利用先有索引项进行查询性能优化 1.1. 再分析的话就是我们所写的查询条件,其实大部分情况也无非以下几种:1 1.2. 范围查找 动态索引查找1 1.2.1. 索引联合 所谓的索引联 ...
- Atitit 发帖机系列(7) 词法分析的方法attilax大总结)
Atitit 发帖机系列(7) 词法分析的方法attilax大总结) 1.1. 词法分析貌似俩大方法,一个直接根据状态图转换,一个根据dfa1 1.2. switchcase或者ifelse 最原始方 ...
- 批处理集锦——(4)2>nul和1>nul是什么意思?
>nul 是屏蔽操作成功显示的信息,但是出错还是会显示(即1>nul) 2>nul 是屏蔽操作失败显示的信息,如果成功依旧显示. >nul 2>nul 就是正确的错误的一 ...
- salesforce 零基础开发入门学习(六)简单的数据增删改查页面的构建
VisualForce封装了很多的标签用来进行页面设计,本篇主要讲述简单的页面增删改查.使用的内容和设计到前台页面使用的标签相对简单,如果需要深入了解VF相关知识以及标签, 可以通过以下链接查看或下载 ...