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"> & ...
随机推荐
- [LintCode] Intersection of Two Linked Lists 求两个链表的交点
Write a program to find the node at which the intersection of two singly linked lists begins. Notice ...
- 2016huasacm暑假集训训练五 H - Coins
题目链接:http://acm.hust.edu.cn/vjudge/contest/126708#problem/H 题意:A有一大堆的硬币,他觉得太重了,想花掉硬币去坐的士:的士司机可以不找零,但 ...
- chrome浏览器js 导出excel
<table id="table"> <tr> <th>ID</th> <th>姓名</th> <th ...
- jqueryui 进度条使用
<title></title> <script src="../Js/NewJs_CFJ/jquery.js" type="text/jav ...
- Public key for mysql....rpm is not installed
解决方案: 此时导入rpm的签名信息即可,用root登录,执行下面命令(我的linux版本是centos 5.1) 原文地址: http://my.oschina.net/u/555639/blog/ ...
- 结合数据库登录注册模块,登录成功之后跳到WebView
最近刚刚做了一个模块,在本地建立一个数据库,存储注册的账号,登录的时候取出,正确则登录,登录之后跳到一个webView网页. 直接上代码吧. LoginActivity.java package co ...
- Setting Up KeePass For Centos 6
This mini-howto describes how to set up KeePass on Centos 6. It requires building mono from source a ...
- ABP理论学习之MVC控制器(新增)
返回总目录 本篇目录 介绍 AbpController基类 本地化 异常处理 响应结果的包装 审计日志 授权 工作单元 其他 介绍 ABP通过Abp.Web.Mvc nuget包集成了ASP.NET ...
- Linux环境下部署完JDK后运行一个简单的Java程序
前言 前一篇文章详细讲解了如何在Windows环境下安装虚拟机+Linux系统,并且成功部署了JDK. 不过部署完JDK之后,我们判断部署是否成功的依据是看"java -version&qu ...
- 用java PreparedStatement就不用担心sql注入了吗?
先感慨下,好久没写博客了,一是工作太忙,二是身体不太给力,好在终于查清病因了,趁着今天闲下来,迫不及待与读者交流,最后忠告一句:身体是活着的本钱! 言归正传,对java有了解的同学基本上都体验过JDB ...