很多 javascript 框架都提供了自定义事件(custom events),例如 jquery、yui 以及 dojo 都支持“document ready”事件。而部分自定义事件是源自回调(callback)。

回调将多个事件句柄存储在数组中,当满足触发条件时,回调系统则会从数组中获取对应的句柄并执行。那么,这会有什么陷阱呢?在回答这个问题之前,我们先看下代码。

下面是两段代码依次绑定到 domcontentloaded 事件中

document.addeventlistener("domcontentloaded", function() {
console.log("init: 1");
does_not_exist++; // 这里会抛出异常
}, false); document.addeventlistener("domcontentloaded", function() {
console.log("init: 2");
}, false);

那么运行这段代码会返回什么信息?显然,会看见这些(或者类似的):

init: 1
error: does_not_exist is not defined
init: 2

可以看出,两段函数都被执行。即使第一个函数抛出了个异常,但并不影响第二段代码运行。

麻烦

ok,我们回来看下常见框架中的回调系统。首先,我们看下 jquery 的(因为它很流行):

$(document).ready(function() {
console.log("init: 1");
does_not_exist++; // 这里会抛出异常
}); $(document).ready(function() {
console.log("init: 2");
});

然后控制台中输出了什么?

init: 1
error: does_not_exist is not defined

这样问题就很明了了。回调系统其实很脆弱 -- 如果中间有段代码抛出了异常,那么其余将不会被执行。想象下在实际情况中,这后果可能会更严重,譬如有些糟糕的插件可能会“一粒老屎坏了一锅粥”。

其他的框架,dojo 的情况和 jquery 类似,不过 yui 的情况有些许不同。在它的回调系统中,使用了 try/catch 语句避免因异常发生的中断。但有个小小的负面影响,就是看不到相应的异常了。

yahoo.util.event.ondomready(function() {
console.log("init: 1");
does_not_exist++; // 这里会抛出异常
}); yahoo.util.event.ondomready(function() {
console.log("init: 2");
});

输出:

init: 1
init: 2

那么,有无完美的解决方案呢?

解决方案

我想到了个解决方案,就是将回调和事件结合起来。可以先建立个事件,当回调触发时才运行它。由于每个事件都有其独立的运行环境(execution context),那么即使其中某个事件抛出了异常将不会影响其他的回调。

这听起来有点复杂,还是代码说话吧。

var currenthandler;

// 标准事件支持
if (document.addeventlistener) {
document.addeventlistener("fakeevents", function() {
// 执行回调
currenthandler();
}, false); // 新建事件
var dispatchfakeevent = function() {
var fakeevent = document.createevent("uievents");
fakeevent.initevent("fakeevents", false, false);
document.dispatchevent(fakeevent);
};
} else {
// 针对 ie 的代码在后面详细阐述
} var onloadhandlers = []; // 将回调加入数组中
function addonload(handler) {
onloadhandlers.push(handler);
}; // 逐条取出回调,并利用上述新建的事件执行
onload = function() {
for (var i = 0; i < onloadhandlers.length; i++) {
currenthandler = onloadhandlers[i];
dispatchfakeevent();
}
};

万事俱备,让我们将上面坨代码扔到我们新的回调系统中

addonload(function() {
console.log("init: 1");
does_not_exist++; // 这里会抛出异常
}); addonload(function() {
console.log("init: 2");
});

上帝保佑,看运行结果我们看到了如下的信息:

init: 1
error: does_not_exist is not defined
init: 2

赞!这就是我们期望的。这两个回调都运行而且互不影响,并且还能获得异常的信息,太好了!

好了,我们回过头来扶起 internet explorer 这个“阿斗”(我已经听见场下观众的建议了)。internet explorer 不支持 w3c 的标准事件规范,谢天谢地好在它有自身的实现 -- 有个 fireevents 的方法,但只能在用户事件的时候触发(例如用户点击 click)。

不过终于找到了门道,我们来看下具体代码:

var currenthandler;

if (document.addeventlistener) {
// 省略上述的代码
} else if (document.attachevent) { // msie
// 利用扩展属性,当此对象被改变时触发
document.documentelement.fakeevents = 0;
document.documentelement.attachevent("onpropertychange", function(event) {
if (event.propertyname == "fakeevents") {
// 执行回调
currenthandler();
}
}); dispatchfakeevent = function(handler) {
// 触发 propertychange 事件
document.documentelement.fakeevents++;
};
}

简而言之,殊途同归,只是针对 internet explorer 使用了 propertychange 事件作为触发器。

