提高網站性能的方法之一就是將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