myEvent.js JavaScript クロスブラウザ イベント フレームワーク_JavaScript スキル

WBOY
リリース: 2016-05-16 18:00:32
オリジナル
1376 人が閲覧しました

イベントはどれくらい複雑ですか? 最高の addEvent はどのように誕生したか という 6 年前の先人の努力がわかります。また、新星 jQuery も 1,600 行以上の苦労して作成したコード (v 1.5.1) を費やして、 6年後に現れた問題を解決 各種コアブラウザ。

私は先人のコードと私自身の理解を参考にしてイベント フレームワークを作成しようとしました。私のフレームワークは、マルチイベント バインディングを実装するための統一されたインターフェイスを提供できます。メモリリークなどの問題はありませんが、さらに重要なことに、パフォーマンスはかなり優れています。

私のメソッド:

すべてのコールバック関数は、要素、イベント タイプ、およびコールバック関数の一意の ID に基づいて _create オブジェクトにキャッシュされます (その内部構造は以下のソース コードに示されています (_cache に関する注記)。
イベント バインディングは _create プロキシ関数を使用して処理され、要素のすべての種類のイベントはこれを通じて配布され、apply メソッドを使用して IE のポインタが要素を指すようにします。
配列キューによるIEコールバック関数の実行順序の問題を解決します。
fix 関数は、コールバック関数で渡されたイベント パラメーターやその他の互換性の問題を処理します。ここではjQuery.event.fixを参照しています。
メモリ リークを避けるために、イベントと要素間の循環参照を切断します。
1. コア実装:

コードをコピーします コードは次のとおりです:

// myEvent 0.2
// 2011.04.06 - TangBin - planart.cn - MIT ライセンス
/**
* イベントフレーム
* @namespace
* @http://www.planeart.cn/?p=1285 を参照
*/
var myEvent = (function () {
var _fid = 1、
_guid = 1、
_time = (新しい日付).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 = argument,
events = _cache[guid].events[type];
for (; i < events.length; i ) {
if (events[i].apply() elem, data) === false)event.preventDefault();
},
_cache = {/*
1: {
elem: HTMLElement)、
イベント: {
クリック: [(関数)、(..)]、
(..)
}、
リスナー: (関数)
}、
(..)
*/};
var api = {
/**
* イベントバインディング
* @param {HTMLElement} 要素
* @param {String} イベント名
* @param {Function} バインドされる関数
*/
bind: function (elem, type, callback) {
var guid = elem[_nEid] || (elem[_nEid] = _guid );
if (!_cache[guid]) _cache[guid] = {
elem,
listener: _create(guid) 、
イベント: {}
};
if (type && !_cache[guid].events[type]) {
_cache[guid].events[type] = [];
api.add(elem, type, _cache[guid].listener);
};
if (コールバック) {
if (!callback[_nFid]) コールバック[_nFid] = _fid ;
_cache[guid].events[type].push(callback);
} else
return _cache[guid];
},
/**
* アンロードするイベント
* @param {HTMLElement} 要素
* @param {String} イベント名
* @param {Function} アンロードする関数
*/
unbind: function (elem, type, callback) {
var events, i, list,
guid = elem[_nEid],
ハンドラー = _cache[guid];
if (!handler) return;
イベント = handler.events;
if (コールバック) {
list = events[type];
if (!list) return;
for (i = 0; i list[i][_nFid] === callback[_nFid] && list.splice(i--, 1);
};
if (list.length === 0) return api.unbind(elem, type);
} else if (type) {
イベントを削除[タイプ];
api.remove(elem, type, handler.listener);
} else {
for (i in events) {
api.remove(elem, i, handler.listener);
};
_cache[guid] を削除します;
};
},
/**
* イベント トリガー (注: ブラウザーのデフォルトの動作とバブリングはトリガーされません)
* @param {HTMLElement} 要素
* @param {String} イベント名
* @param {Array} (オプション) 追加データ
*/
triggerHandler: function (elem, type, data) {
var guid = elem[_nEid],
event = {
type: type,
target: elem,
currentTarget: elem,
preventDefault: _noop,
stopPropagation: _noop
};
データ = データ || [];
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) {};
},
// 原生イベント绑定接続口
add: _DOM ? function (elem, type, リスナー) {
elem.addEventListener(type, リスナー, false);
} : 関数 (elem, タイプ, リスナー) {
elem.attachEvent('on' タイプ, リスナー);
},
// 原生イベント卸ダウンロードインターフェース
remove: _DOM ? function (elem, type, リスナー) {
elem.removeEventListener(type, リスナー, false);
} : 関数 (elem, タイプ, リスナー) {
elem.detachEvent('on' タイプ, リスナー);
},
// 修正
fix: function (event) {
if (_DOM) return イベント;
変数名、
newEvent = {}、
doc = document.documentElement、
body = document.body;
newEvent.target =event.srcElement ||書類;
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 && 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.popularTarget = events.fromElement === newEvent.target ?イベント.toElement : イベント.fromElement;
// !!IE写イベント会极その容易导致内存漏漏,Firefox写イベント会报错
// 収集イベント
for (イベントの名前) newEvent[名前] = イベント[名前];
新しいイベントを返します;
}
};
API を返す;
})();


