Callbacks vs Events
前言:本文翻译自Dean Edwards的一篇文章,原文地址:http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/。
文章主要指出了用“回调模式实现自定义事件”的一些弊端,同时提出了一种解决方案,即将回调的函数包装成原生事件,利用事件系统触发
来完成回调的触发。
大多数主流的js库都声称他们支持一种或多种形式的自定义事件。比如,jQuery,YUI以及Dojo他们都支持自定义事件“document ready”。然而
这些自定义事件的实现往往使用的是一种回调模式。
回调系统(模式)往往需要一个数组来存储回调函数。如果当前的事件被处罚,则回调系统会轮询这个数组,并依次调用这些回调函数。听起来这样
实现是可以的,究竟会出现什么问题呢?在我回答之前,先看看例子。
下面是一个简单的例子,使用了DOMContentLoaded事件来完成两个相互独立的初始化:
document.addEventListener("DOMContentLoaded", function() {
console.log("Init: 1");
DOES_NOT_EXIST++; // this will throw an error
}, false);
document.addEventListener("DOMContentLoaded", function() {
console.log("Init: 2");
}, false);
你认为当事件出发时会出现什么效果呢?
是这样的:
Init: 1 Error: DOES_NOT_EXIST is not defined Init: 2
关键在于,这两个函数都执行了,第一个函数在执行时抛出错误,但并不影响第二个函数的执行。
问题所在
现在我们尝试下用“回调模式”实现自定义事件的系统。在这里,使用jQuery库。
$(document).ready(function() {
console.log("Init: 1");
DOES_NOT_EXIST++; // this will throw an error
});
$(document).ready(function() {
console.log("Init: 2");
});
我们会在console中看到如下结果:
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++; // this will throw an error
});
YAHOO.util.Event.onDOMReady(function() {
console.log("Init: 2");
});
结果:
Init: 1 Init: 2
确实如此,你看不到第一个回调的错误。
但是,我们需要寻找真正的解决(兼容)方案。
解决方案
可以将回调模式和真实事件触发结合在一起混合使用。我们可以出发一个伪事件,并在该事件内,执行回调函数。每个回调函数都拥有其自己的执行上下文。如果在伪事件中出现错误(译者注:什么意思?当伪事件的回调函数出现错误?)也不会影响我们的回调系统。
这听起来可能有些复杂,我会用例子来解释它:
var currentHandler;
if (document.addEventListener) {
document.addEventListener("fakeEvents", function() {
// execute the callback
currentHandler();
}, false);
var dispatchFakeEvent = function() {
var fakeEvent = document.createEvent("UIEvents");
fakeEvent.initEvent("fakeEvents", false, false);
document.dispatchEvent(fakeEvent);
};
} else { // MSIE
// I'll show this code later
}
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++; // this will throw an error
});
addOnLoad(function() {
console.log("Init: 2");
});
我们会发现这样的结果:
Init: 1 Error: DOES_NOT_EXIST is not defined Init: 2
很完美!这就是我们想要的结果。所有的回调函数都被执行,并且我们也得到了第一个回调函数执行出错的消息。
但是我肯定你会问IE怎么实现呢(我有很好的听觉,哈哈)?MSIE不支持标准的事件分派系统。它有自己的方法:fireEvent,但是也只有
对真实的事件(e.g. click)才有效果。
我会用代码来帮我解释:
var currentHandler;
if (document.addEventListener) {
// We've seen this code already
} else if (document.attachEvent) { // MSIE
document.documentElement.fakeEvents = 0; // an expando property
document.documentElement.attachEvent("onpropertychange", function(event) {
if (event.propertyName == "fakeEvents") {
// execute the callback
currentHandler();
}
});
dispatchFakeEvent = function(handler) {
// fire the propertychange event
document.documentElement.fakeEvents++;
};
}
我们可以使用元素或对象的propertychange事件来帮助我们完成触发。
总结
我已经展示了如何用原生的事件系统来触发自定义事件。js库的作者们应该可以发现这种模型可以被扩展到跨浏览器的自定义实现上。
更新
有些人建议使用setTimeout。这是我的答复:
对于这个特殊的例子,定时器是可以正常工作的。这只是一个论证这种技术的简单例子而已。这种混合方法的真正好处在于其他的自定义事件。大多数的js库用回调模式实现自定义事件。就像我之前论证的,回调模式很脆弱。用定时器来进行事件分派在某种程度上是可以,但是它并不是真正的事件系统。在 实际的事件系统中,事件被依次分派。还有其他的问题,比如删除事件或者阻止事件冒泡,这无法用定时器实现。
这篇文章的重点是我提出了一种“将回调系统包裹在真正事件分派系统的自定义事件”实现。它会在IE下也真正触发自定义事件。如果你基于事件代理来创建
事件系统,那么这种技术可能会很有用。
个人见解:
如果仅仅是“实现多个回调函数互相独立执行”,那么可以使用一种方法,也正是原文中评论之一的做法:
try {
callback();
}
catch(e){
setTimeout(function(){
throw e;
}, 0);
}
这样可以实现回调之间独立执行,并且异步抛出执行错误。
但正如DE所说,他的目的不仅仅是解决上述问题,而是深入到更底层,颠覆自定义事件的固有实现模式--回调模式,采用基于伪事件的触发完成自定义事件的方法。而现在
很多库也借鉴了这种方法,确实也证实了DE的伟大之处。
以后还是应该多跟着巨人后面走走,拾人牙慧也不是不可以。
Callbacks vs Events的更多相关文章
- Python Twisted、Reactor
catalogue . Twisted理论基础 . 异步编程模式与Reactor . Twisted网络编程 . reactor进程管理编程 . Twisted并发连接 1. Twisted理论基础 ...
- 原生JS、CSS实现的转盘效果(目前仅支持webkit)
这是一个原生JS.CSS实现的转盘效果(题目在这:http://www.cnblogs.com/arfeizhang/p/turntable.html),花了半个小时左右,准备睡觉,所以先贴一段代码, ...
- DPDK中断机制简析
DPDK通过在线程中使用epoll模型,监听UIO设备的事件,来模拟操作系统的中断处理. 一.中断初始化 在rte_eal_intr_init()函数中初始化中断.具体如下: 1.首先初始化intr_ ...
- 探讨 : Host在IIS上的WCF Service的执行方式
一个WCF请求由两个线程来完成 运行在IIS上的WCF service, 你可能会注意到一个比较有趣的现象. 当WCF service接收到一个请求时, 这个请求实际上会有两个线程在执行这个请求. 一 ...
- Storm源码分析--Nimbus-data
nimbus-datastorm-core/backtype/storm/nimbus.clj (defn nimbus-data [conf inimbus] (let [forced-schedu ...
- Netty的核心组件
Netty的主要组成模块: Channels Callbacks Futures Events 和 handlers 这些模块代表了不同类型的概念:资源,逻辑和通知.你的应用将会利用这些模块来获取网络 ...
- [转] 理解CheckPoint及其在Tensorflow & Keras & Pytorch中的使用
作者用游戏的暂停与继续聊明白了checkpoint的作用,在三种主流框架中演示实际使用场景,手动点赞. 转自:https://blog.floydhub.com/checkpointing-tutor ...
- Netty实战 - 1. 基本概念
1. Netty简介 Netty是由JBOSS提供的一个java开源框架.它提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序.Netty是一个基于NI ...
- vue弹窗组件
文件结构 component.vue <template> <div class="_vuedals" v-show="show"> & ...
随机推荐
- input在标签内设置禁止输入空格
1.通过正则匹配解决问题 此处涉及\s:匹配任意空白符 \S:匹配任意非空白字符 <input type="text" onkeyup="this.value=t ...
- Odoo 二次开发教程(五)-新API的介绍与应用
[关于odoo新API的介绍,Internet上资料很少,或者不够完整详实,这会对初学者造成很大的困惑,本篇的目的就是希望能帮助新手了解新API的大概] odoo 新api的实现是借助于python装 ...
- c++ 宏定义声明类,并在类中实现回调
#include <iostream> #include <windows.h> #include <string> using namespace std; ty ...
- JSP内置对象及常用方法
jsp九大内置对象及四个作用域: 何为作用域 先让我们看看效果: 大概流程是这样的,我们访问index.jsp的时候,分别对pageContext, request, session,applicat ...
- 使用Dotfuscator 进行.Net代码混淆 代码加密的方法
混淆代码能在一定程度上放置代码被盗用,保护我们的知识产权 1.打开vs2012,选择工具-〉Dotfuscator Software Services 2.选择你需要混淆的DLL 文件,可以多选择 3 ...
- git配置ssh(github)
[参考官方文档] SSH keys are a way to identify trusted computers, without involving passwords. The steps be ...
- vue组件编译原理
<body> <my-com1 type="type" v-pre="pre">com1</my-com1> <my- ...
- Microsoft Avro介绍
Microsoft发布了他们自己对Apache Avro通信协议的实现.Avro被描述为"紧凑的二进制数据序列化格式,类似于Thrift或者Protocol Buffers",同时 ...
- static与并发
在java中static用来修饰Class类中属性和方法. 被static修饰的成员属性和成员方法独立于该类的任何对象,它们在内存空间上会被放在描述Class的位置中,也就是说它们为此类(Class) ...
- ios 避免循环引用
类似网络请求的情况下会导致循环引用,但是 如果网络请求的对象是局部变量,就必须用self,不能用weakSelf,否则,一旦当前方法所在对象销毁,那weakSelf就为空了(如果目的是这样,那就另当别 ...