MyEvent. js javascript cross-browser event framework

Source: Internet
Author: User

How complicated is event? It can be seen how the best addEvent came into being six years ago, and jQuery paid more than one thousand six hundred lines of hard-earned code (v 1.5.1) after six years, the browser with various cores is ready.

I tried to write an event framework based on the Code and understanding of my predecessors. My framework completed the core of an event mechanism, it provides a unified interface to bind multiple events and avoid memory leaks. More importantly, the performance is good.

My methods:

All callback functions are cached in A _ create object based on elements, event types, and unique callback function IDS (the following source code comments about _ cache can be seen in the internal structure ).
An _ create proxy function is used for event binding. All types of events of an element are distributed through this function. At the same time, the apply method is used to direct the IE pointer to the element.
Solve the issue of IE callback function execution sequence through array queue.
The fix function will handle the event parameters passed in by the callback function and other compatibility issues. JQuery. event. fix is referenced here.
Disconnects the cyclic reference of events and elements to avoid Memory leakage.
I. Core Implementation:
Copy codeThe Code is as follows:
// MySQL 0.2
// 2011.04.06-TangBin-planeart.cn-MIT Licensed
/**
* Event framework
* @ Namespace
* @ See http://www.planeart.cn /? P = 1285
*/
Var myEvent = (function (){
Var _ fid = 1,
_ Guid = 1,
_ Time = (new Date). getTime (),
_ NEid = '{$ eid}' + _ time,
_ NFid = '{$ fid}' + _ time,
_ DOM = document. addEventListener,
_ Noop = function (){},
_ Create = function (guid ){
Return function (event ){
Event = api. fix (event | window. event );
Var I = 0,
Type = (event | (event = document. event). type,
Elem = _ cache [guid]. elem,
Data = arguments,
Events = _ cache [guid]. events [type];
For (; I <events. length; I ++ ){
If (events [I]. apply (elem, data) = false) event. preventDefault ();
};
};
},
_ Cache = {/*
1 :{
Elem: (HTMLElement ),
Events :{
Click: [(Function), (...)],
(..)
},
Listener: (Function)
},
(..)
*/};
Var api = {
/**
* Event binding
* @ Param {HTMLElement} Element
* @ Param {String} event name
* @ Param {Function} refers to the Function to be bound.
*/
Bind: function (elem, type, callback ){
Var guid = elem [_ nEid] | (elem [_ nEid] = _ guid ++ );
If (! _ Cache [guid]) _ cache [guid] = {
Elem: elem,
Listener: _ create (guid ),
Events :{}
};
If (type &&! _ Cache [guid]. events [type]) {
_ Cache [guid]. events [type] = [];
Api. add (elem, type, _ cache [guid]. listener );
};
If (callback ){
If (! Callback [_ nFid]) callback [_ nFid] = _ fid ++;
_ Cache [guid]. events [type]. push (callback );
} Else
Return _ cache [guid];
},
/**
* Event uninstall
* @ Param {HTMLElement} Element
* @ Param {String} event name
* @ Param {Function} refers to the Function to be detached.
*/
Unbind: function (elem, type, callback ){
Var events, I, list,
Guid = elem [_ nEid],
Handler = _ cache [guid];
If (! Handler) return;
Events = handler. events;
If (callback ){
List = events [type];
If (! List) return;
For (I = 0; I <list. length; I ++ ){
List [I] [_ nFid] === callback [_ nFid] & list. splice (I --, 1 );
};
If (list. length = 0) return api. unbind (elem, type );
} Else if (type ){
Delete events [type];
Api. remove (elem, type, handler. listener );
} Else {
For (I in events ){
Api. remove (elem, I, handler. listener );
};
Delete _ cache [guid];
};
},
/**
* Event triggering (Note: The default browser behavior and bubbling will not be triggered)
* @ Param {HTMLElement} Element
* @ Param {String} event name
* @ Param {Array} (optional) additional data
*/
TriggerHandler: function (elem, type, data ){
Var guid = elem [_ nEid],
Event = {
Type: type,
Target: elem,
CurrentTarget: elem,
PreventDefault: _ noop,
StopPropagation: _ noop
};
Data = data | [];
Data. unshift (event );
Guid & _ cache [guid]. listener. apply (elem, data );
Try {
Elem ['on' + type] & elem ['on' + type]. apply (elem, data );
// Elem [type] & elem [type] ();
} Catch (e ){};
},
// Native event binding Interface
Add: _ DOM? Function (elem, type, listener ){
Elem. addEventListener (type, listener, false );
}: Function (elem, type, listener ){
Elem. attachEvent ('on' + type, listener );
},
// Native event uninstall Interface
Remove: _ DOM? Function (elem, type, listener ){
Elem. removeEventListener (type, listener, false );
}: Function (elem, type, listener ){
Elem. detachEvent ('on' + type, listener );
},
// Corrected
Fix: function (event ){
If (_ DOM) return event;
Var name,
NewEvent = {},
Doc = document.doc umentElement,
Body = document. body;
NewEvent.tar get = event. srcElement | document;
NewEvent.tar get. nodeType ===3 & (newEvent.tar get = newEvent.tar get. parentNode );
NewEvent. preventDefault = function () {event. returnValue = false };
NewEvent. stopPropagation = function () {event. cancelBubble = true };
NewEvent. pageX = newEvent. clientX + (doc & doc. scrollLeft | body & body. scrollLeft | 0)-(doc & doc. clientLeft | body & body. clientLeft | 0 );
NewEvent. pageY = newEvent. clientY + (doc & doc. scrollTop | body & body. scrollTop | 0)-(doc & doc. clientTop | body & body. clientTop | 0 );
NewEvent. relatedTarget = event. fromElement = newEvent.tar get? Event. toElement: event. fromElement;
//!! Writing an event by IE will cause memory leakage, and an error will be reported when writing the event by Firefox.
// Copy the event
For (name in event) newEvent [name] = event [name];
Return newEvent;
}
};
Return api;
})();

I tested the binding event of 10 thousand elements. The test tool is sIEve. Result:

Event framework Time consumed Memory
IE8
JQuery. bind 1064 MS 79.80 MB
MyEvent. bind 623 MS 35.82 MB
IE6
JQuery. bind 2503 MS 74.23 MB
MyEvent. bind 1810 MS 28.48 MB

We can see that both execution efficiency and memory usage of myEvent have some advantages. This is probably because jQuery's event mechanism is too powerful, resulting in performance loss.
Test sample: http://www.planeart.cn/demo/myEvent/

Ii. Extend the Custom Event Mechanism
JQuery can customize events. It uses a special namespace to store custom events. Based on the above code, I mimic jQuery's Custom Event mechanism, and transplanted its famous ready event and another jQuery hashchange event plug-in.

These two custom events are very important. The ready event can be bound to the element in the DOM, which is more important than the traditional window. onload is much faster. The hashchange event can listen to changes in the anchor. It is often used to solve AJAX history problems. For example, the new version of Twitter uses this method to process AJAX, in addition to improving the user experience of AJAX applications, the anchor mechanism can be indexed by google according to certain rules.

Of course, the imgReady event implemented in my previous article can also be added through this extension and updated later.

Copy codeThe Code is as follows:
// MyEvent 0.2.2
// 2011.04.07-TangBin-planeart.cn-MIT Licensed
/**
* Event framework
* @ Namespace
* @ See http://www.planeart.cn /? P = 1285
*/
Var myEvent = (function (){
Var _ ret, _ name,
_ Fid = 1,
_ Guid = 1,
_ Time = (new Date). getTime (),
_ NEid = '{$ eid}' + _ time,
_ NFid = '{$ fid}' + _ time,
_ DOM = document. addEventListener,
_ Noop = function (){},
_ Create = function (guid ){
Return function (event ){
Event = myEvent. fix (event | window. event );
Var type = (event | (event = document. event). type,
Elem = _ cache [guid]. elem,
Data = arguments,
Events = _ cache [guid]. events [type],
I = 0,
Length = events. length;
For (; I <length; I ++ ){
If (events [I]. apply (elem, data) = false) event. preventDefault ();
};
Event = elem = null;
};
},
_ Cache = {/*
1 :{
Elem: (HTMLElement ),
Events :{
Click: [(Function), (...)],
(..)
},
Listener: (Function)
},
(..)
*/};
Var API = function (){};
API. prototype = {
/**
* Event binding
* @ Param {HTMLElement} Element
* @ Param {String} event name
* @ Param {Function} refers to the Function to be bound.
*/
Bind: function (elem, type, callback ){
Var events, listener,
Guid = elem [_ nEid] | (elem [_ nEid] = _ guid ++ ),
Special = this. special [type] | | {},
CacheData = _ cache [guid];
If (! CacheData) cacheData = _ cache [guid] = {
Elem: elem,
Listener: _ create (guid ),
Events :{}
};
Events = cacheData. events;
Listener = cacheData. listener;
If (! Events [type]) events [type] = [];
If (! Callback [_ nFid]) callback [_ nFid] = _ fid ++;
If (! Special. setup | special. setup. call (elem, listener) === false ){
Events [type]. length ===0 & this. add (elem, type, listener );
};
Events [type]. push (callback );
},
/**
* Event uninstall
* @ Param {HTMLElement} Element
* @ Param {String} event name
* @ Param {Function} refers to the Function to be detached.
*/
Unbind: function (elem, type, callback ){
Var events, special, I, list, fid,
Guid = elem [_ nEid],
CacheData = _ cache [guid];
If (! CacheData) return;
Events = cacheData. events;
If (callback ){
List = events [type];
Fid = callback [_ nFid];
If (! List) return;
For (I = 0; I <list. length; I ++ ){
List [I] [_ nFid] === fid & list. splice (I --, 1 );
};
If (! List. length) this. unbind (elem, type );
} Else if (type ){
Special = this. special [type] | | {};
If (! Special. teardown | special. teardown. call (elem) === false ){
This. remove (elem, type, cacheData. listener );
};
Delete events [type];
} Else {
For (I in events ){
This. remove (elem, I, cacheData. listener );
};
Delete _ cache [guid];
};
},
/**
* Event triggering (Note: The default browser behavior and bubbling will not be triggered)
* @ Param {HTMLElement} Element
* @ Param {String} event name
* @ Param {Array} (optional) additional data
*/
TriggerHandler: function (elem, type, data ){
Var guid = elem [_ nEid],
CacheData = _ cache [guid],
Event = {
Type: type,
Target: elem,
CurrentTarget: elem,
PreventDefault: _ noop,
StopPropagation: _ noop
};
Data = data | [];
Data. unshift (event );
CacheData & cacheData. events [type] & _ cache [guid]. listener. apply (elem, data );
Try {
Elem ['on' + type] & elem ['on' + type]. apply (elem, data );
// Elem [type] & elem [type] ();
} Catch (e ){};
},
// Custom event Interface
Special :{},
// Native event binding Interface
Add: _ DOM? Function (elem, type, listener ){
Elem. addEventListener (type, listener, false );
}: Function (elem, type, listener ){
Elem. attachEvent ('on' + type, listener );
},
// Native event uninstall Interface
Remove: _ DOM? Function (elem, type, listener ){
Elem. removeEventListener (type, listener, false );
}: Function (elem, type, listener ){
Elem. detachEvent ('on' + type, listener );
},
// Corrected
Fix: function (event ){
If (_ DOM) return event;
Var name,
NewEvent = {},
Doc = document.doc umentElement,
Body = document. body;
NewEvent.tar get = event. srcElement | document;
NewEvent.tar get. nodeType ===3 & (newEvent.tar get = newEvent.tar get. parentNode );
NewEvent. preventDefault = function () {event. returnValue = false };
NewEvent. stopPropagation = function () {event. cancelBubble = true };
NewEvent. pageX = newEvent. clientX + (doc & doc. scrollLeft | body & body. scrollLeft | 0)-(doc & doc. clientLeft | body & body. clientLeft | 0 );
NewEvent. pageY = newEvent. clientY + (doc & doc. scrollTop | body & body. scrollTop | 0)-(doc & doc. clientTop | body & body. clientTop | 0 );
NewEvent. relatedTarget = event. fromElement = newEvent.tar get? Event. toElement: event. fromElement;
//!! Writing event IE directly causes memory leakage. Firefox reports an error.
// Disguise event
For (name in event) newEvent [name] = event [name];
Return newEvent;
}
};
Return new API ();
})();
// DOM ready event
MyEvent. ready = (function (){
Var readyList = [], DOMContentLoaded,
ReadyBound = false, isReady = false;
Function ready (){
If (! IsReady ){
If (! Document. body) return setTimeout (ready, 13 );
IsReady = true;
If (readyList ){
Var fn, I = 0;
While (fn = readyList [I ++]) {
Fn. call (document ,{});
};
ReadyList = null;
};
};
};
Function bindReady (){
If (readyBound) return;
ReadyBound = true;
If (document. readyState === 'complete '){
Return ready ();
};
If (document. addEventListener ){
Document. addEventListener ('domcontentloaded', DOMContentLoaded, false );
Window. addEventListener ('load', ready, false );
} Else if (document. attachEvent ){
Document. attachEvent ('onreadystatechang', DOMContentLoaded );
Window. attachEvent ('onload', ready );
Var toplevel = false;
Try {
Toplevel = window. frameElement = null;
} Catch (e ){};
If (document.doc umentElement. doScroll & toplevel ){
DoScrollCheck ();
};
};
};
MyEvent. special. ready = {
Setup: bindReady,
Teardown: function (){}
};
If (document. addEventListener ){
DOMContentLoaded = function (){
Document. removeEventListener ('domcontentloaded', DOMContentLoaded, false );
Ready ();
};
} Else if (document. attachEvent ){
DOMContentLoaded = function (){
If (document. readyState === 'complete '){
Document. detachEvent ('onreadystatechang', DOMContentLoaded );
Ready ();
};
};
};
Function doScrollCheck (){
If (isReady) return;
Try {
Document.doc umentElement. doScroll ('left ');
} Catch (e ){
SetTimeout (doScrollCheck, 1 );
Return;
};
Ready ();
};
Return function (callback ){
BindReady ();
If (isReady ){
Callback. call (document ,{});
} Else if (readyList ){
ReadyList. push (callback );
};
Return this;
};
})();
// Hashchange Event v1.3
(Function (window, undefined ){
Var config = {
Delay: 50,
Src: null,
Domain: null
},
Str_hashchange = 'hashchange ',
Doc = document,
IsIE =! -[1,],
Fake_onhashchange, special = myEvent. special,
Doc_mode = doc.doc umentMode,
Supports_onhashchange = 'on' + str_hashchange in window & (doc_mode = undefined | doc_mode> 7 );
Function get_fragment (url ){
Url = url | location. href;
Return '#' + url. replace (/^ [^ #] * #? (. *) $/, '$1 ');
};
Special [str_hashchange] = {
Setup: function (){
If (supports_onhashchange) return false;
MyEvent. ready (fake_onhashchange.start );
},
Teardown: function (){
If (supports_onhashchange) return false;
MyEvent. ready (fake_onhashchange.stop );
}
};
/** @ Inner */
Fake_onhashchange = (function (){
Var self = {},
Timeout_id, last_hash = get_fragment (),
/** @ Inner */
Fn_retval = function (val ){
Return val;
},
History_set = fn_retval,
History_get = fn_retval;
Self. start = function (){
Timeout_id | poll ();
};
Self. stop = function (){
Timeout_id & clearTimeout (timeout_id );
Timeout_id = undefined;
};
Function poll (){
Var hash = get_fragment (),
History_hash = history_get (last_hash );
If (hash! = Last_hash ){
History_set (last_hash = hash, history_hash );
MyEvent. triggerHandler (window, str_hashchange );
} Else if (history_hash! = Last_hash ){
Location. href = location. href. replace (/#. */, '') + history_hash;
};
Timeout_id = setTimeout (poll, config. delay );
};
IsIE &&! Supports_onhashchange & (function (){
Var iframe, iframe_src, iframe_window;
Self. start = function (){
If (! Iframe ){
Iframe_src = config. src;
Iframe_src = iframe_src & iframe_src + get_fragment ();
Iframe = doc. createElement ('<IFRAME title = empty style = "DISPLAY: none" tabIndex =-1 src = "' + (iframe_src | 'javascript: 0 ') + '"> </IFRAME> ');
MyEvent. bind (iframe, 'load', function (){
MyEvent. unbind (iframe, 'load ');
Iframe_src | history_set (get_fragment ());
Poll ();
});
Doc. getElementsByTagName ('html ') [0]. appendChild (iframe );
Iframe_window = iframe. contentWindow;
Doc. onpropertychange = function (){
Try {
If (event. propertyName = 'title '){
Iframe_policy.document.title = doc. title;
};
} Catch (e ){};
};
};
};
Self. stop = fn_retval;
/** @ Inner */
History_get = function (){
Return get_fragment (iframe_window.location.href );
};
/** @ Inner */
History_set = function (hash, history_hash ){
Var iframe_doc = iframe_window.document,
Domain = config. domain;
If (hash! = History_hash ){
Iframe_doc.title = doc. title;
Iframe_doc.open ();
Domain & iframe_doc.write ('<SCRIPT> document. domain = "' + domain + '" </SCRIPT> ');
Iframe_doc.close ();
Iframe_window.location.hash = hash;
};
};
})();
Return self;
})();
}) (This );

The ready event is a pseudo event. The call method is as follows:

Copy codeThe Code is as follows: myEvent. ready (function (){
// [Code ..]
});

Hashchange events can be bound in the standard mode:
Copy codeThe Code is as follows: myEvent. bind (window, 'hashchange', function (){
// [Code ..]
});

Here are some articles worth reading:
Javascript cross-browser Event System (situ zhengmei. His blog has a series of explanations)
More elegant compatibility (belleve invis)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.