更新

有些用户留言建议使用 settimeout:

try { callback(); } catch(e){ settimeout(function(){ throw e; }, 0); }

而下面是我的考虑

如没特别的要求,其实定时器的确也能搞定这问题。
上面仅仅是举例说明了这一技术的可行性。 意义在于,目前很多框架在回调系统的实现都非常的
脆弱,这或许能给这些框架能它们提供更优化的思路。
而定时器的实现并非实际的触发了事件,在实际事件
中,事件会被顺序的执行、可相互影响(譬如冒泡)、
还可以停止 -- 而这些是定时器无法做到的。

总之,最重要的是已经实现了包括 internet explorer 在内,使用事件执行回调的实现。如果你正编写基于事件代理的回调系统,我想你会对这一技术感兴趣的。

更新2

prototype 在针对 internet explorer 的自定义事件处理上,也是同上述的方法触发回调:

http://andrewdupont.net/2009/03/24/link-dean-edwards/

译注,prototype 1.6 对应的代码,摘记如下:

function createwrapper(element, eventname, handler) {
var id = geteventid(element); // 获取绑定事件的 id
var c = getwrappersforeventname(id, eventname); // 获取对应的事件的所有回调
if (c.pluck("handler").include(handler)) return false; // 避免重复绑定 // 新建回调
var wrapper = function(event) {
if (!event || !event.extend ||
(event.eventname && event.eventname != eventname))
return false; event.extend(event);
handler.call(element, event);
}; // 加入到回调数组
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
} function observe(element, eventname, handler) {
element = $(element); // 对应事件的元素
var name = getdomeventname(eventname); // 事件执行方式 var wrapper = createwrapper(element, eventname, handler); // 封装回调 if (!wrapper) return element; // 绑定事件
if (element.addeventlistener) {
element.addeventlistener(name, wrapper, false);
} else {
element.attachevent("on" + name, wrapper);
} return element;
} // 调用方式
document.observe("dom:loaded", function() {
console.log("init: 1");
does_not_exist++;
}); document.observe("dom:loaded", function() {
console.log("init: 2");
});

看把 prototype 的作者给乐的 :-/

-- split --

在本人看来,原文的作者表述的技术点,除了如何创建健壮的回调系统外,其实还有两条。

其一,就是如何保证在出现异常的时,继续运行期望的代码;其二,就是如何创建互不干扰的“运行环境”。

原文提到的 createevent 和 settimeout 都是好办法,只是处理原作者所言在回调系统中,的确使用 createevent 会比较合适。settimeout 相对应的详细信息,可移步到 realazy 兄的相关文章。

而即使出错也能继续运行期望的代码,其实可以考虑使用 finally 语句,下面是个例子:

var callbacks = [
function() { console.log(0); },
function() { console.log(1); throw new error; },
function() { console.log(2); },
function() { console.log(3); }
]; for(var i = 0, len = callbacks.length; i < len; i++) {
try {
callbacks[i]();
} catch(e) {
console.info(e); // 获得异常信息
} finally {
continue;
}
}

这一灵感同样来自 dean edwards 文章后的回复,在这里也贴下吧:

function iterate(callbacks, length, i) {
if (i >= length) return; try {
callbacks[i]();
} catch(e) {
throw e;
} finally {
iterate(callbacks, length, i+1);
}
}

最后,留个小问题。谁知道上述的代码中,留言者提出的为什么异常到最后才打印出来不?

