回调式和事件式事件处理系统有什么区别?

5

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

为了方便起见(也作为 gist),我提供了我的实现源代码。到目前为止,我还没有发现任何问题。它在我测试过的所有浏览器中都可以正常工作。

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

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

我不知道那个开发者在说什么。你问过了吗?事件系统是可以响应特定操作并在该操作发生时引起某些行为的任何系统。回调只是一个函数,可以传递给另一个函数,在稍后的时间被调用。 - user2736012
1
请注意,在IE浏览器中,typeof doc.attachEvent返回的是object(如果已实现),因此你的代码中的那些分支将永远不会被执行。一些宿主对象返回的是unknown。宿主对象不需要完全符合ECMA-262的所有细节,因此你不应该依赖它们这样做。 - RobG
他说的是“代码不错。唯一的问题是你正在使用回调而不是事件。:) http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/” - little pootis
我的实现中说后续的回调没有被执行,但是当我们使用纯事件时,它们会被执行。但问题是 - 如何改进以支持纯事件。 - little pootis
1
如果这是你真正想问的问题,那么为什么要问两者之间的区别呢?这个人的解决方案已经在文章中给出。而且仅仅因为那个人决定一个错误将阻止其余处理程序,并不意味着它不是一个事件系统。他正在强加一种任意的区分。看看NodeJS。他们有自己的事件系统,但它的行为方式相同。 - user2736012
显示剩余5条评论
1个回答

2
我并不关心你的系统是基于回调、事件还是λ演算。就此公开的部分而言,它似乎写得相当好,并承诺能够完成一个好的工作(尽管我很好奇你是如何处理removeEvent()的;))。
然而,我对你的实现有一些评论:
  • 每次添加事件处理程序时检查执行的浏览器是不必要的。
我惊讶于接受每次在调用属性之前检查其存在的做法的人数。没有人会在函数调用中间将IE换成FF(如果你问我,任何愚蠢到定义实际上不是ECMA-5替代品的document.addEventListener属性的人都应该被鞭打致死),因此在开始时检查你所在的平台,然后完成它,像这样:
if (doc.addEventListener) {
    addEvent  = // ...
    freeEvent = // ...
}
else if (doc.attachEvent) {
    addEvent  =  // ...
    freeEvent =  // ...
}
/* etc. */
  • 你提供了一个统一的接口来附加事件处理程序,但是根据您的代码将在哪个浏览器上执行,实际的处理程序会有所不同。

例如,在IE8-中,事件的目标将无法像ECMA-5约定中那样以相同的方式可用。

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

cancel = function (e) { e.returnValue = false; }; // IE8-
cancel = function (e) { e.preventDefault();    }; // ECMA-5

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

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

  • 当图像已经被缓存时,IE8-中无法触发加载事件
  • 非常复杂的鼠标滚轮报告系统

对于我的自用目的,我采用了在实际处理程序周围生成包装器的方式,以处理所有平台特性并在调用实际用户代码之前建立一致的执行上下文。

最后一句话:除了它的美感之外,我不确定是否仍然有必要支持Netscape4样式的事件。但这是一个信仰问题,所以……


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接