一、前言                            

最近在研究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. 从css3书写顺序引出来的border-radius参数

    本鱼表示偶已经不会取标题了... 当时写这篇文章主要是想探讨一下优雅降级和渐进增强的区别,按照正常的逻辑思维,不管是降级还是增强,应该对于效果是没什么区别的,因为后者会覆盖前者,但今天无意看到张鑫旭的 ...

  2. Android Studio1.4.x JNI开发基础-基本环境配置

    从Eclipse时代到Android Studio普及,开发工具越来越好用.早些时候还需要安装Cygwin工具,从Android Studio1.3以后,在Android 环境开发JNI程序搭建开发环 ...

  3. 重读 code complete 说说代码质量

    重读code complete 说说代码质量 2014年的第一篇文章本来计划写些过去一年的总结和新年展望,但是因为还有一些事情要过一阵才能完成,所以姑且不谈这个,说说最近重读code complete ...

  4. 在SQL Server里为什么我们需要更新锁

    今天我想讲解一个特别的问题,在我每次讲解SQL Server里的锁和阻塞(Locking & Blocking)都会碰到的问题:在SQL Server里,为什么我们需要更新锁?在我们讲解具体需 ...

  5. JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后

    Brief 一天有个朋友问我“JS中计算0.7 * 180怎么会等于125.99999999998,坑也太多了吧!”那时我猜测是二进制表示数值时发生round-off error所导致,但并不清楚具体 ...

  6. php对xml文件进行CURD操作

    XML是一种数据存储.交换.表达的标准: - 存储:优势在于半结构化,可以自定义schema,相比关系型二维表,不用遵循第一范式(可以有嵌套关系): - 交换:可以通过schema实现异构数据集成: ...

  7. 计算机中数据实体和数据表示形式(以C#为例)

    摘自网络的一段话: “在程序代码中,可以用多种方式表示数据,十进制.十六进制.八进制都是常用的表示方式,但计算机内部永远就只使用二进制,与你写程序时用什么无关.你说要定义数组int a[10],其中涉 ...

  8. 【转载】MVC使用HandleErrorAttribute自定义异常

    本文导读:在ASP.NET MVC中,可以使用HandleErrorAttribute特性来具体指定如何处理Action抛出的异常.只要某个Action设置了HandleErrorAttribute特 ...

  9. 插入排序---希尔插入排序算法(Javascript版)

    取一个小于n的整数作为第一个增量,把序列分组.所有距离为增量的倍数的元素放在同一个组中.先在各组内进行直接插入排序:然后,取第二个增量(第二个<第一个)重复上述的分组和排序,直至所取的增量=1, ...

  10. iOS 阶段学习第23天笔记(XML数据格式介绍)

    iOS学习(OC语言)知识点整理 一.XML数据格式介绍 1)概念:xml是extensible markup language扩展的标记语言,一般用来表示.传输和存储数据 2)xml与json目前使 ...