提高网站性能的方法之一就是将JavaScript文件放到文档的底部(我在Improve Your Web Site Performance – Tips & Tricks To Get A Good YSlow Rating中讨论过)。但是,这有一个缺点。
问题
在JavaScript文件加载完毕运行之前,你打算为文档中一些元素分配一个事件处理器。如果用户此时单击这些元素,会发生什么情况?那么浏览器将执行元素默认的行为。如,一个将用户带到另外一个页面的链接,而你并不打算这么做,而是想通过AJAX或其他方式动态加载一些信息。
理论解决方案
最近,我和我的同事David Billskog讨论并研究了这个问题,其解决方案是在文档的头部包含一个JavaScript块(inline或included),并确信能捕获页面中所有的click事件。然后过滤它们确定一个元素正是你正打算应用事件处理器的那个元素。
最后,当页面加载完毕/事件处理器已经分配时,移除先前分配的clickCatcher,迭代在这之前被点击的所有元素,然后触发这些元素真正的click事件,引发正常的和应用的事件行为。
实际解决方案
我和David开始各自的工作,对我来说,解决方案不需要任何JavaScript库,而David的解决方案是基于jQuery版本的(可以很容易的应用到任何其它库中)。我列出我的解决方案——clickCatcher。
clickCatcher使用一些简单的script函数作为基础:
The clickCatcher
(function () { clickCatcher = function () { var clicks = [], addClicks = function (evt) { var classCheck = /catch/, body = /body/i, target = (evt.target)? evt.target : evt.srcElement; while (!classCheck.test(target.className) && !body.test(target.nodeName)) { target = target.parentNode; } if (classCheck.test(target.className)) { clicks.push(target); if (evt.preventDefault) { evt.preventDefault(); } evt.returnValue = false; return false; } }, callClicks = function () { removeEvent(document, "click", addClicks); for (var i=0, il=clicks.length; i<il; i++) { fireEvent(clicks[i], "click"); }; }, init = function () { addEvent(document, "click", addClicks); // Could be called here, but now called manually in script loaded later - adapt to your situation //addEvent(window, "load", callClicks); }; return { init : init, callClicks : callClicks }; }(); clickCatcher.init(); return clickCatcher;})();
clickCatcher封装在一个匿名的自调用函数之中,这样就不会与网页的全局变量相混淆,而是创建自己的作用域。如果你想在其它地方访问它,这部分代码可以选择性的暴露clickCatcher对象自身。代码给文档附加一个事件处理器,如果被单击的元素(或任何它的父元素)有类名catch(如链接),就将每一个click存储到一个数组中。
一旦页面加载完毕,你可以让其自动调用,或你可以通过调用clickCatcher.callClicks()方法手动触发它;
包含addEvent, removeEvent, fireEvent方法完整的代码,看起来就是这样:
// By Robert Nyman, http://robertnyman.com/clickcatcher/ - This content is released under the MIT License: http://www.opensource.org/licenses/mit-license.php(function () { // addEvent by John Resig, http://ejohn.org/blog/flexible-javascript-events/ function addEvent( obj, type, fn ) { if ( obj.attachEvent ) { obj['e'+type+fn] = fn; obj[type+fn] = function(){obj['e'+type+fn]( window.event );} obj.attachEvent( 'on'+type, obj[type+fn] ); } else obj.addEventListener( type, fn, false ); } function removeEvent( obj, type, fn ) { if ( obj.detachEvent ) { obj.detachEvent( 'on'+type, obj[type+fn] ); obj[type+fn] = null; } else obj.removeEventListener( type, fn, false ); } // fireEvent by Jehiah Czebotar, http://jehiah.cz/archive/firing-javascript-events-properly function fireEvent(element, event) { var evt; if (document.createEvent) { // dispatch for firefox + others evt = document.createEvent("HTMLEvents"); evt.initEvent(event, true, true ); // event type,bubbling,cancelable return !element.dispatchEvent(evt); } else { // dispatch for IE evt = document.createEventObject(); return element.fireEvent('on'+event,evt); } } clickCatcher = function () { var clicks = [], addClicks = function (evt) { var classCheck = /catch/, body = /body/i, target = (evt.target)? evt.target : evt.srcElement; while (!classCheck.test(target.className) && !body.test(target.nodeName)) { target = target.parentNode; } if (classCheck.test(target.className)) { clicks.push(target); if (evt.preventDefault) { evt.preventDefault(); } evt.returnValue = false; return false; } }, callClicks = function () { removeEvent(document, "click", addClicks); for (var i=0, il=clicks.length; i<il; i++) { fireEvent(clicks[i], "click"); }; }, init = function () { addEvent(document, "click", addClicks); // Could be called here, but now called manually in script loaded later - adapt to your situation //addEvent(window, "load", callClicks); }; return { init : init, callClicks : callClicks }; }(); clickCatcher.init(); return clickCatcher;})();
你可以到clickCatcher页测试这些代码。
setTimeout 技巧
但是,不管是是从clickCatcher内部来触发clicks的执行,还是手动调用它们,你要确信这是最后调用的。David研究中的一个有趣的结果是,你可以通过使用setTimeout控制调用栈,time设置为0,这样,相对于其它完全相同load事件处理器,它将被放置到栈的最后。
也将就是说,如果你在window已加载时使用addEvent调用某些东西,你可以像这样控制它(或许只在同一个script块或JavaScript文件中时相对安全)。
// Calling click handlersetTimeout(function () { addEvent(window, "load", callClicks);}, 0);// My own function - will be called before the above functionaddEvent(window, "load", myCoolFunc);
jQuery 版本
先前我提到过David的jQuery版本,这意味着在你调用之前须包含jQuery,但是你可以将其它JavaScript文件放到页面底部。他介绍了他的方法,结果在clickCatcher with jQuery 。
反馈?
从我们的测试中看到,在JavaScript加载完毕之前捕获click事件,这是一个有效可靠的方法。你仍可以在文档的底部放置js来提升网站性能。
原文地址:http://robertnyman.com/2010/05/24/catching-clicks-with-clickcatcher-before-your-javascript-files-have-loadedevents-been-applied/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+robertnyman+%28Robert%27s+talk%29
转载地址:http://www.denisdeng.com/?p=1018