JS魔法堂:jsDeferred源码剖析
一、前言
最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(《JavaScript框架设计》提供该资讯,再次感谢),追本溯源地了解jsDeferred是十分有必要的,并且当你看过官网(http://cho45.stfuawsc.com/jsdeferred/)的新手引导后就会有种不好好学学就太可惜的感觉了,而只看API和使用指南是无法满足我对它的好奇心的,通过解读源码读透它的设计思想才是根本。
本文部分内容将和《JS魔法堂:剖析源码理解Promises/A》中的内容作对比来讲解。
由于内容较多,特设目录一坨
1. 基础功能部分
2. 辅助功能部分
二、jsDeferred与Promises/A的核心区别
jsDeferred的特点:
①. 内部通过单向链表结果存储 成功事件处理函数、失败事件处理函数 和 链表中下一个Deferred类型对象;
②. Deferred实例内部没有状态标识(也就是说Deferred实例没有自定义的生命周期);
③. 由于Deferred实例没有状态标识,因此不支持成功/失败事件处理函数的晚绑定;
④. Deferred实例的成功/失败事件是基于事件本身的触发而被调用的;
⑤. 由于Deferred实例没有状态标识,因此成功/失败事件可被多次触发,也不存在不变值作为事件处理函数入参的说法;
Promises/A的特点:
①. 内部通过单向链表结果存储 成功事件处理函数、失败事件处理函数 和 链表中下一个Promise类型对象;
②. Promise实例内部有状态标识:pending(初始状态)、fulfilled(成功状态)和rejected(失败状态),且状态为单方向移动“pending->fulfilled","pending->rejected";(也就是Promse实例存在自定义的生命周期,而生命周期的每个阶段具备不同的事件和操作)
③. 由于Promise实例含状态标识,因此支持事件处理函数的晚绑定;
④. Promise实例的成功/失败事件函数是基于Promise的状态而被调用的。
核心区别
Promises调用成功/失败事件处理函数的两种流程:
①. 调用resolve/reject方法尝试改变Promise实例的状态,若成功改变其状态,则调用Promise当前状态相应的事件处理函数;(类似于触发onchange事件)
②. 通过then方法进行事件绑定,若Promise实例的状态不是pending,则调用Promise当前状态相应的事件处理函数。
由上述可以知道Promises的成功/失败事件处理函数均基于Promise实例的状态而被调用,而非成功/失败事件。
jsDeferred调用成功/失败事件处理函数的流程:
①. 调用call/fail方法触发成功/失败事件,则调用相应的事件处理函数。
因此jsDeferred的是基于事件的。
三、从API说起
下列内容均为大概介绍API接口,具体用法请参考官网。
1. 构造函数
Deferred ,可通过 new Deferred() 或 Deferred() 两种方式创建Deferred实例。
2. 实例方法
Deferred next({Function} fn) ,绑定成功事件处理函数,返回一个新的Deferred实例。
Deferred error({Function} fn) ,绑定失败事件处理函数,返回一个新的Deferred实例。
Deferred call(val*) ,触发成功事件,返回一个新的Deferred实例。
Deferred fail(val*) ,触发失败事件,返回一个新的Deferred实例。
3. 静态属性
{Function} Deferred.ok ,默认的成功事件处理函数。
{Function} Deferred.ng ,默认的失败事件处理函数。
{Array} Deferred.methods ,默认的向外暴露的静态方法。(供 Deferred.define方法 使用)
4. 静态方法
{Function}Deferred Deferred.define(obj, list) ,暴露list制定的静态方法到obj上,obj默认是全局对象。
Deferred Deferred.call({Function} fn [, arg]*) ,创建一个Deferred实例并且触发其成功事件。
Deferred Deferred.next({Function} fn) ,创建一个Deferred实例并且触发其成功事件,其实就是无法传入参到成功事件处理函数的 Deferred.call() 。
Deferred Deferred.wait(sec) ,创建一个Deferred实例并且等sec秒后触发其成功事件。
Deferred Deferred.loop(n, fun) ,循环执行fun并且上一个fun,最后一个fun的返回值将作为Deferred实例的成功事件处理函数的入参。
Deferred Deferred.parallel(dl) ,将dl中非Deferred对象转换为Deferred对象,然后并行触发dl中的Deferred实例的成功事件,当
所有Deferred对象均调用了成功事件处理函数后,返回的Deferred实例则触发成功事件,并且所有返回值将被封装为数组作为Deferred实例的成功事件处理函数的入参。
Deferred Deferred.earlier(dl) ,将dl中非Deferred对象转换为Deferred对象,然后并行触发dl中的Deferred实例的成功事件,当
其中一个Deferred对象调用了成功事件处理函数则终止其他Deferred对象的触发成功事件,而返回的Deferred实例则触发成功事件,并且那个被调用的成功事件处理函数的返回值为Deferred实例的成功事件处理函数的入参。
Boolean Deferred.isDeferred(obj) ,判断obj是否为Deferred类型。
Deferred Deferred.chain(args) ,创建一个Deferred实例一次执行args的函数
Deferred Deferred.connect(funo, options) ,将某个函数封装为Deferred对象
Deferred Deferred.register(name, fn) ,将静态方法附加到Deferred.prototype上
Deferred Deferred.retry(retryCount, funcDeferred, options) ,尝试调用funcDeffered方法(返回值类型为Deferred)retryCount,直到触发成功事件或超过尝试次数为止。
Deferred Deferred.repeat(n, fun) ,循环执行fun方法n次,若fun的执行事件超过20毫秒则先将UI线程的控制权交出,等一会儿再执行下一轮的循环。
jsDeferred采用DSL风格的API设计,语义化我喜欢啊!
四、细说功能实现
1. 基础功能部分
1.1. 构造函数
function Deferred () { return (this instanceof Deferred) ? this.init() : new Deferred() }
// 默认的成功事件处理函数
Deferred.ok = function (x) { return x };
// 默认的失败事件处理函数
Deferred.ng = function (x) { throw x };
Deferred.prototype = {
// 初始化函数
init : function () {
this._next = null;
this.callback = {
ok: Deferred.ok,
ng: Deferred.ng
};
return this;
}};
1.2. call函数
Deferred.prototype.call = function (val) { return this._fire("ok", val) };
Deferred.prototype._filre = function(okng, value){
var next = "ok";
try {
// 调用当前Deferred实例的事件处理函数
value = this.callback[okng].call(this, value);
} catch (e) {
next = "ng";
value = e;
if (Deferred.onerror) Deferred.onerror(e);
}
if (Deferred.isDeferred(value)) {
// 若事件处理函数返回一个新Deferred实例,则将新Deferred实例的链表指针指向当前Deferred实例的链表指针指向,
// 这样新Deferred实例的事件处理函数就会先与原链表中其他Deferred实例的事件处理函数被调用。
value._next = this._next;
} else {
if (this._next) this._next._fire(next, value);
}
return this;
};
1.3. fail函数
Deferred.prototype.fail = function (err) { return this._fire("ng", err) };
1.4. next函数
Deferred.prototype.next = function (fun) { return this._post("ok", fun) };
Deferred.prototype._post = function (okng, fun) {
// 创建一个新的Deferred实例,插入Deferred链表尾,并将事件处理函数绑定到新的Deferred上
this._next = new Deferred();
this._next.callback[okng] = fun;
return this._next;
};
《JS魔法堂:剖析源码理解Promises/A》中的官网实现示例是将事件处理函数绑定到当前的Promise实例,而不是新创的Promise实例。而jsDeferred则是绑定到新创建的Deferred实例上。这是因为Promise实例默认的事件处理函数为undefined,而Deferred是含默认的事件处理函数的。
1.5. error函数
Deferred.prototype.error = function (fun) { return this._post("ng", fun) };
2. 辅助功能部分
jsDeferred的基础功能部分都十分好理解,我认为它的精彩之处在于类方法——辅助功能部分。
2.1. Deferred.define函数实现
Deferred.define = function (obj, list) {
if (!list) list = Deferred.methods;
// 以全局对象作为默认的入潜目标
// 由于带代码运行在sloppy模式,因此函数内的this指针指向全局对象。若运行在strict模式,则this指针值为undefined。
// 即使被以strict模式运行的程序调用,本段程序依然以sloppy模式运行使用
if (!obj) obj = (function getGlobal () { return this })();
for (var i = ; i < list.length; i++) {
var n = list[i];
obj[n] = Deferred[n];
}
return Deferred;
};
当我第一次看新手引导中的示例代码
Deferred.define();
next(function(){
............
}).next(function(){
...............
});
这不是就和jdk1.5的静态导入 import static一样吗?!两者同样是以入侵的方式将类方法附加到当前执行上下文中,这种导入的方式有人喜欢有人明令禁止(原上下文被破坏,维护性大大降低)。而我则有一个准则,就是导入的类方法足够少(5个左右,反正能看一眼API就记得那种),团队的小伙伴们均熟知这些API,并且仅以此方式导入一个类的方法到当前执行上下文中。其实能满足这些要求的库不多,还不如起个短小精干的类名作常规导入更实际。这里扯远了,我再看看 Deferred.define方法 吧,其实它除了将类方法导入到当前执行上下文,还可以导入到一个指定的对象中(这个方法比较中用!)
var ctx = {};
Deferred.define(ctx);
ctx.next(function(){
..............
}).next(function(){
.............
});
2.2. Deferred.isDeferred函数实现
Deferred.isDeferred = function (obj) {
return !!(obj && obj._id === Deferred.prototype._id);
};
// 貌似是Mozilla有个插件也叫Deferred,因此不能通过instanceof来检测。cho45于是自定义标志位来作检测,并在github上提交fxxking Mozilla,哈哈!
Deferred.prototype._id = 0xe38286e381ae;
2.3. Deferred.wait函数实现
Deferred.wait = function (n) {
var d = new Deferred(), t = new Date();
var id = setTimeout(function () {
// 入参为实际等待毫秒数,由于各浏览器的setTimeout均有一个最小精准度参数(IE9+和现代浏览器为4msec,IE5.5~8为15.4msec),因此实际等待的时间一定被希望的长
d.call((new Date()).getTime() - t.getTime());
}, n * );
d.canceller = function () { clearTimeout(id) };
return d;
};
刚看到该函数时我确实有点小鸡冻,我们可以将《JS魔法堂:剖析源码理解Promises/A》的第三节“从感性领悟”下的示例,写得于现实生活的思路更贴近了。
// 任务定义部分
var 下班 = function(){};
var 搭车 = function(){};
var 接小孩 = function(){};
var 回家 = function(){}; // 流程部分
next(下班)
.wait(*)
.next(下班)
.wait(*)
.next(搭车)
.wait(*)
.next(接小孩)
.wait(*)
.next(回家);
2.4. Deferred.next函数实现
该函数可为是真个jsDeferred最出彩的地方了,也是后续其他方法的实现基础,它的功能是创建一个新的Deferred对象,并且异步执行该Deferred对象的call方法来触发成功事件。针对运行环境的不同,它提供了相应的异步调用的实现方式并作出降级处理。
Deferred.next =
Deferred.next_faster_way_readystatechange ||
Deferred.next_faster_way_Image ||
Deferred.next_tick ||
Deferred.next_default;
由浅入深,我们先看看使用setTimeout实现异步的 Deferred.next_default方法 (存在最小时间精度的问题)
Deferred.next_default = function (fun) {
var d = new Deferred();
var id = setTimeout(function () { d.call() }, );
d.canceller = function () { clearTimeout(id) };
if (fun) d.callback.ok = fun;
return d;
};
然后是针对nodejs的 Deferred.next_tick方法
Deferred.next_tick = function (fun) {
var d = new Deferred();
// 使用process.nextTick来实现异步调用
process.nextTick(function() { d.call() });
if (fun) d.callback.ok = fun;
return d;
};
然后就是针对现代浏览器的 Deferred.next_faster_way_Image方法
Deferred.next_faster_way_Image = function (fun) {
var d = new Deferred();
var img = new Image();
var handler = function () {
d.canceller();
d.call();
};
img.addEventListener("load", handler, false);
img.addEventListener("error", handler, false);
d.canceller = function () {
img.removeEventListener("load", handler, false);
img.removeEventListener("error", handler, false);
};
// 请求一个无效data uri scheme导致马上触发load或error事件
// 注意:先绑定事件处理函数,再设置图片的src是个良好的习惯。因为设置img.src属性后就会马上发起请求,假如读的是缓存那有可能还未绑定事件处理函数,事件已经被触发了。
img.src = "data:image/png," + Math.random();
if (fun) d.callback.ok = fun;
return d;
};
最后就是针对IE5.5~8的 Deferred.next_faster_way_readystatechange方法
Deferred.next_faster_way_readystatechange = ((typeof window === 'object') && (location.protocol == "http:") && !window.opera && /\bMSIE\b/.test(navigator.userAgent)) && function (fun) {
var d = new Deferred();
var t = new Date().getTime();
/* 原理:
由于浏览器对并发请求数作出限制(IE5.5~8为2~3,IE9+和现代浏览器为6),
因此当并发请求数大于上限时,会让请求的发起操作排队执行,导致延时更严重了。
实现手段:
以150毫秒为一个周期,每个周期以通过setTimeout发起的异步执行作为起始,
周期内的其他异步执行操作均通过script请求实现。
(若该方法将在短时间内被频繁调用,可以将周期频率再设高一些,如100毫秒)
*/
if (t - arguments.callee._prev_timeout_called < ) {
var cancel = false;
var script = document.createElement("script");
script.type = "text/javascript";
// 采用无效的data uri sheme马上触发readystate变化
script.src = "data:text/javascript,";
script.onreadystatechange = function () {
// 由于在一次请求过程中script的readystate会变化多次,因此通过cancel标识来保证仅调用一次call方法
if (!cancel) {
d.canceller();
d.call();
}
};
d.canceller = function () {
if (!cancel) {
cancel = true;
script.onreadystatechange = null;
document.body.removeChild(script);
}
};
// 不同于img元素,script元素需要添加到dom树中才会发起请求
document.body.appendChild(script);
} else {
arguments.callee._prev_timeout_called = t;
var id = setTimeout(function () { d.call() }, );
d.canceller = function () { clearTimeout(id) };
}
if (fun) d.callback.ok = fun;
return d;
};
2.5. Deferred.call函数实现
Deferred.call = function (fun) {
var args = Array.prototype.slice.call(arguments, );
// 核心在Deferred.next
return Deferred.next(function () {
return fun.apply(this, args);
});
};
2.6. Deferred.loop函数实现
Deferred.loop = function (n, fun) {
// 入参n类似于Python中range的效果
// 组装循环的配置信息
var o = {
begin : n.begin || ,
end : (typeof n.end == "number") ? n.end : n - ,
step : n.step || ,
last : false,
prev : null
};
var ret, step = o.step;
return Deferred.next(function () {
function _loop (i) {
if (i <= o.end) {
if ((i + step) > o.end) {
o.last = true;
o.step = o.end - i + ;
}
o.prev = ret;
ret = fun.call(this, i, o);
if (Deferred.isDeferred(ret)) {
return ret.next(function (r) {
ret = r;
return Deferred.call(_loop, i + step);
});
} else {
return Deferred.call(_loop, i + step);
}
} else {
return ret;
}
}
return (o.begin <= o.end) ? Deferred.call(_loop, o.begin) : null;
});
};
上述代码的理解难点在于Deferred实例A的事件处理函数若返回一个新的Deferred实例B,而实例A的Deferred链表中原本指向Deferred实例C,那么当调用实例A的call方法时是实例C的事件处理函数先被调用,还是实例B的事件处理函数先被调用呢?这时只需细读 Deferred.prototype.call方法 的实现就迎刃而解了,答案是先调用实例B的事件处理函数哦!
2.7. Deferred.parallel函数实现
Deferred.parallel = function (dl) {
// 对入参作处理
var isArray = false;
if (arguments.length > ) {
dl = Array.prototype.slice.call(arguments);
isArray = true;
} else if (Array.isArray && Array.isArray(dl) || typeof dl.length == "number") {
isArray = true;
} var ret = new Deferred(), values = {}, num = ;
for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
// 若d为函数类型,则封装为Deferred实例
// 若d既不是函数类型,也不是Deferred实例则报错哦!
if (typeof d == "function")
dl[i] = d = Deferred.next(d);
d.next(function (v) {
values[i] = v;
if (--num <= ) {
// 凑够数就触发事件处理函数
if (isArray) {
values.length = dl.length;
values = Array.prototype.slice.call(values, );
}
ret.call(values);
}
}).error(function (e) {
ret.fail(e);
});
num++;
})(dl[i], i); // 当dl为空时触发Deferred实例的成功事件
if (!num) Deferred.next(function () { ret.call() });
ret.canceller = function () {
for (var i in dl) if (dl.hasOwnProperty(i)) {
dl[i].cancel();
}
};
return ret;
};
通过源码我们可以知道parallel的入参必须为函数或Deferred实例,否则会报错哦!
2.8. Deferred.earlier函数实现
Deferred.earlier = function (dl) {
// 对入参作处理
var isArray = false;
if (arguments.length > ) {
dl = Array.prototype.slice.call(arguments);
isArray = true;
} else if (Array.isArray && Array.isArray(dl) || typeof dl.length == "number") {
isArray = true;
}
var ret = new Deferred(), values = {}, num = ;
for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
// d只能是Deferred实例,否则抛异常
d.next(function (v) {
values[i] = v;
// 一个Deferred实例触发成功事件则终止其他Deferred实例触发成功事件了
if (isArray) {
values.length = dl.length;
values = Array.prototype.slice.call(values, );
}
ret.call(values);
ret.canceller();
}).error(function (e) {
ret.fail(e);
});
num++;
})(dl[i], i); // 当dl为空时触发Deferred实例的成功事件
if (!num) Deferred.next(function () { ret.call() });
ret.canceller = function () {
for (var i in dl) if (dl.hasOwnProperty(i)) {
dl[i].cancel();
}
};
return ret;
};
通过源码我们可以知道earlier的入参必须为Deferred实例,否则会报错哦!
2.9. Deferred.chain函数实现
Deferred.chain = function () {
var chain = Deferred.next();
// 生成Deferred实例链表,链表长度等于arguemtns.length
for (var i = , len = arguments.length; i < len; i++) (function (obj) {
switch (typeof obj) {
case "function":
var name = null;
// 通过函数名决定是订阅成功还是失败事件
try {
name = obj.toString().match(/^\s*function\s+([^\s()]+)/)[];
} catch (e) { }
if (name != "error") {
chain = chain.next(obj);
} else {
chain = chain.error(obj);
}
break;
case "object":
// 这里的object包含形如{0:function(){}, 1: Deferred实例}、Deferred实例
chain = chain.next(function() { return Deferred.parallel(obj) });
break;
default:
throw "unknown type in process chains";
}
})(arguments[i]);
return chain;
};
2.10. Deferred.connect函数实现
Deferred.connect = function (funo, options) {
var target, // 目标函数所属的对象
func, // 目标函数
obj; // 配置项
if (typeof arguments[] == "string") {
target = arguments[];
func = target[arguments[]];
obj = arguments[] || {};
} else {
func = arguments[];
obj = arguments[] || {};
target = obj.target;
} // 预设定的入参
var partialArgs = obj.args ? Array.prototype.slice.call(obj.args, ) : [];
// 指出成功事件的回调处理函数位于原函数的入参索引
var callbackArgIndex = isFinite(obj.ok) ? obj.ok : obj.args ? obj.args.length : undefined;
// 指出失败事件的回调处理函数位于原函数的入参索引
var errorbackArgIndex = obj.ng; return function () {
// 改造成功事件处理函数,将预设入参和实际入参作为成功事件处理函数的入参
var d = new Deferred().next(function (args) {
var next = this._next.callback.ok;
this._next.callback.ok = function () {
return next.apply(this, args.args);
};
}); // 合并预设入参和实际入参
var args = partialArgs.concat(Array.prototype.slice.call(arguments, ));
// 打造func的成功事件处理函数,内部将触发d的成功事件
if (!(isFinite(callbackArgIndex) && callbackArgIndex !== null)) {
callbackArgIndex = args.length;
}
var callback = function () { d.call(new Deferred.Arguments(arguments)) };
args.splice(callbackArgIndex, , callback); // 打造func的失败事件处理函数,内部将触发d的失败事件
if (isFinite(errorbackArgIndex) && errorbackArgIndex !== null) {
var errorback = function () { d.fail(arguments) };
args.splice(errorbackArgIndex, , errorback);
}
// 相当于setTimeout(function(){ func.apply(target, args) })
Deferred.next(function () { func.apply(target, args) });
return d;
};
};
如何简化将setTimeout、setInterval、XmlHttpRequest等异步API封装为Deferred对象(或Promise)对象的步骤是一件值思考的事情,而jsDeferred的connect类方法提供了一个很好的范本。
2.11. Deferred.register函数实现
Deferred.register = function (name, fun) {
this.prototype[name] = function () {
var a = arguments;
return this.next(function () {
return fun.apply(this, a);
});
};
}; Deferred.register("loop", Deferred.loop);
Deferred.register("wait", Deferred.wait);
2.12. Deferred.retry函数实现
Deferred.retry = function (retryCount, funcDeferred, options) {
if (!options) options = {}; var wait = options.wait || ; // 尝试的间隔时间,存在最小时间精度所导致的延时问题
var d = new Deferred();
var retry = function () {
// 有funcDeferred内部触发事件
var m = funcDeferred(retryCount);
m.next(function (mes) {
d.call(mes);
}).
error(function (e) {
if (--retryCount <= ) {
d.fail(['retry failed', e]);
} else {
setTimeout(retry, wait * );
}
});
};
// 异步执行retry方法
setTimeout(retry, );
return d;
};
2.13. Deferred.repeat函数实现
Deferred.repeat = function (n, fun) {
var i = , end = {}, ret = null;
return Deferred.next(function () {
var t = (new Date()).getTime();
// 当fun的执行耗时小于20毫秒,则马上继续执行下一次的fun;
// 若fun的执行耗时大于20毫秒,则将UI线程控制权交出,并将异步执行下一次的fun。
// 从而降低因循环执行耗时操作使页面卡住的风险。
do {
if (i >= n) return null;
ret = fun(i++);
} while ((new Date()).getTime() - t < );
return Deferred.call(arguments.callee);
});
};
五、总结
通过剖析jsDeferred源码我们更深刻地理解Promises/A和Promises/A+规范,也了解到setTimeout的延时问题和通过img、script等事件缩短延时的解决办法(当然这里并没有详细记录解决办法的细节),最重要的是吸取大牛们的经验和了解API设计的艺术。但这里我提出一点对jsDeferred设计上的吐槽,就是Deferred实例的私有成员还是可以通过实例直接引用,而不像Promises/A官网实现示例那样通过闭包隐藏起来。
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4141918.html ^_^肥子John
六、参考
JS魔法堂:jsDeferred源码剖析的更多相关文章
- JS魔法堂:IMG元素加载行为详解
一.前言 在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨. 二.资源加载的相关属 ...
- JS魔法堂:剖析源码理解Promises/A规范
一.前言 Promises/A是由CommonJS组织制定的异步模式编程规范,有不少库已根据该规范及后来经改进的Promises/A+规范提供了实现 如Q, Bluebird, when, rsvp. ...
- JS魔法堂:mmDeferred源码剖析
一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...
- JS魔法堂:属性、特性,傻傻分不清楚
一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...
- JS魔法堂:LINK元素深入详解
一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...
- JS魔法堂:函数节流(throttle)与函数去抖(debounce)
一.前言 以下场景往往由于事件频繁被触发,因而频繁执行DOM操作.资源加载等重行为,导致UI停顿甚至浏览器崩溃. 1. window对象的resize.scroll事件 2. 拖拽时的mousemov ...
- Node 进阶:express 默认日志组件 morgan 从入门使用到源码剖析
本文摘录自个人总结<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 章节概览 morgan是express默认的日志中间件, ...
- socket_server源码剖析、python作用域、IO多路复用
本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...
- JS魔法堂:追忆那些原始的选择器
一.前言 ...
随机推荐
- 从css3书写顺序引出来的border-radius参数
本鱼表示偶已经不会取标题了... 当时写这篇文章主要是想探讨一下优雅降级和渐进增强的区别,按照正常的逻辑思维,不管是降级还是增强,应该对于效果是没什么区别的,因为后者会覆盖前者,但今天无意看到张鑫旭的 ...
- Android Studio1.4.x JNI开发基础-基本环境配置
从Eclipse时代到Android Studio普及,开发工具越来越好用.早些时候还需要安装Cygwin工具,从Android Studio1.3以后,在Android 环境开发JNI程序搭建开发环 ...
- 重读 code complete 说说代码质量
重读code complete 说说代码质量 2014年的第一篇文章本来计划写些过去一年的总结和新年展望,但是因为还有一些事情要过一阵才能完成,所以姑且不谈这个,说说最近重读code complete ...
- 在SQL Server里为什么我们需要更新锁
今天我想讲解一个特别的问题,在我每次讲解SQL Server里的锁和阻塞(Locking & Blocking)都会碰到的问题:在SQL Server里,为什么我们需要更新锁?在我们讲解具体需 ...
- JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后
Brief 一天有个朋友问我“JS中计算0.7 * 180怎么会等于125.99999999998,坑也太多了吧!”那时我猜测是二进制表示数值时发生round-off error所导致,但并不清楚具体 ...
- php对xml文件进行CURD操作
XML是一种数据存储.交换.表达的标准: - 存储:优势在于半结构化,可以自定义schema,相比关系型二维表,不用遵循第一范式(可以有嵌套关系): - 交换:可以通过schema实现异构数据集成: ...
- 计算机中数据实体和数据表示形式(以C#为例)
摘自网络的一段话: “在程序代码中,可以用多种方式表示数据,十进制.十六进制.八进制都是常用的表示方式,但计算机内部永远就只使用二进制,与你写程序时用什么无关.你说要定义数组int a[10],其中涉 ...
- 【转载】MVC使用HandleErrorAttribute自定义异常
本文导读:在ASP.NET MVC中,可以使用HandleErrorAttribute特性来具体指定如何处理Action抛出的异常.只要某个Action设置了HandleErrorAttribute特 ...
- 插入排序---希尔插入排序算法(Javascript版)
取一个小于n的整数作为第一个增量,把序列分组.所有距离为增量的倍数的元素放在同一个组中.先在各组内进行直接插入排序:然后,取第二个增量(第二个<第一个)重复上述的分组和排序,直至所取的增量=1, ...
- iOS 阶段学习第23天笔记(XML数据格式介绍)
iOS学习(OC语言)知识点整理 一.XML数据格式介绍 1)概念:xml是extensible markup language扩展的标记语言,一般用来表示.传输和存储数据 2)xml与json目前使 ...