Last active
September 25, 2015 07:07
-
-
Save think49/882821 to your computer and use it in GitHub Desktop.
compatible-event.js : addEventListener, attachEvent のラッパー関数。addEvent したリスナーは window unload 時に削除される(循環参照対策)。attachEvent でも実行順が保証される。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
compatible-event.js
概要
このライブラリは大別して3つの機能があります。
1, 2 は IE (JScript) のための機能です。addEventListener と同等の機能を期待できます。
3 は循環参照に起因する問題で IE6SP2- に存在する 循環参照によるメモリリークパターン を解消します。
addEventListener でも実装しているのは「循環参照によるメモリリークは新規実装でも生じる可能性がある」と私が教わったためです。
実際に jquery.js や prototype.js も同様の実装をしているようです。
この実装によってイベントハンドラ関数(リスナー関数)でクロージャを形成し、クロージャがDOMノードを参照するコードを書いても、循環参照しなくなります。
3 が不要と判断できる場合は JScriptEvent 単体で使用することも出来ます。ListenerEvent, JScriptEvent には依存関係はありません。
(*備考1) IE の attachEvent で追加されたイベントハンドラはランダムに実行されることになっています。実際には一定の法則があるようですが、仕様ではランダムと規定されているので今後実装が変わる可能性があります。
既知の不具合
参考リンク
compatible-event.js
DOM Events
MSDN
HTML5
参考資料