自定义浏览器事件,模拟浏览器事件流

简述

我认为,事件是我们前端最为熟悉的编程模式,在前端开发中能接触太多太多,而且相对而言,事件是一种相对容易理解,逻辑性高的的模式,对于优化组件/插件的逻辑是一个很好的应用模式。

这文章主要是用JavaScript实现3级dom事件机制,后面的更新会涉及应用倒组件开发当中。

  1. 浏览器事件一:事件的基础应用
  2. 浏览器事件二:浏览器是如何实现事件
  3. 浏览器事件三:自定义事件

如何实现

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实现的优点

  1. 良好的维护性
    jQuery是一个使用率最高的前端库,这意味这它能提供非常友好的文档,以及学习社区。从项目的角度上来看这是提高维护性的一个做法。
  2. 良好的兼容性
    解决兼容性是意见相当麻烦琐碎的事情,如果没有充足的经验,很难再编码的的开始就预计到那些兼容问题,所以jQuery 本身某种层度就是提供兼容处理封装。

jQuery实现的缺点

  1. 依赖jQuery
    实现上,我们并不是所有项目都需要依赖jQuery,jQuery本身虽然十分好,但是jq链式操作dom写出来的页面/组件,再可读性上确实不尽人意,业界都是认同那是一坨意大利面(特别是webapp项目)。所以其实从另一个层面上看,jQuery并不是一个十分好提高维护性的方案。
  2. 能实现的功能有限
    如果单纯做组件抽象,或者基于监听响应实现的对象,jq的实现确实是满足并且能提供简洁的实现方法,但是在某些需求上不一定能满足,如:如果我们希望组件的实现逻辑更加清晰,希望加入一个事件响应流,能在组件之间链式传播。
  3. 性能上
    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中的事件驱动都是类似这种机制。在事件中这样的思想/机制能把事件的耦合度降低。在大型组件的事件逻辑中,加入这种机制个人认为是非常好的。

这里提供一个实现思路,鄙人愚见,请大神指点。
自定义浏览器事件,模拟浏览器事件流_第1张图片

创建自己的对象的事件流

 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)
    }
}

你可能感兴趣的:(javascript)