一、前言                            

最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(《JavaScript框架设计》提供该资讯,再次感谢),追本溯源地了解jsDeferred是十分有必要的,并且当你看过官网(http://cho45.stfuawsc.com/jsdeferred/)的新手引导后就会有种不好好学学就太可惜的感觉了,而只看API和使用指南是无法满足我对它的好奇心的,通过解读源码读透它的设计思想才是根本。

本文部分内容将和《JS魔法堂:剖析源码理解Promises/A》中的内容作对比来讲解。

由于内容较多,特设目录一坨

二、jsDeferred与Promises/A的核心区别

三、从API说起

  四、细说功能实现

  1. 基础功能部分

    1.1. 构造函数

    1.2. call函数

    1.3. fail函数

    1.4. next函数

    1.5. error函数

  2. 辅助功能部分

    2.1. Deferred.define函数

    2.2. Deferred.isDeferred函数

    2.3. Deferred.wait函数

    2.4. Deferred.next函数

    2.5. Deferred.call函数

    2.6. Deferred.loop函数

    2.7. Deferred.parallel函数

    2.8. Deferred.earlier函数

    2.9. Deferred.chain函数

    2.10. Deferred.connect函数

    2.11. Deferred.register函数

    2.12. Deferred.retry函数

    2.13. Deferred.repeat函数

  五、总结

六、参考

二、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

六、参考                                

《JavaScript框架设计》

jsDeferred官网

JS魔法堂:jsDeferred源码剖析的更多相关文章

  1. JS魔法堂:IMG元素加载行为详解

    一.前言 在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨. 二.资源加载的相关属 ...

  2. JS魔法堂:剖析源码理解Promises/A规范

    一.前言 Promises/A是由CommonJS组织制定的异步模式编程规范,有不少库已根据该规范及后来经改进的Promises/A+规范提供了实现 如Q, Bluebird, when, rsvp. ...

  3. JS魔法堂:mmDeferred源码剖析

    一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...

  4. JS魔法堂:属性、特性,傻傻分不清楚

    一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...

  5. JS魔法堂:LINK元素深入详解

    一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...

  6. JS魔法堂:函数节流(throttle)与函数去抖(debounce)

    一.前言 以下场景往往由于事件频繁被触发,因而频繁执行DOM操作.资源加载等重行为,导致UI停顿甚至浏览器崩溃. 1. window对象的resize.scroll事件 2. 拖拽时的mousemov ...

  7. Node 进阶:express 默认日志组件 morgan 从入门使用到源码剖析

    本文摘录自个人总结<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 章节概览 morgan是express默认的日志中间件, ...

  8. socket_server源码剖析、python作用域、IO多路复用

    本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...

  9. JS魔法堂:追忆那些原始的选择器

    一.前言                                                                                                 ...

随机推荐

  1. 麒麟系统使用root权限运行程序

    最近在虚拟机里安装了个国产麒麟系统.(不知道麒麟系统的百度下.) ************************************************** 官方网址:http://www. ...

  2. 编译Linux内核

    下面的实验以 debian7.5 64bit 为例. 获取源码 获取 debian7.5 本身的源码非常简单: sudo apt-get install linux-source https://ww ...

  3. ADO.NET封装的SqlHelper

    参照别人的方法,顺便再次复习下ADO.NET的相关知识.为自己的类库做准备. namespace Common.SqlHelper { /// <summary> /// ADO.NET- ...

  4. [锋利的JQ]-超链接提示效果

    关键知识点: 1.事件对象:当事件一旦被触发,事件对象便会创立.事件对象只能作用于该事件的事件处理程序. 2.认识了mousemove事件了连续触发执行的特性. 代码: HTML: <div c ...

  5. Clr编写Insert Triggr

    在CLR编写一个插入娄据的触发器. 这个触发器是当对表插入数据时,即时把刚才插入的数据显示出来: 可复制代码: public static void tri_RetrieveJustInsertedD ...

  6. 控件使用经验-MVP模式+控件封装

    项目背景 几年前参与了一个面向学校的人事管理软件的开发,基于WinForm平台.今天主要想谈一谈其中关于控件的使用经验.这个项目我们大量使用了第三方控件.由于这个产品的生命周期很长,我们在设计时要考虑 ...

  7. QQ--模拟发表带图说说

    发表说说之前,必须登录. 模拟QQ登录 >> http://www.cnblogs.com/deeround/p/4386629.html 发表带图说说,自然少不了上传图片,我这使用的PC ...

  8. java调用Oracle分页存储过程

    Java程序 package com.test; import java.sql.CallableStatement; import java.sql.Connection; import java. ...

  9. Java总结篇系列:Java 反射

    Java反射: package com.corn; import java.lang.reflect.Constructor; import java.lang.reflect.Field; impo ...

  10. ASP.NET访问Excel 失败的解决方法(错误号:80070005,8000401a)

    用asp.net把值写入Excel在本地测试通过,然后提交服务器后老是写入不成功 并提示错误: Retrieving the COM class factory for component with ...