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"> & ...
随机推荐
- DES算法
好久没写过博客啦,最近在gao搞Qt,做出漂亮的UI确实挺难的,做美工也不简单啊,好啦,言归正传,下面是实现DES的python源码,是借鉴了开源中国一个大师的源码写出来的,直接贴啦. 加密部分: # ...
- Mariadb数据库设置及操作 一主多从 备份还原(实测笔记)
环境: 系统硬件:vmware vsphere (CPU:2*4核,内存2G,双网卡) 系统版本:CentOS-7-x86_64-Minimal-1611.iso 数据库版本信息 : 10.1.20- ...
- iOS开发点滴:iPhone屏幕适配
最近开始做iOS开发,遇到一些小问题和解决方法,记录下. 今天是iPhone屏幕适配 iPhone5出来之后屏幕就有iPhone就有了2种尺寸:3.5寸和4寸,xcode 5 的IB设计器里面界面 ...
- [.net 面向对象程序设计深入](0) 开篇
[.net 面向对象程序设计深入](0)开篇 [.net 面向对象编程基础]和 [.net 面向对象程序设计进阶]在15年底写完了,群里也加进来不少热爱学习的小伙伴.让我深切感受到在这个 ...
- 递归算法经典实例小结(C#实现)
一 .递归算法简介 在数学与计算机科学中,递归是指在函数的定义中使用函数自身的方法. 递归算法是一种直接或者间接地调用自身算法的过程.在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往 ...
- Linq操作非泛型集合
我们都知道,Linq能查询泛型集合,确切的说是:LINQ能实现查询泛型对象或者实现了IEnumerable.但是,很遗憾的是诸如ArrayList这样的非泛型集合并没有实现IEnumerable.那咋 ...
- Web3DGame之路(三)分析babylonjs
BabylonJS的例子十分详实 http://doc.babylonjs.com/tutorials Babylonjs的学习比较顺畅,开始做一些深入分析 一.语言选择 首先是js还是ts的问题 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (14) -----第三章 查询之查询中设置默认值和存储过程返回多结果集
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-6在查询中设置默认值 问题 你有这样一个用例,当查询返回null值时,给相应属性 ...
- C# 服务程序 - 调试服务
前言:本篇文章环境是VS2015,win10.如果有任何的差别,请注意 1. 创建服务程序 1)用VC创建服务程序,叫做 MyTestWindowsService 创建完成之后,可以看到 2)添加安装 ...
- 2013 duilib入门简明教程 -- 复杂控件介绍 (13)
首先将本节要介绍的控件全部拖到界面上,并调整好位置,如图: 然后将Name属性改成其他名字, 不能是[控件名+UI+数字]这种,因为这是DuiDesigner ...