原文: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