我给一万个元素绑定事件进行了测试,测试工具为sIEve,结果:
事件框架 耗时 内存
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

myEvent には、実行効率とメモリ使用量の点で一定の利点があることがわかります。これは、jQuery イベント メカニズムが強力すぎることによるパフォーマンスの低下が原因である可能性があります。
テストサンプル: http://www.planeart.cn/demo/myEvent/

2. カスタム イベントのメカニズムを拡張する
jQuery は、カスタム イベントを格納するために特別な名前空間を使用します。そのコードをベースに jQuery のカスタム イベントを移植しました。有名な Ready イベントと別の jQuery hashchange イベント プラグイン。

これら 2 つのカスタム イベントは非常に重要です。ready イベントは、DOM の準備ができたときにイベントを要素にバインドできます。これは、従来の window.onload の使用よりもはるかに高速です。hashchange イベントは、アンカー ポイントの変更を監視でき、よく使用されます。たとえば、Twitter の新しいバージョンでは、このメソッドを使用して AJAX アプリケーションのユーザー エクスペリエンスが向上するだけでなく、特定のルールに従っている場合、アンカー メカニズムを使用して Google によってインデックスを作成することもできます。

もちろん、前の記事で実装した imgReady イベントもこの拡張機能を介して組み込むことができ、後で更新されます。

コードをコピー コードは次のとおりです:

