JavaScript
event
EventListener

addEventListener & removeEventListener polyfill

More than 3 years have passed since last update.


概要

本投稿は、teratailの質問「IE8以下でのaddEventListener、removeEventListenerへの対応について」に対するaddEventListenerremoveEventListenerのpolyfillコードに関する意見収集が目的である。


元のコード

addEventListener polyfill for IE6+


特徴、及びコメントの指摘


  • 対応しているのはaddEventListenerメソッドのみ


  • Event.targetプロパティとEvent.currentTargetプロパティが未実装 1

  • jQueryで問題が発生する 2

    原因はjQueryがremoveEventListenerメソッドの対応判定にdocument.addEventListenerを利用するため。

  • 8行目に無駄なvar宣言が存在する 3

    (直後のコメントに、無駄では無いと書かれている…?)

  • 引数fnに関数以外の値が指定された場合の挙動が想定されていない 4

※著者はChromeの翻訳機能に頼っているため、訳は不正確の可能性あり


コード


1.1



  • listeners_get,listeners_set関数をlistener_get,listener_setに改名

  • 変数listenersに保持していたリスナーと内部無名関数(リスナーを内包し、Eventオブジェクトを補完する関数)の参照を、リスナー自身のx-ms-event-listenersプロパティに代入するよう変更。

    (JavaScriptでは関数もオブジェクトなため、関数に連想配列のように値を設定できる)


v1.1

/**

* @license addEventListener polyfill 1.0 / Eirik Backer / MIT Licence
* https://gist.github.com/2864711/946225eb3822c203e8d6218095d888aac5e1748e
*/

(function (window, document, listeners_prop_name) {
if ((!window.addEventListener || !window.removeEventListener) && window.attachEvent && window.detachEvent) {
/**
* @param {*} value
* @return {boolean}
*/

var is_callable = function (value) {
return typeof value === 'function';
};
/**
* @param {!Window|HTMLDocument|Node} self
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @return {!function(Event)|undefined}
*/

var listener_get = function (self, listener) {
var listeners = listener[listeners_prop_name];
if (listeners) {
var lis;
var i = listeners.length;
while (i--) {
lis = listeners[i];
if (lis[0] === self) {
return lis[1];
}
}
}
};
/**
* @param {!Window|HTMLDocument|Node} self
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @param {!function(Event)} callback
* @return {!function(Event)}
*/

var listener_set = function (self, listener, callback) {
var listeners = listener[listeners_prop_name] || (listener[listeners_prop_name] = []);
return listener_get(self, listener) || (listeners[listeners.length] = [self, callback], callback);
};
/**
* @param {string} methodName
*/

var docHijack = function (methodName) {
var old = document[methodName];
document[methodName] = function (v) {
return addListen(old(v));
};
};
/**
* @this {!Window|HTMLDocument|Node}
* @param {string} type
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @param {boolean=} useCapture
*/

var addEvent = function (type, listener, useCapture) {
if (is_callable(listener)) {
var self = this;
self.attachEvent(
'on' + type,
listener_set(self, listener, function (e) {
e = e || window.event;
e.preventDefault = e.preventDefault || function () { e.returnValue = false };
e.stopPropagation = e.stopPropagation || function () { e.cancelBubble = true };
e.target = e.target || e.srcElement || document.documentElement;
e.currentTarget = e.currentTarget || self;
e.timeStamp = e.timeStamp || (new Date()).getTime();
listener.call(self, e);
})
);
}
};
/**
* @this {!Window|HTMLDocument|Node}
* @param {string} type
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @param {boolean=} useCapture
*/

var removeEvent = function (type, listener, useCapture) {
if (is_callable(listener)) {
var self = this;
var lis = listener_get(self, listener);
if (lis) {
self.detachEvent('on' + type, lis);
}
}
};
/**
* @param {!Node|NodeList|Array} obj
* @return {!Node|NodeList|Array}
*/

var addListen = function (obj) {
var i = obj.length;
if (i) {
while (i--) {
obj[i].addEventListener = addEvent;
obj[i].removeEventListener = removeEvent;
}
} else {
obj.addEventListener = addEvent;
obj.removeEventListener = removeEvent;
}
return obj;
};

addListen([document, window]);
if ('Element' in window) {
/**
* IE8
*/

var element = window.Element;
element.prototype.addEventListener = addEvent;
element.prototype.removeEventListener = removeEvent;
} else {
/**
* IE < 8
*/

//Make sure we also init at domReady
document.attachEvent('onreadystatechange', function () { addListen(document.all) });
docHijack('getElementsByTagName');
docHijack('getElementById');
docHijack('createElement');
addListen(document.all);
}
}
})(window, document, 'x-ms-event-listeners');



