./images/js-event-target.png
在 JS 中事件是与浏览器进行交互的主要途径,事件与 DOM 进行交互是最常见的方式,但是也可以用于非 DOM 代码中,实现自定义的事件。自定义事件的主要概念是创建一个管理事件的对象,用来删除,增加,触发事件等。事件是瞬间触发的,所以自定义事件也一样,当程序执行到某处时,触发了用户注册的事件处理程序完成这个过程。

在下面我们实现了一个自定义的事件管理类,以及一个弹出层对话框的 Dialog 组件,这个组件继承了事件类里的方法,通过给 Dialog 实例注册事件以及用户的操作进行触发,完成了自定义事件大概的过程。

下面的内容涉及到了 JS 中 prototype chain,继承模式等相关姿势,不太熟悉的同学可以先看看 JS 面向对象部分相关的内容。

先上代码

function EventTarget() {
  this._handlers = {};
}

EventTarget.prototype = {
  constructor: EventTarget,
  addEvent: function (type, fn) {
    if (typeof type === 'string' && typeof fn === 'function') {
      if (typeof this._handlers[type] === 'undefined') {
        this._handlers[type] = [];
      }
      this._handlers[type].push(fn);
    }
  },
  fireEvent: function (event) {
    if (!event.target) {
      event.target = this;
    }

    if (this._handlers[event.type] instanceof Array) {
      var handlers = this._handlers[event.type];
      for (var i = 0; i < handlers.length; i++) {
        handlers[i](event);
      }
    }
  },
  removeEvent: function (type, handler) {
    if (this._handlers[type] instanceof Array) {
      var handlers = this._handlers[type];
      for (var i = 0; i < handlers.length; i++) {
        if (handlers[i] === handler) {
          handlers.splice(i, 1);
          break;
        }
      }
    }
  },
};

这可以看成是一个工具类,写过 DOM 事件管理类的同学应该觉得比较熟悉,通过封装 addEventListener 或者 attachEvent 来进行事件注册等等。这里也一样只是进行了简单的封装。

_handlers 用于存放事件处理程序,addEvent 注册事件,也是注册事件类型,如果没有该事件类型,那么我们就创建一个自定义类型的事件,并把事件处理程序 push 到该类型的数组,所以一种 type 可能对应了多个事件处理程序,fireEvent 通过传入事件对象,以及 event.type 来进行事件触发(执行对应的函数),removeEvent 就是删除对应的事件处理程序。

那么用途呢?

下面我们定义了一个 Dialog 组件并继承了 EventTarget 就能绑定自定义的事件啦。。。

function extend(subType, supType) {
  var proto = Object(supType.prototype);
  subType.prototype = proto;
  subType.constructor = subType;
}

function Dialog(id) {
  EventTarget.call(this);
  this.dialog = document.getElementById(id);
  var that = this;
  document.querySelector('#close').onclick = function () {
    that.close();
  };
}

extend(Dialog, EventTarget);

Dialog.prototype.show = function () {
  this.dialog.style.display = 'block';
  this.fireEvent({
    type: 'showPageCover',
  });
};

Dialog.prototype.close = function () {
  this.dialog.style.display = 'none';
  this.fireEvent({
    type: 'closePageCover',
  });
};

function openDialog() {
  var dialog = new Dialog('dialog');
  dialog.addEvent('closePageCover', function () {
    document.querySelector('.page-cover').style.display = 'none';
  });
  dialog.addEvent('showPageCover', function () {
    document.querySelector('.page-cover').style.display = 'block';
  });
  dialog.show();
}

这里是组件的代码,以及绑定自定义的事件对遮罩层隐藏/显示,因为遮罩层是不属于 Dialog 组件的,所以如果将遮罩层的代码写到 Dialog 中,这样就会造成代码耦合度较高的情况,通过自定事件,将遮罩层处理的事件处理程序注册到 Dialog,实现松散耦合。

html 页面代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }

      .dialog {
        width: 300px;
        height: 200px;
        background-color: #666;
        position: absolute;
        top: 50%;
        left: 50%;
        margin-left: -150px;
        margin-top: -100px;
        border-radius: 5px;
        display: none;
      }

      .dialog #close {
        width: 20px;
        height: 20px;
        float: right;
        cursor: pointer;
        background-color: red;
        border: 0;
        position: relative;
      }

      .dialog .title {
        height: 20px;
      }
      .page-cover {
        width: 100%;
        height: 100%;
        position: absolute;
        background-color: #999;
        opacity: 0.4;
        display: none;
      }
    </style>
  </head>
  <body>
    <div class="page-cover"></div>
    <input type="button" value="Click" id="button" onclick="openDialog()" />
    <input type="button" value="Click Clear" onclick="clearClose()" />
    <div class="dialog" id="dialog">
      <img src="" id="close" />
      <div class="title">title</div>
      <div class="content"></div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

参考文章:

http://www.jb51.net/article/33697.htm > http://www.jb51.net/article/33698.htm > http://baike.baidu.com/view/1854779.htm > http://www.zhangxinxu.com/wordpress/2012/04/js-dom%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6/ > http://www.jb51.net/article/40978.htm