// myEvent 0.2.2
// 2011.04.07 - TangBin - planart.cn - MIT ライセンス
/**
* イベントフレーム
* @namespace
* @http://www.planeart.cn/?p=1285 を参照
*/
var myEvent = (function () {
var _ret, _name,
_fid = 1,
_guid = 1,
_time = (新しい日付).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 = argument、
events = _cache[guid].events[type]、
i = 0、
length = events.length
for (; i < length; ; i ) {
if (events[i].apply(elem, data) === false)
};
event = elem = null;
},
_cache = {/*
1: {
elem: (HTMLElement),
events: {
click: [(Function), (..)],
(..)
}、
リスナー: (関数)
}、
(..)
*/};
var API = function () {} ;
API.prototype = {
/**
* イベントバインディング
* @param {HTMLElement} 要素
* @param {String} イベント名
* @param {Function} バインドされる関数
*/
bind: function (elem, type, callback) {
var イベント、リスナー、
guid = elem[_nEid] ] || (elem[_nEid] = _guid )、
special = this.special[type] || {}、
cacheData = _cache[guid];
if (!cacheData)cacheData = _cache[guid] = {
elem: elem,
listener: _create(guid),
events: {}
};
イベント =cacheData.events;
リスナー =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) );
};
イベント[タイプ].push(callback);
},
/**
* アンロードするイベント
* @param {HTMLElement} 要素
* @param {String} イベント名
* @param {Function} アンロードする関数
*/
unbind: function (elem, type, callback) {
var events,special,i,list,fid,
guid = elem [_nEid]、
cacheData = _cache[guid];
if (!cacheData) return;
イベント =cacheData.events;
if (コールバック) {
list = events[type];
fid = コールバック[_nFid];
if (!list) return;
for (i = 0; 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);
};
イベントを削除[タイプ];
} else {
for (i in events) {
this.remove(elem, i, cacheData.listener);
};
_cache[guid] を削除します;
};
},
/**
* イベント トリガー (注: ブラウザーのデフォルトの動作とバブリングはトリガーされません)
* @param {HTMLElement} 要素
* @param {String} イベント名
* @param {Array} (オプション) 追加データ
*/
triggerHandler: function (elem, type, data) {
var guid = elem[_nEid],
cacheData = _cache[guid] ,
event = {
type: type,
target: elem,
currentTarget: elem,
preventDefault: _noop,
stopPropagation: _noop
};
データ = データ || [];
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) {};
},
// 自定义イベントインターフェース
special: {},
// 原生イベント绑定インターフェース
add: _DOM ? function (elem, type, リスナー) {
elem.addEventListener(type, リスナー, false);
} : 関数 (elem, タイプ, リスナー) {
elem.attachEvent('on' タイプ, リスナー);
},
// 原生イベント卸ダウンロードインターフェース
remove: _DOM ? function (elem, type, リスナー) {
elem.removeEventListener(type, リスナー, false);
} : 関数 (elem, タイプ, リスナー) {
elem.detachEvent('on' タイプ, リスナー);
},
// 修正
fix: function (event) {
if (_DOM) return イベント;
変数名、
newEvent = {}、
doc = document.documentElement、
body = document.body;
newEvent.target =event.srcElement ||書類;
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 && 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.popularTarget = events.fromElement === newEvent.target ?イベント.toElement : イベント.fromElement;
// !!直接写event IE导致内存漏,Firefox会报错
// 伪装event
for (イベントの名前) newEvent[name] = イベント[name];
新しいイベントを返します;
}
};
新しい API() を返します;
})();
// DOM就绪イベント
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({});
readyList = null;
関数バインドレディ() {
if (readyBound) return;
if (document.readyState === 'complete') {
returnready();
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', DOMContentLoaded, false); );
} else if (document.attachEvent) {
document.attachEvent('onreadystatechange', DOMContentLoaded);
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);
準備完了();
};
} else if (document.attachEvent) {
DOMContentLoaded = function () {
if (document.readyState === 'complete') {
document.detachEvent('onreadystatechange', DOMContentLoaded) ;
準備完了();
};
};
};
function doScrollCheck () {
if (isReady) return;
try {
document.documentElement.doScroll('left');
} catch (e) {
setTimeout(doScrollCheck, 1);
戻る;
};
準備完了();
};
return 関数 (コールバック) {
bindReady();
if (isReady) {
callback.call(document, {});
} else if (readyList) {
readyList.push(callback);
};
これを返します;
};
})();
// Hashchange Event v1.3
(function (window, unknown) {
var config = {
lay: 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 === 未定義 || doc_mode > 7);
function get_fragment(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);
}
}
/**@インナー*/
fake_onhashchange = (function () {
var self = {},
timeout_id, last_hash = get_fragment(),
/**@インナー*/
fn_retval = function (val) {
return val
},
history_set = fn_retval,
history_get = fn_retval;
self.start = function () {
timeout_id ||ポーリング();
};
self.stop = function () {
timeout_id && clearTimeout(timeout_id);
timeout_id = 未定義;
};
関数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.lay);
};
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('');
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;
/**@インナー*/
history_get = function () {
return get_fragment(iframe_window.location.href);
};
/**@インナー*/
history_set = function (hash,history_hash) {
var iframe_doc = iframe_window.document,
domain = config.domain;
if (ハッシュ !==history_hash) {
iframe_doc.title = doc.title;
iframe_doc.open();
ドメイン && iframe_doc.write('<SCRIPT>document.domain="' ドメイン '"</SCRIPT>');
iframe_doc.close();
iframe_window.location.hash = ハッシュ;
};
};
})();
自分自身を返します。
})();
})(これ);

ready イベントは伪イベント、调用方式:

复制代码 代码如:
myEvent.ready(function () {
//[code..]
});

hashchange イベントは標準方式で定義できます:
复制代码 代次:
myEvent.bind(window, 'hashchange', function () {
//[code..]
});

ここに文章が得られます:
javascript がユーザーをまたがるイベント系(司徒正美。他博有一系列的讲解)
詳細优雅の兼容(BELLEVE INVIS)
関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート