代码改变世界

回调函数与DOM事件

2014-05-09 17:53  fangzhao.lee  阅读(968)  评论(0编辑  收藏  举报

原文:http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/

先看如下代码:

1 document.addEventListener("DOMContentLoaded", function() {
2   console.log("Init: 1");
3   DOES_NOT_EXIST++; // error
4 }, false);
5 
6 document.addEventListener("DOMContentLoaded", function() {
7   console.log("Init: 2");
8 }, false);

你预期当页面加载后,console下会出现什么结果?

结果是这样的:

Init: 1

Uncaught ReferenceError: DOES_NOT_EXIST is not defined

Init: 2

重点在于: 两个事件监听函数都执行了.虽然在第一个事件监听函数中出现了错误,但并没有阻止第二个函数的执行.

 

问题来了.

 

接下来我们基于回调函数系统的代码.使用jQuery:

1 $(document).ready(function() {
2   console.log("Init: 1");
3   DOES_NOT_EXIST++; // error
4 });
5 
6 $(document).ready(function() {
7   console.log("Init: 2");
8 });

 

此时你从console下看到了什么?没错,是这样:

Init: 1
Uncaught ReferenceError: DOES_NOT_EXIST is not defined

 

好吧,这意味着回调函数系统是极其脆弱的.一旦任何一个回调函数中抛出了异常,则余下的回调函数序列将不再执行.

在实际开发环境中,这意味着一个写得烂的插件可以令其他插件无法初始化.

Dojo与jQuery有相同的问题,而YUI包装了try/catch机制,它会让回调函数中的错误悄悄地被捕获:

1 YAHOO.util.Event.onDOMReady(function() {
2   console.log("Init: 1");
3   DOES_NOT_EXIST++; // this will throw an error
4 });
5 
6 YAHOO.util.Event.onDOMReady(function() {
7   console.log("Init: 2");
8 });

所以你将在console看到如下结果:

Init: 1

Init: 2

几近完美的初始化! 貌似没什么好担心的了,除了那些你看不到的错误.

那该如何解决呢?

下面的解决方案是这样的: 使用回调函数混合真正的事件调度.

我们可以触发一个自定义事件,并在该事件的监听函数中,迂回地执行回调函数.

因为每个事件处理程序都有它自己的上下文,所以,即便在事件处理函数内发生了错误,也不会影响到我们的回调函数系统了.

回调函数序列中的每一个函数都将被执行.

这里是代码:

 1 var currentHandler;
 2 
 3 if (document.addEventListener) {
 4   document.addEventListener("fakeEvents", function() {
 5     // execute the callback
 6     currentHandler();
 7   }, false);
 8 
 9   var dispatchFakeEvent = function() {
10     var fakeEvent = document.createEvent("UIEvents");
11     fakeEvent.initEvent("fakeEvents", false, false);
12     document.dispatchEvent(fakeEvent);
13   };
14 } else { // MSIE
15 
16   document.documentElement.fakeEvents = 0; // an expando property
17 
18   document.documentElement.attachEvent("onpropertychange", function(event) {
19     if (event.propertyName == "fakeEvents") {
20       // execute the callback
21       currentHandler();
22     }
23   });
24 
25   dispatchFakeEvent = function(handler) {
26     // fire the propertychange event
27     document.documentElement.fakeEvents++;
28   };
29 }
30 
31 var onLoadHandlers = [];
32 function addOnLoad(handler) {
33   onLoadHandlers.push(handler);
34 };
35 
36 window.onload = function() {
37   for (var i = 0; i < onLoadHandlers.length; i++) {
38     currentHandler = onLoadHandlers[i];
39     dispatchFakeEvent();
40   }
41 };

 这次,执行结果当然又是我们预期的了:

Init: 1

Uncaught ReferenceError: DOES_NOT_EXIST is not defined

Init: 2