版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hulk_oh/article/details/52902412
简述
我认为,事件是我们前端最为熟悉的编程模式,在前端开发中能接触太多太多,而且相对而言,事件是一种相对容易理解,逻辑性高的的模式,对于优化组件/插件的逻辑是一个很好的应用模式。
这文章主要是用JavaScript实现3级dom事件机制,后面的更新会涉及应用倒组件开发当中。
- 浏览器事件一:事件的基础应用
- 浏览器事件二:浏览器是如何实现事件
- 浏览器事件三:自定义事件
如何实现
jQuery 实现方式
jQuery 是实现了一个独立的事件方法,甚至可以在自定义对象(object)上绑定事件,功能十分强大。
例:
//import jQuery
$(selector).on('yourEvent',function(){
console.log('i m yourEvent handler');
})
$(selector).trigger('yourEvent')
上面例子就是jQuery 的自定义事件实现方式,下面再展示一个绑定对象上的事件,它其实是一个订阅/发布模式的实现 —> cowboy 实现的简易订阅/发布
例:
(function($) {
var o = $({});
$.subscribe = function() {
o.on.apply(o, arguments);
};
$.unsubscribe = function() {
o.off.apply(o, arguments);
};
$.publish = function() {
o.trigger.apply(o, arguments);
};
}(jQuery));
jQuery的事件实现原理这里不作陈述,这里提供Aaron的jQuery源码解析传送门
jQuery实现的优点
- 良好的维护性
jQuery是一个使用率最高的前端库,这意味这它能提供非常友好的文档,以及学习社区。从项目的角度上来看这是提高维护性的一个做法。 - 良好的兼容性
解决兼容性是意见相当麻烦琐碎的事情,如果没有充足的经验,很难再编码的的开始就预计到那些兼容问题,所以jQuery 本身某种层度就是提供兼容处理封装。
jQuery实现的缺点
- 依赖jQuery
实现上,我们并不是所有项目都需要依赖jQuery,jQuery本身虽然十分好,但是jq链式操作dom写出来的页面/组件,再可读性上确实不尽人意,业界都是认同那是一坨意大利面(特别是webapp项目)。所以其实从另一个层面上看,jQuery并不是一个十分好提高维护性的方案。 - 能实现的功能有限
如果单纯做组件抽象,或者基于监听响应实现的对象,jq的实现确实是满足并且能提供简洁的实现方法,但是在某些需求上不一定能满足,如:如果我们希望组件的实现逻辑更加清晰,希望加入一个事件响应流,能在组件之间链式传播。 - 性能上
jQuery事件实现内部也是已经实现事件的,如果在jq之上在添加自定义的事件功能,性能上必然有影响(不可控),性能影响不会很大,但做性能优化时会是一个问题。
任意对象绑定事件
在原生JavaScript对象,自定义的object 对象并没有事件的概念, 而实际上 我们希望某些组件拥有事件驱动,js拥有完整的事件驱动对象,但是我们开发者没有办法使用(第二章有提及),不过我们还是能基于官方的文档事件一个与js一样的事件对象。
官方自定义 eventtarget
我们从官方的事件文档中看到一个简单的事件驱动对象实现例子。
var eventTarget = function() {
this.listeners = {};
};
eventTarget.prototype.listeners = null;
eventTarget.prototype.addEventListener = function(type, callback) {
if(!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
};
eventTarget.prototype.removeEventListener = function(type, callback) {
if(!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for(var i = 0, l = stack.length; i < l; i++) {
if(stack[i] === callback){
stack.splice(i, 1);
return this.removeEventListener(type, callback);
}
}
};
eventTarget.prototype.dispatchEvent = function(event) {
if(!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type];
event.target = this;
for(var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
};
这个例子可以是我们实现自己事件的最顶层类。
如何绑定到自定义对象
假设我们已经有了 eventTarget对象,下面我们要做的就是把事件绑定到对象上
var component = function(){};
component.prototype = new eventTarget();
var com_1 = new component();
com_1.addEventListener('click',function(){
console.log('i m listenning click event')
});
com_1.dispatchEvent(new MouseEvent('click')) //i m listenning click event
加入htmlElement事件流
htmlElement的事件流是独立的,实际上的C++在做事件流的传递,自定义的事件没法加入到这个事件流当中。
这种冒泡的事件流机制非常常见,在ios,Android中的事件驱动都是类似这种机制。在事件中这样的思想/机制能把事件的耦合度降低。在大型组件的事件逻辑中,加入这种机制个人认为是非常好的。
这里提供一个实现思路,鄙人愚见,请大神指点。
创建自己的对象的事件流
var flowEvent = function (parentNode) {
this.listeners = {};
this.childrenNode = [];
this.parentNode = parentNode; // 父节点
parentNode && parentNode.childrenNode.push(this) //加入父节点的子节点
}
flowEvent.prototype = new eventTarget(); //继承事件
flowEvent.prototype.dispatchEvent = function (event) { //重写事件分发方法
if(!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type]; //要独立lisenter
event.target = this;
for(var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
if(event.target){
//todo 加入阻止同类事件&阻止传递
this.childrenNode.length>0 && this.childrenNode.forEach(function (v) {
v.dispatchEvent(event)
})
}else{
this.parentNode && this.parentNode.dispatchEvent(event)
}
}