获取元素中的所有监听事件
在Chrome DevTools
命令行API提供了多种方式来观察和检查事件监听器。
比如说 getEventListeners
方法,可以获取元素上的所有监听器,但是这些方法只能在chrome
调试中使用,不能在js
中使用。
如果我们想在js
中使用,可以修改addEventListener
和removeEventListener
。
使用call方法复写方法
const addEventListener = document.addEventListener;
document.addEventListener = function () {
console.log('rewrite addEventListener')
addEventListener.call(document, arguments[0], arguments[1]);
};
可以在调用addEventListener
的时候将事件记录在对应的元素上。其实就是在给元素添加一个属性用来记录所有的事件,然后暴露一些方法用来访问这些事件。
添加属性
创建一个对象,用来保存事件及方法。
class BaseEvent {
constructor(ele) {
this.element = ele;
this._events = {};
// 复写方法
this.rewriteAddEventListener();
this.rewriteRemoveEventListener();
// 将方法绑定到元素上,即可以通过document.fn进行访问
ele.getAllEventListeners = this.getAllEventListeners.bind(this);
ele.getAllEvents = this.getAllEvents.bind(this);
}
rewriteAddEventListener() {
const addEventListener = this.element.addEventListener;
const _ = this;
this.element.addEventListener = function () {
_.addEvent(arguments[0], arguments[1]);
addEventListener.call(_.element, arguments[0], arguments[1]);
};
}
rewriteRemoveEventListener() {
const removeEventlistener = this.element.removeEventListener;
const _ = this;
this.element.removeEventListener = function () {
_.removeEvent(arguments[0]);
removeEventlistener.call(_.element, arguments[0], arguments[1]);
};
}
addEvent(eventName, callback) {
if (this._events[eventName]) {
this._events[eventName].push(callback);
} else {
this._events[eventName] = [callback];
}
}
removeEvent(eventName) {
delete this._events[eventName];
}
getAllEvents() {
return Object.getOwnPropertyNames(this._events);
}
getAllEventListeners() {
return this._events;
}
}
使用:
document._extends = new BaseEvent(document);
document.getAllEventListeners()
复写获取元素的方法
可能有人觉得上边的每次都要new
一个BaseEvent
才能使用比较繁琐。那么有没有其他更简单的方法。我们试想一下我们是怎么获取对应元素的呢?不就是通过浏览器上的那几个方法嘛(getElementById
等),那我们直接复写这些方法就好了,每次在获取对应元素的时候给它们加上一个BaseEvent
实例,这样拿到元素之后不就可以直接使用了嘛。
function extendElement(ele) {
if (!ele) {
return null;
}
if (!ele._extends) {
ele._extends = new BaseEvent(ele);
return ele;
}
}
const getElementById = document.getElementById;
document.getElementById = function () {
const elem = getElementById.call(document, arguments[0]);
return extendElement(elem);
};
对于getElementById
、querySelector
而言,获取的只是单个元素,很容易处理。但是对于其他API来讲,获取的是元素数组,这个需要进行额外的处理。
const getElementsByTagName = document.getElementsByTagName;
document.getElementsByTagName = function () {
const elem = getElementsByTagName.call(document, arguments[0]);
if (!elem) {
return new HTMLAllCollection();
}
return proxyObject(elem);
};
function proxyObject(obj) {
const hanlder = {
get: (obj, prop) => {
return extendElement(obj[prop]);
}
}
const proxyObject = new Proxy(obj, hanlder);
return proxyObject;
}
采用Proxy
代理拦截对数组的访问,当访问具体元素时候再添加上一个BaseEvent
。
监听赋值事件
对于document.onclick = function(){}
这些事件而已,这边的处理就是监听这些事件,当事件被赋值时,使用addEventListener
进行监听。当document.onclick = null
时取消监听该事件。
observeAssignmentEvent() {
const _ = this;
eventList.forEach((item) => {
Object.defineProperty(_.element, `on${item}`, {
set(fn) {
let i = _.isAlreadyListenedByAssignment(item);
if(fn) {
if( i !== -1) {
_.element.removeEventListener(item, _._events[item][i])
}
_.element.addEventListener(item, {value: fn, intro: 'add by assignment'});
return;
}
if(i !== -1) {
_.element.removeEventListener(item, _._events[item][i])
}
}
})
})
}
isAlreadyListenedByAssignment(item) {
if(!this._events[item] || this._events[item].length === 0) {
return -1;
}
let idx = -1;
this._events[item].forEach((item, i) => {
if(typeof item === 'object') {
if(item.intro === 'add by assignment') {
idx = i;
return;
}
}
})
return idx;
}
其他API都是类似的处理,这里就不一一介绍了。
具体代码可以查看https://github.com/leopord-lau/eevent
使用
在项目安装eevent
:
npm i eevent
在项目中引入import 'eevent'
就可以了。