DOM事件

DOM事件模型

DOM0级模型

又称为原始事件模型,在该模型中,事件不会传播,即没有事件流的概念。这种方式所有浏览器都兼容,但是逻辑与显示并没有分离。事件绑定监听函数比较简单,有两种方式:

  • HTML代码中直接绑定:

    <input type="button" onclick="fun()">
  • 通过JS代码指定属性值:

    var btn = document.getElementById('.btn');
    btn.onclick = fun;

    移除监听函数:

    btn.onclick = null;
IE事件模型

IE事件模型共有两个过程:

  • 事件处理阶段(target phase)。事件到达目标元素,触发目标元素的监听函数。
  • 事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。

事件绑定监听函数的方式如下:

// eventType指定事件类型(注意加on),handler是事件处理函数
attachEvent(eventType, handler)

事件移除监听函数的方式如下:

detachEvent(eventType, handler)
DOM2级模型

属于W3C标准模型,现代浏览器(除IE6-8之外的浏览器)都支持该模型。在该事件模型中,一次事件共有三个过程:

  • 事件捕获阶段(capturing phase)。事件从 document 一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
  • 事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
  • 事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。

事件绑定监听函数的方式如下:

// eventType指定事件类型(不要加on);handler是事件处理函数;useCapture用于指定是否在捕获阶段进行处理,true表示在捕获阶段触发,false表示在冒泡阶段触发
addEventListener(eventType, handler, useCapture)

事件移除监听函数的方式如下:

removeEventListener(eventType, handler, useCapture)

img

捕获阶段,事件依次传递的顺序是:window –> document –> html–> body –> 父元素–>目标元素。

冒泡阶段,事件依次传递的顺序是:

一般的浏览器: (除IE6.0之外的浏览器):目标元素->父元素 -> body -> html -> document -> window

IE6.0:目标元素->父元素 -> body -> html -> document

通用的事件侦听器函数

const eventUtils = {
    // 添加事件
    addEvent: function(element, type, callback) {
        // DOM2 高版本浏览器的写法
        if(element.addEventListener) {
            element.addEventListener(type, callback, false);	// false表示在事件冒泡阶段触发
        }
        // DOM2 IE8及以下版本的写法
        else if(element.attachEvent) {
            element.attachEvent('on' + type, callback)
        }
        // DOM0的写法
        else {
            element['on' + type] = callback;
        }
    },
    // 移除事件
    removeEvent: function(element, type, callback) {
        // DOM2 高版本浏览器的写法
        if(element.removeEventListener) {
            element.removeEventListener(type, callback, false);	// false表示在事件冒泡阶段触发
        }
        // DOM2 IE8及以下版本的写法
        else if(element.detachEvent) {
            element.detachEvent('on' + type, callback);
        }
        // DOM0的写法
        else {
            element['on' + type] = null;
        }
    },
    // 获取 Event 对象
    getEvent: function(event) {
        // 普通浏览器的写法为 event,IE678 的写法为 window.event
        return event || window.event;
    },
    // 获取事件类型
    getType:function(event) {
        return event.type;
    },
    // 获取事件目标
    getTarget: function(event) {
        // 普通浏览器的写法为 event.target,IE8及以下版本的写法为 event.srcElement
        return event.target || event.srcElement;
    },
	// 阻止事件冒泡
	stopPropagation: function(event) {
        if(event.stopPropagation) {
            event.stopPropagation();
        }
        // IE10及以下版本的写法
        else {
            event.cancelBubble = true;
        }
	},
	// 阻止事件默认行为
	preventDefault: function(event) {
        if(event.preventDefault) {
            event.preventDefault();
        }
        // IE8及以下版本的写法
        else {
            event.returnValue = false;
        }
    }
};

事件代理/事件委托

事件在冒泡过程中会上传到父元素,因此可以把子元素的事件侦听函数定义在父元素上,由父元素的侦听函数统一处理多个子元素的事件。

下面例子中的 ul 列表中有多个列表项 a 标签,如果给每个链接都添加一个事件,那么会过于消耗内存和性能,我们希望,只绑定一次事件,即可应用到多个元素上。因此可以在父元素 ul 上注册一个 click 事件,点击某个子元素 a 会向父元素冒泡,父元素侦听到 click 事件后,根据 event.target 的值判断点击的是哪个 a 标签,从而作出相应的处理。

<ul class="list">
	<li><a href="#">111</a></li>
	<li><a href="#">222</a></li>
	<li><a href="#">333</a></li>
</ul>
const list = document.querySelector('.list');
eventUtils.addEvent(list, 'click', function(e) {
    let event = e || window.event;
    let target = event.target || event.srcElement;
    // 满足某个条件,才执行后续代码
    if(target.tagName.toLowerCase() === 'a') {
        console.log(target.textContent);
    }
});