Myevent.js JavaScript cross-browser event framework _javascript Tips

Source: Internet
Author: User
Tags unique id
How complex is the event? Visible predecessors of 6 years ago efforts: The best addevent is how the birth, Up-and-comer jquery also paid more than 1600 lines of sweat code (v 1.5.1) buttoned up 6 years after the various nuclear browsers.

I've tried to write an event framework by referring to the code of my predecessors and my own understanding, and my framework completes the core of an event mechanism that provides a unified interface to implement multiple event bindings and avoid memory leaks, and, more importantly, good performance.

My approach:

All callback functions are cached in a _create object based on the element, event type, and unique ID of the callback function (its internal structure is visible below the source's comment on _cache).
Event bindings are handled using a _create proxy function, and all types of events for an element are distributed through this, and the Apply method is used to let IE's pointers point to elements.
Solve the problem of the order of IE callback function through array queue.
The Fix function handles the event arguments passed in by the callback function and other compatibility issues. Here is a reference to JQuery.event.fix.
Disconnect event and element circular references to avoid memory leaks.
first, the core implementation:
Copy Code code as follows:

MyEvent 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 Bindings
* @param {HtmlElement} element
* @param {String} event name
* @param the function to be bound by {function}
*/
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} to unload
*/
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 triggers (NOTE: Browser default behavior and bubbling is not 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);
},
Correction
Fix:function (event) {
if (_dom) return event;
var name,
Newevent = {},
Doc = Document.documentelement,
BODY = document.body;
Newevent.target = Event.srcelement | | Document
NewEvent.target.nodeType = = 3 && (newevent.target = NewEvent.target.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 &AMP;&A mp Doc.clientleft | | Body && Body.clientleft | | 0);
Newevent.pagey = Newevent.clienty + (Doc && Doc.scrolltop | | body && Body.scrolltop | | 0)-(Doc &&amp ; Doc.clienttop | | Body && Body.clienttop | | 0);
Newevent.relatedtarget = Event.fromelement = = Newevent.target? Event.toElement:event.fromElement;
// !! IE Write event will be extremely easy to cause memory leaks, Firefox write event will be an error
Copy Event
for (name in event) Newevent[name] = Event[name];
return newevent;
}
};
return API;
})();

I tested 10,000 element binding events, the test tool is sieve, and the result:
Event Framework Take 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

You can see that both the execution efficiency and the memory footprint MyEvent have some advantages, which is probably due to the fact that the jquery event mechanism is too powerful to cause the loss of performance.
Test Sample: http://www.planeart.cn/demo/myEvent/

Second, extend custom event mechanism
jquery is a customizable event that stores custom events with a special namespace, and I mimic the jquery custom event mechanism based on the above code and transplant its famous ready event with another jquery Hashchange event plug-in.

These two custom events are very important, ready events can be in the DOM ready to bind events to elements, Much faster than the traditional use of window.onload; Hashchange events can listen for anchor changes and are often used to solve Ajax history problems, such as the new version of Twitter used to handle Ajax, and the use of anchor mechanisms in addition to improving the user experience of AJAX applications, if the rules can be Google index to.

Of course, the Imgready event that I implemented earlier in this article can also be extended through this to update later.

Copy Code code 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 Bindings
* @param {HtmlElement} element
* @param {String} event name
* @param the function to be bound by {function}
*/
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} to unload
*/
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 triggers (NOTE: Browser default behavior and bubbling is not 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);
},
Correction
Fix:function (event) {
if (_dom) return event;
var name,
Newevent = {},
Doc = Document.documentelement,
BODY = document.body;
Newevent.target = Event.srcelement | | Document
NewEvent.target.nodeType = = 3 && (newevent.target = NewEvent.target.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 &AMP;&A mp Doc.clientleft | | Body && Body.clientleft | | 0);
Newevent.pagey = Newevent.clienty + (Doc && Doc.scrolltop | | body && Body.scrolltop | | 0)-(Doc &&amp ; Doc.clienttop | | Body && Body.clienttop | | 0);
Newevent.relatedtarget = Event.fromelement = = Newevent.target? Event.toElement:event.fromElement;
// !! Direct write event IE lead to memory leaks, Firefox will complain
Disguise Event
for (name in event) Newevent[name] = Event[name];
return newevent;
}
};
return new API ();
})();
Dom Ready Events
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 (' onreadystatechange ', domcontentloaded);
Window.attachevent (' onload ', ready);
var toplevel = false;
try {
TopLevel = Window.frameelement = = NULL;
catch (e) {};
if (Document.documentElement.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 (' onreadystatechange ', domcontentloaded);
Ready ();
};
};
};
function Doscrollcheck () {
if (IsReady) return;
try {
Document.documentElement.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.documentmode,
Supports_onhashchange = ' on ' + str_hashchange in window && (doc_mode = = undefined | | doc_mode > 7);
function get_fragment (URL) {
url = URL | | Location.href;
Return ' # ' + url.replace (/^[^#]*#? *) $/, ' $ ';
};
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_window.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 that is invoked in the following way:

Copy Code code as follows:
Myevent.ready (function () {
[Code ...]
});

Hashchange events can be bound in a standard way:
Copy Code code as follows:
Myevent.bind (window, ' Hashchange ', function () {
[Code ...]
});

Here are some articles worth reading:
JavaScript cross-browser event System (Masaki). His blog has a series of explanations)
More elegant compatibility (Belleve Invis)

Related Article

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.