⤴Top⤴

事件代理

博客分类: 前端

事件代理

事件代理

事件流

几个重要的事件定义:

DOM 事件流主要包括三个阶段:

event-bubbling

然而需要注意的是:

事件模型

DOM0 级事件

DOM0 级事件又称为原始事件模型,在该模型中,事件不会传播,即没有事件流的概念。事件绑定监听函数比较简单, 有两种方式:

<!-- 内联 -->
<button id="myBtn" onclick="sayName()">click</button>
// 动态绑定
// 一个 DOM 对象只能注册一个类型的事件,否则覆盖
// 事件处理程序在当前元素的作用域中运行,this 指向当前元素
var btn = document.getElementById('myBtn')[];
btn.onclick = _ => console.log(this.id); // 'myBtn'

// 移除绑定事件
btn.onclick = null;

为什么木有 DOM1 级事件 ? 是因为 DOM1 标准中并没有定义事件相关的内容 😳。

DOM2 级事件

addEventListener

DOM2 级事件 新增两个方法用来添加和移除事件处理程序,第三个参数表示采取哪种事件流处理程序,默认为 false,即冒泡;true 为捕获:

// 一个 DOM 对象能够注册一个类型的多个事件,依次触发
btn.addEventListener('click', handler, false); // 冒泡

btn.addEventListener('click', function() {
  alert(this.id);
}, false);
// 移除事件监听,注意要对应同一个事件才有效。若直接采用匿名函数表示,则会识别成不同函数,此时移除无效
btn.removeEventListener('click', handler, false); // 有效

btn.removeEventListener('click', function() {
  alert(this.id);
}, false); // 无效

attachEvent

IE 事件处理程序实现了与 DOM 中类似的两个方法:

由于 IE8 及更早版本只支持冒泡事件,因此只能将添加的事件程序添加到冒泡阶段,且与 DOM0 级的主要区别是事件处理程序的作用域,皆为全局作用域:

// 和 DOM0 一样采用 on + 事件名
// attachEvent 方法也可对一个 DOM 对象注册一个类型的多个事件,但会反向触发
btn.attachEvent('onclick', _ => console.log(this === window)); // true

btn.attachEvent('onclick', _ => console.log('tate'); // 'tate' 先触发

兼容写法

跨浏览器的事件处理的兼容性写法:

var EventUtil = {
  // 添加句柄
  addHandler(ele, type, handler) {
    if (ele.addEventListener) {
      ele.addEventListener(type, handler, false);
    } else if (ele.attachEvent) { // 兼容 IE8 及以下
      ele.attachEvent('on' + type, handler);
    } else {
      ele['on' + type] = handler;
    }
  },
  // 删除句柄
  removeHandler(ele, type, handler) {
    if (ele.removeEventListener) {
      ele.removeEventListener(type, handler, false);
    } else if (ele.detachEvent) {
      ele.detachEvent('on' + type, handler);
    } else {
      ele['on' + type] = null;
    }
  }
}

事件对象

当一个事件被触发时,会产生一个事件对象(event), 这个对象里面包含了与该事件相关的属性或者方法。DOM 和 IE 中的事件常用对象相比较:

作用 DOM IE
获取事件类型 event.type event.type
获取事件源 event.target event.srcElement
阻止默认行为 event.preventDefault() event.returnValue = false
阻止冒泡行为 event.stopPropagation() event.cancelBubble = true

DOM 事件中还有个 currentTarget 属性,与 target 的区别是:

DOM 事件中还可以通过 stopPropagation 阻止冒泡行为,与 stopImmediatePropagation 的区别是:

在 DOM 事件中,兼容 DOM 的浏览器都会将一个 event 对象作为参数传入到事件处理程序中,而 IE 事件中的 event 是一个 window 全局对象:

// DOM 事件
btn.addEventListener('click', (event) => {
  console.log(event.type); // 'click'
}, false);

// IE 事件
btn.attachEvent('onclick', _ => console.log(window.event.type)); // 'click'

跨浏览器的事件对象的兼容性写法:

var EventUtil = {
  // 获取事件对象
  // IE 模型中 event 是一个全局唯一的对象绑定在 window 对象上
  getEvent(event) {
    return event ? event : window.event;
  },
  // 获取类型
  getType(event) {
    return event.type;
  },
  getElement(event) {
    return event.target || event.srcElement;
  },
  // 阻止默认事件
  preventDefault(event) {
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  },
  // 阻止冒泡
  stopPropagation(event) {
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  }
}

事件代理

事件在冒泡过程中会上传到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理(Event delegation)。事件代理利用了事件冒泡,只指定一个事件处理程序,便可以管理某一类型的所有事件。

<ul id="color-list">
  <li>red</li>
  <li>yellow</li>
  <li>green</li>
  <li>orange</li>
  <!-- ...动态扩展 -->
</ul>
var colorList = document.getElementById('color-list');
var liArr = colorList.querySelectorAll('ul li');

colorList.addEventListener('click', showColor, false);

function showColor(ev) {
  var event = ev || window.event;
  var targetElement = event.target || event.srcElement;

  var content = targetElement.innerHTML; // 获取标签内容
  var index = [].indexOf.call(liArr, targetElement); // 获取索引值

  if (targetElement.nodeName.toLowerCase() === 'li') {
    alert(index + ' : ' + content);
  }
}

事件模拟

createEvent() - 生成一个事件对象,参数是事件类型,比如:

事件类型 事件初始化方法
UIEvents event.initUIEvent
MouseEvents event.initMouseEvent
MutationEvents event.initMutationEvent
HTMLEvents event.initEvent
Event event.initEvent
CustomEvent event.initCustomEvent
KeyboardEvent event.initKeyEvent

dispatchEvent() - 当前节点上触发指定事件,从而触发监听函数的执行,参数是一个 Event 对象的实例,如果在事件传播过程中调用了 event.preventDefault 方法,则返回 false,否则返回 true。IE 用 fireEvent()

document.addEventListener('myEvent', function (event) {
  console.log('Name: %s, Age: %d', event.name, event.age); // Name: tate, Age: 18
}, false);

//创建 event 的对象实例。
var event = document.createEvent('HTMLEvents');
// 3个参数:事件类型,是否冒泡,是否阻止浏览器的默认行为
event.initEvent('myEvent', true, true);
// 自定义事件属性,只要你开心
event.name = 'tate';
event.age = 18;

//触发自定义事件
document.dispatchEvent(event);

IE 为 createEventObject(),不接受参数,返回通用 event 对象:

var event = document.createEventObject();
event.bubbles = true;
event.cancelable = true;
event.name = 'tate';
targetElement.fireEvent('onmouseover', event); // 触发事件

模拟事件示例如下:

参考链接

  1. javaScript事件(一) 事件流 By starof
  2. JS 事件模型 By simon_woo
  3. 事件触发器 - dispatchEvent By magic__man
  4. 深入理解 DOM 事件机制系列第四篇 —— 事件模拟 By 小火柴的蓝色理想