Javascript框架的自定义事件(转)的更多相关文章

  1. 详解javascript实现自定义事件

    这篇文章主要为大家介绍了javascript实现自定义事件的方法,自定义事件,顾名思义,就是自己定义事件类型,自己定义事件处理函数,javascript如何实现自定义事件,需要了解的朋友可以参考下 我 ...

  2. javascript和jquey的自定义事件小结

    “通过事件机制,可以将类设计为独立的模块,通过事件对外通信,提高了程序的开发效率.” 可以把多个关联但逻辑复杂的操作利用自定义事件的机制灵活地控制好 对象之间通过直接方法调用来交互 1)对象A直接调用 ...

  3. Javascript事件模型系列(四)我所理解的javascript自定义事件

    被我拖延了将近一个月的javascript事件模型系列终于迎来了第四篇,也是我计划中的最后一篇,说来太惭愧了,本来计划一到两个星期写完的,谁知中间遇到了很多事情,公司的个人的,搞的自己心烦意乱浮躁了一 ...

  4. 理解的javascript自定义事件

    理解的javascript自定义事件 被我拖延了将近一个月的javascript事件模型系列终于迎来了第四篇,也是我计划中的最后一篇,说来太惭愧了,本来计划一到两个星期写完的,谁知中间遇到了很多事情, ...

  5. JavaScript自定义事件 - createEvent()、initEvent()和dispachEvent()

    在学习目标事件的方法的时候,接触到了dispatchEvent()方法.度娘查一查,这是一个事件触发器,事件触发器其实就是触发事件的东西. 通常情况下,我们触发事件都是在交互中触发的事件,例如点击按钮 ...

  6. Javascript自定义事件功能与用法实例分析

    原文地址:https://www.jb51.net/article/127776.htm 本文实例讲述了javascript自定义事件功能与用法.分享给大家供大家参考,具体如下: 概述 自定义事件很难 ...

  7. Javascript之自定义事件

    Javascript自定义事件,其本质就是观察者模式(又称订阅/发布模式),它的好处就是将绑定事件和触发事件相互隔离开,并且可以动态的添加.删除事件. 下面通过实例,一步一步构建一个具体的Javasc ...

  8. javascript事件之:谈谈自定义事件(转)

    http://www.cnblogs.com/pfzeng/p/4162951.html 对于JavaScript自定义事件,印象最深刻的是用jQuery在做图片懒加载的时候.给需要懒加载的图片定义一 ...

  9. javascript事件之:谈谈自定义事件

    对于JavaScript自定义事件,印象最深刻的是用jQuery在做图片懒加载的时候.给需要懒加载的图片定义一个appear事件.当页面图片开始出现时候,触发这个自定义的appear事件(注意,这里只 ...

随机推荐

  1. vtiger7菜单管理

    添加了新模块,但是菜单上却没显示. 和菜单相关的表有4张 我们要把新建的message放到support模块下面 1.把默认的父级目录tools改成support 2. app2tab 0表示不显示, ...

  2. rabbitmq安装及基本操作(含集群配置)

    一.rabbitmq的安装 因为rabbitmq是基于 erlang语言开发,所有要先安装erlang 1.安装erlang 这里我下载的是19.2的版本,地址为https://www.erlang. ...

  3. CentOS7.5安装nodejs

    安装方法1——直接部署 1.首先安装wget yum install -y wget 如果已经安装了可以跳过该步 2.下载nodejs最新的tar包 可以在下载页面https://nodejs.org ...

  4. Python打包方法——Pyinstaller (转)

      Python版本:Python3.5.2 一.安装Pyinstaller 1.安装pywin32 下载安装文件:查找到跟自己适用的python版本及window系统版本匹配的pywin32,下载后 ...

  5. 004.Docker镜像管理

    一 镜像基本操作 镜像是一个包含程序运行必要依赖环境和代码的只读文件,其本质是磁盘上一系列文件的集合.它采用分层的文件系统,将每一次改变以读写层的形式增加到原来的只读文件上.镜像是容器运行的基石. 1 ...

  6. 011.Docker仓库管理

    一 Docker仓库介绍 docker 仓库,即 registry,实现了镜像的管理.分发,同时还包括用户的认证.docker registry 仓库是一个无状态的.高可靠的服务器应用程序,用来存储d ...

  7. SQLite中SELECT基本形式

    SQLite中SELECT基本形式 每个数据库通常都包含多个表,而每个表又包含多条数据.要获取数据库中的数据,就需要SQL语言提供的查询语句SELECT.本章将讲解和SELECT语句相关的内容,其中包 ...

  8. Luogu2570 [ZJOI2010]贪吃的老鼠 ---- 网络流

    Luogu2570  [ZJOI2010]贪吃的老鼠 题面描述 https://www.luogu.org/problemnew/show/P2570 然后题意大概就是m只老鼠,然后吃n个奶酪,已知 ...

  9. BZOJ.1443.[JSOI2009]游戏Game(二分图博弈 匈牙利)

    题目链接 \(Description\) 一个\(N*M\)的有障碍的棋盘,先手放置棋子后,从后手开始轮流移动棋子,不能走重复的位置,不能移动的输.求在哪些位置放棋子是先手必胜的. \(Solutio ...

  10. Codeforces.542E.Playing on Graph(二分图)

    题目链接 \(Description\) 给出一个n个点m条边的无向图. 你每次需要选择两个没有边相连的点,将它们合并为一个新点,直到这张图变成了一条链. 最大化这条链的长度,或输出无解. n< ...