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"> & ...
随机推荐
- python方法中的self
前几天在写一个c作业时,突发奇想,在结构体中加入函数指针, 像这样: struct People { int _age; int (*age)(); }people; 这样调用时就可以 people. ...
- DAO实现查询
package DB3; import java.sql.*; public class DB { private static String driver="com.microsoft.s ...
- Web系统的常用测试方法
在51上看到一篇不错的文章,拿过来分享一下,学习学习! Web系统的常用测试方法如下: 1. 页面链接检查:每一个链接是否都有对应的页面,并且页面之间切换正确. 2. 相关性检查:删除/增加一项会不会 ...
- 使用VS+VisualGDB编译调试Linux程序
Linux程序开发变得越来越多,越来越多的程序.产品需要跨平台,甚至有些开源项目只支持Linux平台,所以掌握Linux开发变得越来越重要. 但是对于习惯了Windows下的开发,使用了VS这个宇宙第 ...
- 谈谈对Spring IOC的理解
学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...
- 基于GIS的旅游辐射区人口统计
在旅游规划中,考虑旅游景点周边的人口负载量是很重要的一个方面,这将直接影响资源的投入和配置,开发潜力和规模等.基于GIS可以将人口信息进行空间化的展示,还可以通过空间分析的方法计算出旅游景点辐射区的人 ...
- ThreaLocal内存泄露的问题
在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题.表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出.开始是怀疑ThreadLocal的问题, ...
- 【VC++技术杂谈004】使用微软TTS语音引擎实现文本朗读
本文主要介绍如何使用微软TTS语音引擎实现文本朗读,以及生成wav格式的声音文件. 1.语音引擎及语音库的安装 TTS(Text-To-Speech)是指文本语音的简称,即通过TTS引擎把文本转化为语 ...
- entityframework使用CodeFirst创建MySql数据库出错的解决方法恢复
先告诉大家一个秘密,EF在使用 update-database 时候,使用的连接字符串来自于解决方案中的“启动项目”,而不是你在包管理器中选择的“默认项目” 0x01. 先说错误,方便大家检索到 开发 ...
- Android RatingBar 自定义样式
Android RatingBar 自定义样式 1.先定义Style: <style name="RadingStyle" parent="@android:sty ...