SOURCE

// 处理DOM2级事件绑定的兼容性问题

/**
 * @param curEle 绑定的元素
 * @param eventType 事件类型
 * @param eventFn  事件回调
 */
function bind(curEle, eventType, eventFn) {
  // 标准浏览器直接使用
  if ("addEventListener" in document) {
    curEle.addEventListener(eventType, eventFn, false)
    return;
  }
  // curEle.attachEvent("on"+eventType, eventFn.bind(curEle))
  // 解决this问题, 将函数提出 (这里不直接使用Function.prototype.bind也是防止兼容问题)
  var tempFn = function () {  // 把处理前的标签贴在当前属性上
    eventFn.call(curEle);
  }
  // 存储包装前的, 在移除的时候用到
  tempFn.photo = eventFn;
  if (!curEle["myBind_" + eventType]) {
    // 不存在即赋值为一个数组  由于要存储多个方法, 所以让其值为数组
    curEle["myBind_" + eventType] = []
  }

  // 解决重复问题, 往里面添加的时候, 判断是否已经存在
  var ary = curEle["myBind_" + eventType]
  for (var i = 0; i < ary.length; i++) {
    var cur = ary[i];
    if (cur.photo === eventFn) {
      // 如果在数组中已经存在的话,  那么就不再需要添加该事件
      return;
    }
  }
  ary.push(tempFn)
  curEle.attachEvent("on" + eventType, tempFn)
}

function unbind(curEle, eventType, eventFn) {
  if ("removeEventListener" in document) {
    curEle.removeEventListener(eventType, eventFn, false)
    return;
  }

  // 把包装后的结果取出
  var ary = curEle["myBind_" + eventType];
  for (var i = 0; i < ary.length; i++) {
    if (ary[i].photo === eventFn) {
      ary.splice(i, 1);
      curEle.detachEvent("on" + eventType, eventFn);
      break;
    }
  }
}

// 三大问题: 顺序  重复   this


// 顺序问题: 不用浏览器自带的事件池, 而是自己模拟标准浏览器的事件池
/**
 * 创建事件池 且将元素绑定的方法依次添加到事件池中
 * @param curEle
 * @param eventType
 * @param eventFn
 */
function on(curEle, eventType, eventFn) {
  if (!curEle["myEvent" + eventType]) {
    curEle["myEvent" + eventType] = [];
  }
  var ary = curEle["myEvent"]
  for (var i = 0; i < ary.length; i++) {
    var cur = ary[i];
    if (cur === eventFn) {
      return;
    }
  }
  ary.push(eventFn)

  //执行on的时候 给当前元素绑定点击行为, 当点击的时候 执行run方法
  // curEle.addEventListener(eventType, run, false);
  bind(curEle, eventType, run);
}

/**
 * 在事件池中将一个方法移除
 * @param curEle
 */
function off(curEle, eventType, eventFn) {
  // 获取自定义事件池
  var ary = curEle["myEvent" + eventType]
  for (var i = 0; i < ary.length; i++) {
    var cur = ary[i]
    if (cur === eventFn) {
      // ary.splice(i, 1) // 为了防止塌陷问题 不使用splice 需要保留索引以及数组长度和数据位置
      ary[i] = null;
      break;
    }
  }
}

// 给当前元素绑定一个run方法, 在run方法中根据自己存储的顺序分别把绑定的方法执行
function run(event) {
  // this 指向的是当前点击的元素

  var e = event || window.event;
  // 获取自己事件池中绑定的方法, 让这些方法执行
  var flag = e.target ? true : false;
  if (!flag) {
    e.target = e.srcElement;
    e.pageX = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)
    e.pageY = event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)
    e.preventDefault = function () {
      e.returnValue = false;
    }
    e.stopPropagation = function () {
      e.cancelBubble = true;
    }
  }
  var curEle = e.target
  var eventType = e.type;

  var ary = curEle["myEvent" + eventType];
  for (var i = 0; i < ary.length; i++) {
    var tempFn = ary[i];

    // 在执行过程中删除一些方法的时候 ary被改变了 里面有null没法执行 但是run的时候索引会出问题
    if (typeof tempFn === "function") {
      tempFn.call(curEle);
    } else {
      // 说明当前这一样是null  所以可以被移除
      ary.splice(i, 1)
      i--;
    }

  }
}
console 命令行工具 X clear

                    
>
console