1.0



  • removeEventListenerメソッドの対応


  • attachEventメソッドおよびdetachEventメソッドの対応チェックを追加

  • 関数の参照の維持

  • 第二引数が関数かどうか判定し、関数の場合のみ実行


  • DOM Level 2 Eventsの仕様に従い、addEventListenerメソッドが返り値を返さないよう変更


  • Event.target,Event.currentTarget,Event.timeStampプロパティに対応

  • Closure Compiler用のJsDocコメントを追加


v1.0

/**

* @license addEventListener polyfill 1.0 / Eirik Backer / MIT Licence
* https://gist.github.com/2864711/946225eb3822c203e8d6218095d888aac5e1748e
*/

(function (window, document) {
if ((!window.addEventListener || !window.removeEventListener) && window.attachEvent && window.detachEvent) {
/**
* @type {Array}
*/

var listeners = [];
/**
* @param {*} value
* @return {boolean}
*/

var is_callable = function (value) {
return typeof value === 'function';
};
/**
* @param {!Window|HTMLDocument|Node} self
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @return {!function(Event)|undefined}
*/

var listeners_get = function (self, listener) {
var lis;
var i = listeners.length;
while (i--) {
lis = listeners[i];
if (lis[0] === self && lis[1] === listener) {
return lis[2];
}
}
};
/**
* @param {!Window|HTMLDocument|Node} self
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @param {!function(Event)} callback
* @return {!function(Event)}
*/

var listeners_set = function (self, listener, callback) {
return listeners_get(self, listener) || (listeners[listeners.length] = [
self,
listener,
callback
], callback);
};
/**
* @param {string} methodName
*/

var docHijack = function (methodName) {
var old = document[methodName];
document[methodName] = function (v) {
return addListen(old(v));
};
};
/**
* @this {!Window|HTMLDocument|Node}
* @param {string} type
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @param {boolean=} useCapture
*/

var addEvent = function (type, listener, useCapture) {
if (is_callable(listener)) {
var self = this;
self.attachEvent(
'on' + type,
listeners_set(self, listener, function (e) {
e = e || window.event;
e.preventDefault = e.preventDefault || function () { e.returnValue = false };
e.stopPropagation = e.stopPropagation || function () { e.cancelBubble = true };
e.target = e.target || e.srcElement || document.documentElement;
e.currentTarget = e.currentTarget || self;
e.timeStamp = e.timeStamp || (new Date()).getTime();
listener.call(self, e);
})
);
}
};
/**
* @this {!Window|HTMLDocument|Node}
* @param {string} type
* @param {EventListener|function(!Event):(boolean|undefined)} listener
* @param {boolean=} useCapture
*/

var removeEvent = function (type, listener, useCapture) {
if (is_callable(listener)) {
var self = this;
var lis = listeners_get(self, listener);
if (lis) {
self.detachEvent('on' + type, lis);
}
}
};
/**
* @param {!Node|NodeList|Array} obj
* @return {!Node|NodeList|Array}
*/

var addListen = function (obj) {
var i = obj.length;
if (i) {
while (i--) {
obj[i].addEventListener = addEvent;
obj[i].removeEventListener = removeEvent;
}
} else {
obj.addEventListener = addEvent;
obj.removeEventListener = removeEvent;
}
return obj;
};

addListen([document, window]);
if ('Element' in window) {
/**
* IE8
*/

var element = window.Element;
element.prototype.addEventListener = addEvent;
element.prototype.removeEventListener = removeEvent;
} else {
/**
* IE < 8
*/

//Make sure we also init at domReady
document.attachEvent('onreadystatechange', function () { addListen(document.all) });
docHijack('getElementsByTagName');
docHijack('getElementById');
docHijack('createElement');
addListen(document.all);
}
}
})(window, document);



脚注