Javascript框架的自定义事件

2016-02-20 00:51 5 1 收藏

下面是个Javascript框架的自定义事件教程,撑握了其技术要点,学起来就简单多了。赶紧跟着图老师小编一起来看看吧!

【 tulaoshi.com - Web开发 】

Dean Edwards 最近有篇文章很精彩,忍不住在这里翻译下。

-- Split --

很多 Javascript 框架都提供了自定义事件(custom events),例如 jQuery、YUI 以及 Dojo 都支持document ready事件。而部分自定义事件是源自回调(callback)。

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/webkaifa/)

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

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

document.addEventListener("DOMContentLoaded", function() {  console.log("Init: 1");  DOES_NOT_EXIST++; // 这里会抛出异常}, false);document.addEventListener("DOMContentLoaded", function() {  console.log("Init: 2");}, false);

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

Init: 1Error: DOES_NOT_EXIST is not definedInit: 2

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

麻烦

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

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

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

Init: 1Error: 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: 1Init: 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");});

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

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/webkaifa/)
Init: 1Error: DOES_NOT_EXIST is not definedInit: 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); // 获取绑定事件的 IDvar 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 都是好办法,只是处理

来源:http://www.tulaoshi.com/n/20160220/1632665.html

延伸阅读
《死侍》自定义键位心得完美自定义键位 方式1: Q作为传送反击键,鼠标左键轻击,右键重击,中键开枪,E键瞄准放大,R重装和互动,ZXC分别对应键盘的轻枪重,其他不变。 鼠标左键轻击,右键重击,中键开枪,这个几乎没什么可说的。 ZXC的定义是不想占用其他键位,再就是方便看刚开始了解连招,也比较形象和对称,有时还可以用来释放动...
标签: Web开发
一、基本概念: 1.标签(Tag): 标签是一种XML元素,通过标签可以使JSP网页变得简洁并且易于维护,还可以方便地实现同一个JSP文件支持多种语言版本。由于标签是XML元素,所以它的名称和属性都是大小写敏感的 2.标签库(Tag library): 由一系列功能相似、逻辑上互相联系的标签构成的集合称为标签库。 3.标签库描述文件(Tag Library Descriptor...
打造自定义的 AfxMessageBox 作者:1.5kg 下载源代码 一、需求 与标准的 MessageBox 相比,MFC提供了 AfxMessageBox 的方法是我们对消息框的变得更加容易。然而简单的 AfxMessageBox 有时已经不能够满足我们的需求了:有时候我仅仅想提示用户一下,并不需要用户确认,也就是说过一段时间消息框...
PowerPoint借助自定义形状实现填充自定义图片   具体如何操作呢?以powerpoint2007为例,先在PPT页面中画一Tulaoshi.Com个形状,就像下面这个圆: 然后在形状填充里面选择图片或纹理填充: 插入自文件,选择想要的图片就OK了,就会出现下面的效果: 同样的效果还可以应用于艺术字(文本框不行)。如下: ...
标签: Web开发
一、基本概念: 1.标签(Tag): 标签是一种XML元素,通过标签可以使JSP网页变得简洁并且易于维护,还可以方便地实现同一个JSP文件支持多种语言版本。由于标签是XML元素,所以它的名称和属性都是大小写敏感的 2.标签库(Tag library): 由一系列功能相似、逻辑上互相联系的标签构成的集合称为标签库。 3.标签库描述文件(Tag Library Desc...

经验教程

353

收藏

22
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部