基于回调和基于事件的事件处理系统有什么区别?

lit*_*tis 5 javascript javascript-events

我正在研究一个跨浏览器的事件处理系统。我让一些开发人员检查我的代码。其中一位开发人员说我的实现是基于回调而不是真实事件。有什么不同?

为了您的方便(也作为要点),我在下面提供了我的实现的源代码。到目前为止,我还没有发现它有任何问题。它适用于我测试过的所有浏览器。

对于问题的糟糕描述,我很抱歉,我对纯事件部分不熟悉。

var evento = (function (window) {
  var win = window
    , doc = win.document
    , _handlers = {}
    , addEvent
    , removeEvent
    , triggerEvent;

  addEvent = (function () {
    if (typeof doc.addEventListener === "function") {
      return function (el, evt, fn) {
        el.addEventListener(evt, fn, false);
        _handlers[el] = _handlers[el] || {};
        _handlers[el][evt] = _handlers[el][evt] || [];
        _handlers[el][evt].push(fn);

      };
    } else if (typeof doc.attachEvent === "function") {
      return function (el, evt, fn) {
        el.attachEvent(evt, fn);
        _handlers[el] = _handlers[el] || {};
        _handlers[el][evt] = _handlers[el][evt] || [];
        _handlers[el][evt].push(fn);
      };
    } else {
      return function (el, evt, fn) {
        el["on" + evt] = fn;
        _handlers[el] = _handlers[el] || {};
        _handlers[el][evt] = _handlers[el][evt] || [];
        _handlers[el][evt].push(fn);
      };
    }
  }());

  // removeEvent
  removeEvent = (function () {
    if (typeof doc.removeEventListener === "function") {
      return function (el, evt, fn) {
        el.removeEventListener(evt, fn, false);
        Helio.each(_handlers[el][evt], function (fun) {
          if (fun === fn) {
            _handlers[el] = _handlers[el] || {};
            _handlers[el][evt] = _handlers[el][evt] || [];
            _handlers[el][evt][_handlers[el][evt].indexOf(fun)] = undefined;
          }
        });

      };
    } else if (typeof doc.detachEvent === "function") {
      return function (el, evt, fn) {
        el.detachEvent(evt, fn);
        Helio.each(_handlers[el][evt], function (fun) {
          if (fun === fn) {
            _handlers[el] = _handlers[el] || {};
            _handlers[el][evt] = _handlers[el][evt] || [];
            _handlers[el][evt][_handlers[el][evt].indexOf(fun)] = undefined;
          }
        });
      };
    } else {
      return function (el, evt, fn) {
        el["on" + evt] = undefined;
        Helio.each(_handlers[el][evt], function (fun) {
          if (fun === fn) {
            _handlers[el] = _handlers[el] || {};
            _handlers[el][evt] = _handlers[el][evt] || [];
            _handlers[el][evt][_handlers[el][evt].indexOf(fun)] = undefined;
          }
        });
      };
    }
  }());

  // triggerEvent
  triggerEvent = function (el, evt) {
    _handlers[el] = _handlers[el] || {};
    _handlers[el][evt] = _handlers[el][evt] || [];

    for (var _i = 0, _l = _handlers[el][evt].length; _i < _l; _i += 1) {
      _handlers[el][evt][_i]();
    }
  };

  return {
    add: addEvent,
    remove: removeEvent,
    trigger: triggerEvent,
    _handlers: _handlers
  };
}(this));
Run Code Online (Sandbox Code Playgroud)

kur*_*eko 2

我不在乎你的系统是基于回调、事件还是 lambda 演算。对于你在这里暴露的部分,它看起来写得相当好,并且承诺会做得很好(尽管我很好奇你是如何处理的removeEvent();))。

不过,我对您的实施有几点评论:

  • 每次添加事件处理程序时,无需检查所执行的浏览器。

我很惊讶有这么多人接受每次调用属性时检查属性是否存在的做法。没有人会在函数调用过程中将您的 IE 换成 FF(document.addEventListener如果您问我的话,任何愚蠢到定义除实际 ECMA-5 替换之外的属性的人都应该被鞭打致死),所以请检查您使用的平台从开始到结束,就像这样:

if (doc.addEventListener) {
    addEvent  = // ...
    freeEvent = // ...
}
else if (doc.attachEvent) {
    addEvent  =  // ...
    freeEvent =  // ...
}
/* etc. */
Run Code Online (Sandbox Code Playgroud)
  • 您提供了一个统一的接口来附加处理程序,但是根据您的代码将在其上执行的浏览器,实际的处理程序的行为会有所不同。

例如,在 IE8 中,事件目标的可用方式与 ECMA-5 约定中的方式不同。

如果您想提供真正的跨浏览器界面,您应该为事件处理程序提供统一的执行上下文。这可能包括一个“取消”功能,该功能将转换为以下内容:

cancel = function (e) { e.returnValue = false; }; // IE8-
cancel = function (e) { e.preventDefault();    }; // ECMA-5
Run Code Online (Sandbox Code Playgroud)

还应该恢复this到IE8-下的目标对象,并统一targetevent.target语义。

如果你真的想对程序员友善,你还可以解决一些奇怪的问题,比如

  • 当图像已被缓存时,加载事件不会在 IE8 中触发
  • 极其复杂的鼠标滚轮报告系统

可能还有其他一些。

我出于自己的目的而这样做的方法是围绕实际处理程序生成一个包装器,它可以处理所有平台特性并在调用实际用户代码之前建立一致的执行上下文。

最后一点:除了它的美观之外,我不太确定它是否仍然有必要支持 Netscape4 风格的事件。但这是一个信仰问题,所以...