Skip to content

Instantly share code, notes, and snippets.

@think49
Last active September 25, 2015 07:07
Show Gist options
  • Save think49/882821 to your computer and use it in GitHub Desktop.
Save think49/882821 to your computer and use it in GitHub Desktop.
compatible-event.js : addEventListener, attachEvent のラッパー関数。addEvent したリスナーは window unload 時に削除される(循環参照対策)。attachEvent でも実行順が保証される。
/**
* compatible-event.js
*
* @version 0.5.1
* @author think49
*/
'use strict';
var ListenerEvent = (function () {
'use strict';
if (typeof addEventListener !== 'function' || typeof removeEventListener !== 'function') {
return;
}
function ListenerEvent (/*currentTarget*/) {
var caches;
if (!(this instanceof ListenerEvent)) {
return new ListenerEvent;
}
caches = [];
this.getCacheAll = function getCacheAll () {
return caches;
};
this.setCacheAll = function setCacheAll (inputCaches) {
caches = inputCaches;
};
this.setHandleUnload(arguments[0]);
}
(function () {
this.removeCache = function removeCache (node, type, listener, useCapture) {
var cache, caches, l;
caches = this.getCacheAll();
l = caches.length;
while (l--) {
cache = caches[l];
if (cache[0] === node && cache[1] === type && cache[2] === listener && cache[3] === useCapture) {
this.setCacheAll(caches.slice(0, l).concat(caches.slice(l + 1)));
return;
}
}
};
this.pushCache = function pushCache (node, type, listener, useCapture) {
return this.getCacheAll().push([node, type, listener, useCapture]);
};
this.add = function add (node, type, listener, useCapture) {
node.addEventListener(type, listener, useCapture);
this.pushCache(node, type, listener, useCapture);
};
this.remove = function remove (node, type, listener, useCapture) {
var caches;
node.removeEventListener(type, listener, useCapture);
this.removeCache(node, type, listener, useCapture);
};
this.setHandleUnload = function setHandleUnload (/*currentTarget*/) {
var that;
that = this;
function handleUnload (event) {
var currentTarget, caches, cache, i, l;
currentTarget = event.currentTarget;
caches = that.getCacheAll();
for (i = 0, l = caches.length; i < l; i++) {
cache = caches[i];
if (cache[0] === currentTarget && cache[1] === 'unload') {
cache[2].call(currentTarget, event);
}
console.log(cache);
cache[0].removeEventListener(cache[1], cache[2], cache[3]);
}
currentTarget.removeEventListener('unload', handleUnload, false);
that = handleUnload = null;
alert('unload completed');
}
arguments[0] && typeof arguments[0] === 'object' ? arguments[0].addEventListener('unload', handleUnload, false) : addEventListener('unload', handleUnload, false);
};
}).call(ListenerEvent.prototype);
return ListenerEvent;
})();
var JScriptEvent = (function () {
'use strict';
if (typeof attachEvent !== 'object' || typeof detachEvent !== 'object') {
return;
}
function JScriptEvent (/*currentTarget*/) {
var caches;
if (!(this instanceof JScriptEvent)) {
return new JScriptEvent;
}
caches = [];
this.getCacheAll = function getCacheAll () {
return caches;
};
this.setCacheAll = function setCacheAll (inputCaches) {
caches = inputCaches;
};
this.setHandleUnload(arguments[0]);
}
(function () {
function createHandleEvent (node, handleEvent) {
return function (event) {
if (!('currentTarget' in event)) {
event.currentTarget = node;
}
handleEvent.call(node, event);
};
}
this.removeCache = function removeCache (node, type, handleEvent) {
var cache, caches, l;
caches = this.getCacheAll();
l = caches.length;
while (l--) {
cache = caches[l];
if (cache[0] === node && cache[1] === type && cache[2] === handleEvent) {
this.setCacheAll(caches.slice(0, l).concat(caches.slice(l + 1)));
return;
}
}
};
this.pushCache = function pushCache (node, type, listener) {
return this.getCacheAll().push([node, type, listener]);
};
this.add = function add (node, type, handleEvent) {
node.attachEvent('on' + type, handleEvent);
this.pushCache(node, type, handleEvent);
};
this.remove = function remove (node, type, handleEvent) {
var caches;
node.detachEvent(type, handleEvent);
this.removeCache(node, type, handleEvent);
};
this.setHandleUnload = function setHandleUnload (/*currentTarget*/) {
var that;
function handleUnload (event) {
var currentTarget, caches, cache, i, l;
currentTarget = event.currentTarget;
caches = that.getCacheAll();
console.log(currentTarget);
for (i = 0, l = caches.length; i < l; i++) {
cache = caches[i];
if (cache[0] === currentTarget && cache[1] === 'unload') {
cache[2].call(currentTarget, event);
}
console.log(cache);
cache[0].removeEventListener(cache[1], cache[2], cache[3]);
}
currentTarget.detachEvent('unload', handleUnload, false);
that = handleUnload = null;
alert('unload completed');
}
that = this;
if (arguments[0] && typeof arguments[0] === 'object') {
handleUnload = createHandleEvent(arguments[0], handleUnload);
arguments[0].attachEvent('unload', handleUnload);
} else {
handleUnload = createHandleEvent(Function('return this')(), handleUnload);
attachEvent('unload', handleUnload);
}
};
}).call(JScriptEvent.prototype);
return JScriptEvent;
})();
var CompatibleEvent = ListenerEvent || JScriptEvent;
@think49
Copy link
Author

think49 commented Mar 24, 2011

compatible-event.js

概要

このライブラリは大別して3つの機能があります。

  1. attachEvent の実行順を保証する (*備考1)
  2. attachEvent で DOM Events 相当のイベントプロパティが使えるようにする (ついでに this 値も event.currentTarget 相当にする)
  3. addEventListener, attachEvent で追加されたリスナー(イベントハンドラ)は window unload 時に削除される

1, 2 は IE (JScript) のための機能です。addEventListener と同等の機能を期待できます。

3 は循環参照に起因する問題で IE6SP2- に存在する 循環参照によるメモリリークパターン を解消します。
addEventListener でも実装しているのは「循環参照によるメモリリークは新規実装でも生じる可能性がある」と私が教わったためです。
実際に jquery.js や prototype.js も同様の実装をしているようです。
この実装によってイベントハンドラ関数(リスナー関数)でクロージャを形成し、クロージャがDOMノードを参照するコードを書いても、循環参照しなくなります。

3 が不要と判断できる場合は JScriptEvent 単体で使用することも出来ます。ListenerEvent, JScriptEvent には依存関係はありません。

(*備考1) IE の attachEvent で追加されたイベントハンドラはランダムに実行されることになっています。実際には一定の法則があるようですが、仕様ではランダムと規定されているので今後実装が変わる可能性があります。

既知の不具合

  • JScriptEvent で追加される listener において event.relatedTarget など一部のイベントプロパティを使用できません。
  • addEventListener の listener に { handleEvent: ... } を渡せるか、は DOM Events の規定外(実装依存)です。現在のバージョンでは window unload を追加したときには擬似的に { handleEvent: ... } を実装し、new ListenerEvent().add 時にはネイティブ実装を採用する挙動になっており、整合性がとれていません。

参考リンク

compatible-event.js

DOM Events

MSDN

HTML5

参考資料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment